Task #9611: Add support for writing DNS entries matrix.ungleich.cloud and matrix.0co2.cloud

This commit is contained in:
amalelshihaby 2021-08-09 09:43:11 +02:00
parent 5bb0c4cdda
commit 7986b825a7
31 changed files with 626 additions and 478 deletions

View File

@ -5,6 +5,7 @@ from django.forms import ModelForm
from django.utils.translation import get_language, ugettext_lazy as _
from django.core.exceptions import ValidationError
from .validators import domain_name_validator
from .models import VMInstance
from uncloud_pay.models import BillingAddress
@ -37,7 +38,24 @@ class InitialRequestForm(MainForm):
storage = forms.IntegerField(label='Storage', min_value=100, max_value=10000, initial=100)
pricing_name = forms.CharField(required=True)
class RequestHostedVMForm(InitialRequestForm):
class RequestDomainsNamesForm(MainForm):
homeserver_name = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Homeserver Name *'}))
webclient_name = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Webclient Name *'}))
is_open_registration = forms.BooleanField(required=False, initial=False)
def clean_homeserver_name(self):
homeserver_name = self.cleaned_data['homeserver_name']
if VMInstance.objects.filter(homeserver_domain=f"{homeserver_name}.matrix.ungleich.cloud").exists():
raise ValidationError("homeserver name already exists")
return homeserver_name
def clean_webclient_name(self):
webclient_name = self.cleaned_data['webclient_name']
if VMInstance.objects.filter(webclient_domain=f"{webclient_name}.matrix.0co2.cloud").exists():
raise ValidationError("webclient name already exists")
return webclient_name
class RequestDomainsForm(MainForm):
matrix_domain = DomainNameField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Matrix Domain *'}))
homeserver_domain = DomainNameField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Homeserver Domain *'}))
webclient_domain = DomainNameField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Webclient Domain *'}))

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.4 on 2021-08-06 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('matrixhosting', '0009_vminstance_vm_id'),
]
operations = [
migrations.RemoveField(
model_name='vminstance',
name='vm_id',
),
migrations.AddField(
model_name='vminstance',
name='vm_name',
field=models.CharField(blank=True, max_length=256, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-08-06 15:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('matrixhosting', '0010_auto_20210806_1511'),
]
operations = [
migrations.AlterField(
model_name='vminstance',
name='vm_name',
field=models.CharField(editable=False, max_length=253, unique=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-08-08 16:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('matrixhosting', '0011_alter_vminstance_vm_name'),
]
operations = [
migrations.AddField(
model_name='vminstance',
name='homeserver_domain',
field=models.CharField(blank=True, max_length=253, null=True, unique=True),
),
migrations.AddField(
model_name='vminstance',
name='webclient_domain',
field=models.CharField(blank=True, max_length=253, null=True, unique=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-08-08 16:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('matrixhosting', '0012_auto_20210808_1651'),
]
operations = [
migrations.AlterField(
model_name='vminstance',
name='homeserver_domain',
field=models.CharField(blank=True, max_length=253, unique=True),
),
migrations.AlterField(
model_name='vminstance',
name='webclient_domain',
field=models.CharField(blank=True, max_length=253, unique=True),
),
]

View File

@ -1,9 +1,10 @@
import logging
import uuid
import os
import json
import sys
import gitlab
from jinja2 import Environment, FileSystemLoader
import yaml
from django.db import models
from django.conf import settings
@ -21,7 +22,11 @@ class VMInstance(models.Model):
on_delete=models.CASCADE,
editable=True)
vm_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
vm_name = models.CharField(max_length=253, editable=False, unique=True)
homeserver_domain = models.CharField(max_length=253, unique=True, blank=True)
webclient_domain = models.CharField(max_length=253, unique=True, blank=True)
config = models.JSONField(null=False, blank=False)
@ -32,24 +37,18 @@ class VMInstance(models.Model):
termination_date = models.DateTimeField(blank=True, null=True)
def save(self, *args, **kwargs):
# Read the deployment yaml file and render the template
# Then save it as new yaml file and push it to github repo
# Save it as new yaml file and push it to github repo
if 'test' in sys.argv:
return super().save(*args, **kwargs)
template_dir = os.path.join(os.path.dirname(__file__), 'yaml')
env = Environment(loader = FileSystemLoader(template_dir),autoescape = True)
tmpl = env.get_template('deployment.yaml.tmpl')
result = tmpl.render(
name=self.vm_id
)
result = yaml.dump(self.config)
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_OAUTH_TOKEN)
project = gl.projects.get(settings.GITLAB_PROJECT_ID)
project.files.create({'file_path': settings.GITLAB_YAML_DIR + f'{self.vm_id}.yaml',
project.files.create({'file_path': settings.GITLAB_YAML_DIR + f'matrix-{self.vm_name}.yaml',
'branch': 'master',
'content': result,
'author_email': settings.GITLAB_AUTHOR_EMAIL,
'author_name': settings.GITLAB_AUTHOR_NAME,
'commit_message': f'Add New Deployment for {self.vm_id}'})
'commit_message': f'Add New Deployment for matrix-{self.vm_name}'})
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
@ -59,11 +58,11 @@ class VMInstance(models.Model):
return super().delete(*args, **kwargs)
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_OAUTH_TOKEN)
project = gl.projects.get(settings.GITLAB_PROJECT_ID)
f_path = settings.GITLAB_YAML_DIR + f'{self.vm_id}.yaml'
f_path = settings.GITLAB_YAML_DIR + f'matrix-{self.vm_name}.yaml'
file = project.files.get(file_path=f_path, ref='master')
if file:
project.files.delete(file_path=f_path,
commit_message=f'Delete {self.vm_id}', branch='master',
commit_message=f'Delete matrix-{self.vm_name}', branch='master',
author_email=settings.GITLAB_AUTHOR_EMAIL,
author_name=settings.GITLAB_AUTHOR_NAME)

View File

@ -1,3 +1,4 @@
import json
from matrixhosting.models import VMInstance
from uncloud_pay.models import Order
from django.db.models.signals import post_save
@ -5,6 +6,14 @@ from django.dispatch import receiver
@receiver(post_save, sender=Order)
def create_instance(sender, instance, created, **kwargs):
if not created:
return
machine = VMInstance.objects.filter(order=instance).first()
if not machine:
VMInstance.objects.create(owner=instance.owner, order=instance, config=instance.config)
order_config = json.loads(instance.config)
instance_config = {'cpuCores': order_config['cores'], 'ram': order_config['memory'], 'storage': order_config['storage'],
'matrixDomain': order_config['matrix_domain'], 'homeserverDomain': order_config['homeserver_domain'],
'webClientDomain': order_config['webclient_domain'], 'isOpenRegistration': order_config['is_open_registration']}
VMInstance.objects.create(owner=instance.owner, order=instance, vm_name=order_config['homeserver_domain'],
homeserver_domain=order_config['homeserver_domain'],webclient_domain=order_config['webclient_domain'],
config=instance_config)

View File

@ -24,6 +24,7 @@ p {
.d2 {
line-height:1.5em;
padding-top: 15px;
font-style: normal;
width: 40%;
float: left;
}

View File

@ -24,12 +24,7 @@ $( document ).ready(function() {
$('#createvm-modal-title').text("Error Occurred");
$('#createvm-modal-body').html(data.error.message);
} else {
// The payment has succeeded
// Display a success message
window.location.href = '/order/success/';
// modal_btn.attr('href', data.redirect).removeClass('sr-only sr-only-focusable');
// $('#createvm-modal-title').text("Order Succeeded");
// $('#createvm-modal-body').html("Order has been added and the instance will be ready soon");
window.location.href = data.redirect;
}
}
});

View File

@ -20,7 +20,8 @@ function fetch_pricing() {
dataType: 'json',
success: function (data) {
if (data && data['total']) {
$('#total').text(data['total']);
$('#total').text(data['total'] + " CHF");
$('#recurring_price').text(data['recurring_price'] + " CHF");
}
}
});

View File

@ -0,0 +1,6 @@
$(document).ready(function () {
$('input[name="filter"]').change(function(e) {
$(location).attr('href', $(e.target).data('url'));
});
});

View File

@ -1,127 +0,0 @@
{% extends "matrixhosting/base.html" %} {% load static i18n %}
{% block content%}
<!-- Page Content -->
{% csrf_token %}
<div>
<div class="container">
<div class="row">
<div class="col-md-12">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Description</th>
<th scope="col">Starting At</th>
<th scope="col">Config</th>
<th scope="col">Pricing Plan</th>
<th scope="col">OneTime Price</th>
<th scope="col">Recurring Price</th>
<th scope="col">Ending At</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr data-id="{{object.id}}">
<th scope="row">{{ object.id }}</th>
<td>{{ object.description }}</td>
<td>{{ object.starting_date }}</td>
<td>{{ object.config }}</td>
<td>{{ object.pricing_plan}}</td>
<td>{{ object.one_time_price }}</td>
<td>{{ object.recurring_price }}</td>
<td>{{ object.ending_date }}</td>
{% if object.ending_date %}
<td></td>
{% else %}
<td>
<button
class="btn btn-danger btn-sm cancel-subscription"
type="submit"
name="action"
>
Cancel
</button>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div
class="modal fade"
tabindex="-1"
role="dialog"
aria-labelledby="mySmallModalLabel"
aria-hidden="true"
id="mi-modal"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">Cancel Subscription</h4>
</div>
<div class="modal-body">
<p>
Are you sure that you want to cancel this subscription?. </p>
<p>
The instance will be active till the end date of the last bill and will be deleted
after that.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="modal-btn-yes">
Yes
</button>
<button type="button" class="btn btn-primary" id="modal-btn-no">
No
</button>
</div>
</div>
</div>
</div>
<div class="alert" role="alert" id="result"></div>
<!-- /.banner -->
{% endblock %}
{% block js_extra %}
<script type="text/javascript">
var modalConfirm = function (callback) {
$(".cancel-subscription").on("click", function (event) {
$('.selected').removeClass('selected');
$(event.target).parent().parent().addClass('selected');
$("#mi-modal").modal("show");
});
$("#modal-btn-yes").on("click", function () {
callback(true);
});
$("#modal-btn-no").on("click", function () {
callback(false);
$("#mi-modal").modal("hide");
});
};
modalConfirm(function (confirm) {
if (confirm) {
var selected_order = $('.selected').data('id');
$.ajax({
url: '{% url "matrix:dashboard" %}',
type: 'POST',
data: {'order_id': selected_order, 'csrfmiddlewaretoken': '{{ csrf_token }}',},
success: function (data) {
$("#mi-modal").modal("hide");
window.location.reload();
}
});
}
});
</script>
{% endblock %}

View File

@ -17,17 +17,17 @@
<!-- Primary Navigation
============================== -->
{% url 'matrix:index' as index_url %}
{% url 'matrix:payments' as payments_url %}
{% url 'matrix:billing' as payments_url %}
<nav class="primary-menu navbar navbar-expand-lg">
<div id="header-nav" class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
<li class="{% if request.path == index_url %}active{%endif%}"><a href="{{index_url}}">Home</a></li>
<li class="{% if request.path == index_url %}active{%endif%}"><a href="{{index_url}}">{%trans "Home" %}</a></li>
{% if not request.user.is_authenticated %}
<li><a href="">Pricing</a></li>
<li><a href="">Contact Us</a></li>
{% else %}
<li><a href=>Dashboard</a></li>
<li class="{% if request.path == payments_url %}active{%endif%}"><a href="{{payments_url}}">Payments</a></li>
<li class="{% if request.path == payments_url %}active{%endif%}"><a href="{{payments_url}}">{%trans "Payments" %}</a></li>
<li><a href="">Help</a></li>
{% endif %}
</ul>
@ -65,11 +65,11 @@
<ul class="dropdown-menu">
<li class="text-center text-3 py-2">Hi, {{request.user.username}}</li>
<li class="dropdown-divider mx-n3"></li>
<li><a class="dropdown-item" href=""><i class="fas fa-user"></i>My Profile</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-shopping-cart"></i>Orders</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-file-invoice"></i>Bills</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-credit-card"></i>Payment Methods</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-cloud"></i>Instances</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-user"></i>{%trans "My Profile" %}</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-tachometer-alt"></i>{%trans "Dashboard" %}</a></li>
<li><a class="dropdown-item" href="{% url 'matrix:orders' %}"><i class="fas fa-shopping-cart"></i>{%trans "Orders" %}</a></li>
<li><a class="dropdown-item" href="{% url 'matrix:billing' %}"><i class="fas fa-file-invoice"></i>{%trans "Billing" %}</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-cloud"></i>{%trans "Instances" %}</a></li>
<li class="dropdown-divider mx-n3"></li>
<li><a class="dropdown-item" href=""><i class="fas fa-life-ring"></i>Need Help?</a></li>
<li><a class="dropdown-item" href="{% url 'account_logout' %}"><i class="fas fa-sign-out-alt"></i>Sign Out</a></li>

View File

@ -0,0 +1,78 @@
{% load static i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
font-family: Avenir;
font-weight: 500;
line-height: 1.1em;
font-size: 16px;
}
.icon {
width: 16px;
height: 14px;
vertical-align: middle;
margin-right: 2px;
}
.footer {
margin-top: 70px;
font-size: 14px;
}
.footer p {
display: block;
-webkit-margin-before: 5px;
-webkit-margin-after: 5px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
}
.d6 {
width: 68%;
float: left;
font-size: 13px;
}
.wf {
width: 100%;
}
</style>
</head>
<body>
<div class="footer wf custom_footer">
<br/>
<div class="d6">
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/call.png' %}"/>
<span>+41 55 505 6266</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/msg.png' %}"/>
<span>buchhaltung-ag@ungleich.ch</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/home.png' %}"/>
<span>https://www.ungleich.ch</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/twitter.png' %}"/>
@ungleich
</p>
</div>
<div class="d7">
<div>
<p>Glarner Kantonalbank</p>
<p>
<span class="bold">IBAN: CH 4300 7730 0055 5931 177</span>
</p>
<p>
<span class="bold">BIC: GLKBCH22</span>
</p>
</div>
<p style="font-size: 13px; white-space: nowrap !important">Mwst-Nummer: CHE-156.970.649 MWST</span></p>
</div>
</div>
</body>
</html>

View File

@ -256,53 +256,6 @@
<a href="https://ungleich.ch/u/projects/open-chat/" class="btn btn-light">A free test ride on ungleich Matrix</a> </div>
</div>
</section>
<!-- Testimonial
============================================= -->
<section class="section">
<div class="container">
<h2 class="text-9 text-center">What people say about MatrixHosting</h2>
<p class="lead text-center mb-4">Quidam lisque persius interesset his et, in quot quidam</p>
<div class="row">
<div class="col-lg-10 col-xl-8 mx-auto">
<div class="owl-carousel owl-theme" data-autoplay="true" data-nav="true" data-loop="true" data-margin="30" data-stagepadding="5" data-items-xs="1" data-items-sm="1" data-items-md="1" data-items-lg="1">
<div class="item">
<div class="testimonial rounded text-center p-4">
<p class="text-4">“Easy to use, reasonably priced simply dummy text of the printing and typesetting industry. Quidam lisque persius interesset his et, in quot quidam possim iriure.”</p>
<strong class="d-block font-weight-500">Jay Shah</strong> <span class="text-muted">Founder at Icomatic Pvt Ltd</span> </div>
</div>
<div class="item">
<div class="testimonial rounded text-center p-4">
<p class="text-4">“I am happy Working with printing and typesetting industry. Quidam lisque persius interesset his et, in quot quidam persequeris essent possim iriure.”</p>
<strong class="d-block font-weight-500">Patrick Cary</strong> <span class="text-muted">Freelancer from USA</span> </div>
</div>
<div class="item">
<div class="testimonial rounded text-center p-4">
<p class="text-4">“Quidam lisque persius interesset his et, in quot quidam.”</p>
<strong class="d-block font-weight-500">De Mortel</strong> <span class="text-muted">Online Retail</span> </div>
</div>
<div class="item">
<div class="testimonial rounded text-center p-4">
<p class="text-4">“I have used them twice now. Good rates, very efficient service and it denies high street banks an undeserved windfall. Excellent.”</p>
<strong class="d-block font-weight-500">Chris Tom</strong> <span class="text-muted">User from UK</span> </div>
</div>
<div class="item">
<div class="testimonial rounded text-center p-4">
<p class="text-4">“Quidam lisque persius interesset his et, in quot quidam”</p>
<strong class="d-block font-weight-500">Mauri Lindberg</strong> <span class="text-muted">Freelancer from Australia</span> </div>
</div>
<div class="item">
<div class="testimonial rounded text-center p-4">
<p class="text-4">“Only trying it out since a few days. But up to now excellent. Seems to work flawlessly.”</p>
<strong class="d-block font-weight-500">Dennis Jacques</strong> <span class="text-muted">User from USA</span> </div>
</div>
</div>
<div class="text-center mt-4"><a href="#" class="btn-link text-4">See more people review<i class="fas fa-chevron-right text-2 ml-2"></i></a></div>
</div>
</div>
</div>
</section>
<!-- Testimonial end -->
<!-- Frequently asked questions
============================================= -->

View File

@ -28,7 +28,7 @@
</div>
<div class="d2" name="company_address">
<span class="bold">ungleich glarus ag</span>
<address>
<address style="font-style: normal;">
Bahnhofstrasse 1<br>
8783 Linthal<br>
Switzerland
@ -36,14 +36,13 @@
</div>
<div class="first-page">
<div class="d1" style="margin-top:15px;">
<div class="d1">
<b>
<span style="font-size: 16px">{{bill.billing_address.full_name}}</span>
</b>
<br/>
<span>{{bill.billing_address.owner.email}}</span>
<address>
<address style="font-style: normal;">
{{bill.billing_address.street}}<br>
{{bill.billing_address.city}}<br>
{{bill.billing_address.get_country_display}}
@ -51,17 +50,7 @@
</div>
</div>
<div class="d4" style="margin-top:15px;">
<div>
<div class="b1">
<span>{%trans "Date of invoice:" %}</span>
</div>
<div class="b2">
<span>{{bill.starting_date|date}}</span>
<br/>
</div>
</div>
<div>
<div class="bold">
<div class="b1">
<span>{%trans "Invoice Number:" %}</span>
</div>
@ -69,24 +58,27 @@
<span>#{{bill.id}}</span>
</div>
</div>
<div>
<div class="b1">
<span>{%trans "Date of invoice:" %}</span>
</div>
<div class="b2">
<span>{{bill.starting_date|date:"Y-m-d"}}</span>
</div>
</div>
<div>
<div class="b1">
<span>{%trans "Due Date:" %}</span>
</div>
<div class="b2">
<span>{{bill.due_date}}</span>
<span>{{bill.due_date|date:"Y-m-d"}}</span>
</div>
</div>
</div>
<div style="clear: both;">
</div>
<div class="d5" style="margin-top:20px; margin-bottom:10px important;">
<br/>
<span style="font-size: 2em !important; font-weight: bolder !important;">{%trans "INVOICE" %}</span>
<span style="font-size: 2em !important; font-weight: bolder !important;padding-left:5px"> {{bill.starting_date|date:"m-Y"}}</span>
</div>
<div style="clear: both;">
<table class="wf" style="margin-top:20px; margin-bottom:10px important;border-top: 1px solid gray;">
<thead >
@ -118,52 +110,18 @@
</span>
</p>
<p class="ts">
<span class="tl">{{vat_rate}}</span>
<span class="tl">{{bill.billing_address.get_country_display}} VAT {{vat_rate}}%</span>
<span class="tr">{{tax_amount}}
</span>
</p>
</div>
<div class="wf pc" style="background-color: #f5f5f5;padding-top:5px;padding-bottom:5px;">
<p class="bold" style="padding-top:5px;">
<span class="tl" style="font-size: 16px">{%trans "CHF" %}</span>
<span class="tl" style="font-size: 16px">{%trans "Total CHF" %}</span>
<span class="tr" style="font-size: 16px">{{bill.sum}}</span>
</p>
</div>
</div>
<div class="footer wf custom_footer">
<br/>
<div class="d6">
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/call.png' %}"/>
<span>+4(144) 534-6622</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/msg.png' %}"/>
<span>buchhaltung-ag@ungleich.ch</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/home.png' %}"/>
<span>https://www.ungleich.ch</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/twitter.png' %}"/>
@ungleich
</p>
</div>
<div class="d7">
<div>
<p>Glarner Kantonalbank</p>
<p>
<span class="bold">IBAN: CH 4300 7730 0055 5931 177</span>
</p>
<p>
<span class="bold">BIC: GLKBCH22</span>
</p>
</div>
<p style="font-size: 13px; white-space: nowrap !important">Mwst-Nummer: CHE-156.970.649 MWST</span></p>
</div>
</div>
</div>
</div>

View File

@ -8,29 +8,29 @@
<div class="container">
<!-- Steps Progress bar -->
<div class="row mt-3 mb-4">
<div class="col-lg-12 mx-auto mb-4">
<div class="row widget-steps">
<div class="col-4 step complete">
<div class="step-name">{%trans "Details" %}</div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<a href="#" class="step-dot"></a> </div>
<div class="col-4 step active">
<div class="step-name">{%trans "Confirm" %}</div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<a href="#" class="step-dot"></a> </div>
<div class="col-4 step ">
<div class="step-name">{%trans "Success" %}</div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<a href="#" class="step-dot"></a> </div>
</div>
</div>
<div class="col-md-9 col-lg-8 col-xl-7 mx-auto">
<div class="col-lg-12 mx-auto mb-4">
<div class="row widget-steps">
<div class="col-4 step complete">
<div class="step-name">{%trans "Details" %}</div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<a href="#" class="step-dot"></a> </div>
<div class="col-4 step active">
<div class="step-name">{%trans "Confirm" %}</div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<a href="#" class="step-dot"></a> </div>
<div class="col-4 step ">
<div class="step-name">{%trans "Success" %}</div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<a href="#" class="step-dot"></a> </div>
</div>
</div>
<div class="col-md-9 col-lg-8 col-xl-7 mx-auto">
<div id="order-detail{{order.pk}}" class="bg-white shadow-sm rounded p-4 mb-4">
{% if messages %}
<div class="alert alert-warning">
@ -148,51 +148,52 @@
</div>
</div>
</div>
<hr class="">
</div>
<form id="virtual_machine_create_form" action="" method="POST">
{% csrf_token %}
<div class="row">
<div class="col-sm-12">
By clicking "Place order" you agree to our <a href="">Terms of Service</a> and this plan will charge your account balance with {{pricing.total|floatformat:2}} CHF
</div>
<div class="col-sm-12 order-confirm-btn text-right">
<button class="btn choice-btn btn-primary" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
{% trans "Confirm Order" %}
</button>
</div>
</div>
</form>
{% endif %}
</div>
</div>
<div class="col-md-4 col-lg-4 col-xl-5 mx-auto">
<div class="row bg-white shadow-sm rounded p-3 p-4 pb-sm-4 mb-2">
<hr>
<form id="virtual_machine_create_form" action="" method="POST">
{% csrf_token %}
<div class="row">
<div class="col-12 col-sm-12">
<div class="form-group">
<div class="input-group">
{{domains_form.homeserver_name}}
<div class="input-group-append"><span class="input-group-text">.matrix.ungleich.cloud</span></div>
</div>
{{ domains_form.homeserver_name.errors }}
</div>
<div class="form-group">
<div class="input-group">
{{domains_form.webclient_name}}
<div class="input-group-append"><span class="input-group-text">.matrix.0co2.cloud</span></div>
</div>
{{ domains_form.webclient_name.errors }}
</div>
<div class="form-check custom-control custom-checkbox">
{{domains_form.is_open_registration}}
<label class="custom-control-label" for="{{ domains_form.is_open_registration.id_for_label}}">{% trans "Is Open registration possible?" %}</label>
{{ domains_form.is_open_registration.errors }}
</div>
<hr>
</div>
<div class="col-sm-12">
By clicking "Place order" you agree to our <a href="">Terms of Service</a> and this plan will charge your account balance with {{pricing.total|floatformat:2}} CHF
</div>
<div class="col-sm-12 order-confirm-btn mt-2 text-right">
<button class="btn choice-btn btn-primary" id="btn-create-vm" type="submit">
{% trans "Confirm Order" %}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header"><h5 class="createvm-modal-title">{% trans "Order Processing..." %}</h5>
</div>
<div class="modal-body">
<div class="row align-items-center flex-row">
<div class="modal-icon col col-sm-12 text-center">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span>
</div>
<div class="modal-text col col-sm-12 text-center" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %}
</div>
</div>
<div class="modal-footer mt-4">
<a id="createvm-modal-done-btn" class="btn btn-success btn-ok btn-wide sr-only sr-only-focusable" href="{% url 'matrix:order_success' %}">{% trans "OK" %}</a>
<button id="createvm-modal-close-btn" type="button" class="btn btn-danger btn-ok btn-wide sr-only sr-only-focusable" data-dismiss="modal" aria-label="create-vm-close">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_extra %}
@ -203,7 +204,4 @@ aria-hidden="true" data-backdrop="static" data-keyboard="false">
<!-- jQuery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
<!-- Custom JS -->
{% compress js %}
<script type="text/javascript" src="{% static 'matrixhosting/js/order.js' %}"></script>
{% endcompress %}
{% endblock js_extra %}

View File

@ -7,6 +7,29 @@
{% block content %}
<div class="container">
<!-- Steps Progress bar -->
{% 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">&times;</span>
</button>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
<div class="row mt-3 mb-4">
<div class="col-lg-12 mx-auto mb-4">
<div class="row widget-steps">
@ -42,27 +65,7 @@
{{ details_form.non_field_errors }}
</div>
{% endif %}
<div class="row">
<div class="col-12 col-sm-6">
<div class="form-group">
{{details_form.matrix_domain}}
{{ details_form.matrix_domain.errors }}
</div>
<div class="form-group">
{{details_form.homeserver_domain}}
{{ details_form.homeserver_domain.errors }}
</div>
<div class="form-group">
{{details_form.webclient_domain}}
{{ details_form.webclient_domain.errors }}
</div>
<div class="form-check custom-control custom-checkbox">
{{details_form.is_open_registration}}
<label class="custom-control-label" for="{{ details_form.is_open_registration.id_for_label}}">{% trans "Is Open registration possible?" %}</label>
{{ details_form.is_open_registration.errors }}
</div>
{{details_form.pricing_name.as_hidden}}
</div>
<div class="row">
<div class="col-12 col-sm-6">
<div class="form-group px-n4">
<div class="input-group">
@ -97,6 +100,7 @@
</div>
</div>
</div>
{{details_form.pricing_name.as_hidden}}
</div>
</div>
</div>
@ -186,17 +190,19 @@
<hr class="mt-1 mx-n3">
</div>
<div class="row align-items-center flex-row">
<div class="col col-lg-6">
<h4 class="text-3 font-weight-400">{% trans "Total To Pay"%}</h4>
<small>
({% if matrix_vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
</small>
<div class="col col-lg-12">
<p>{% trans "Setup Fees"%} <span class="float-right">{{matrix_vm_pricing.set_up_fees}} CHF</span></p>
<p>{% trans "Recurring Price"%} <span id="recurring_price" class="float-right">{{request.session.pricing.recurring_price}} CHF</span></p>
{% if matrix_vm_pricing.discount_amount %}
<div class="text-muted"><span class="text-2">Discount {{matrix_vm_pricing.discount_amount}}</span><span class="text-2 p-1">CHF</span></div>
{% endif %}
</div>
<div class="col col-lg-6">
<div class="float-right"><span id="total" class="text-4">{{request.session.pricing.total|floatformat}}</span><span class="text-2 p-1">CHF</span></div>
<p>{% trans "Discount"%} <span class="float-right text-danger"> - {{matrix_vm_pricing.discount_amount}} CHF</span></p>
{% endif %}
<hr>
<p class="text-4 font-weight-500">{% trans "Total To Pay"%}
<small>
({% if matrix_vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
</small>
<span id="total" class="float-right">{{request.session.pricing.total}} CHF</span>
</p>
</div>
</div>
<hr class="mt-2 mx-n3">
@ -205,9 +211,9 @@
{% with cards_len=cards|length %}
<p class="text-muted">
{% if cards_len > 0 %}
{% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below.{% endblocktrans %}
{% 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 %}
{% else %}
{% blocktrans %}You haven't any active cards, Please fill in your credit card information below.{% endblocktrans %}
{% blocktrans %}You haven't enough balance in your wallet, Please fill in your credit card information below.{% endblocktrans %}
{% endif %}
</p>
<div>
@ -247,8 +253,11 @@
{% endwith %}
</div>
{% else %}
<p class="text-muted">
{% blocktrans %}Your wallet has enough balance, Press Continue to fill the VM instance settings.{% endblocktrans %}
</p>
<div class="text-right">
<button id="continue-btn" class="btn btn-primary btn-wide" type="submit">{%trans "Checkout" %}</button>
<button id="continue-btn" class="btn btn-primary btn-wide" type="submit">{%trans "Continue" %}</button>
</div>
{% endif %}
</div>

View File

@ -39,9 +39,9 @@
<div class="my-4">
<p class="text-success text-20 line-height-07"><i class="fas fa-check-circle"></i></p>
<p class="text-success text-8 font-weight-500 line-height-07">{%trans "Success!" %}</p>
<p class="lead">{%trans "Order has been successfully added" %}</p>
<p class="lead">{%trans "Order has been successfully added. Your VM will be up and running in a few moments. " %}</p>
</div>
<p class="text-3 mb-4">{%trans "Your current balance is" %}<span class="text-4 font-weight-500"> {{balance}} CHF. </span>{%trans "See transaction details under" %} <a class="btn-link" href="{% url 'matrix:payments' %}">{%trans "Payments" %}</a>.</p>
<p class="text-3 mb-4">{%trans "We will send you a confirmation email as soon as it is ready." %} {%trans "Go to your dashboard" %} <a class="btn-link" href="{% url 'matrix:billing' %}">{%trans "Billing" %}</a>.</p>
<a href="{% url 'matrix:invoice_download' %}" class="btn btn-white border border-success text-success" role="button"><i class="fas fa-print"></i> {%trans "Download Invoice" %}</a>
</div>
</div>

View File

@ -0,0 +1,147 @@
{% extends "matrixhosting/base.html" %}
{% load static i18n compress %}
{% block title %} Payments {% endblock %}
{% block content %}
<!-- Page Content -->
{% csrf_token %}
<div class="container">
<div class="row p-1 mt-4">
<div class="col-lg-12 bg-white shadow-sm border border-light rounded py-4 mb-4">
<h3 class="text-5 font-weight-400 d-flex align-items-center px-1 mb-4">{% trans "Orders"%}</h3>
<!-- Title
=============================== -->
<div class="transaction-title py-2 px-1">
<div class="row">
<div class="col-1 col-sm-1 text-center"><span class="">{% trans "ID"%}</span></div>
<div class="col-2 col-sm-2 text-center"><span class="">{% trans "Date"%}</span></div>
<div class="col-3 col-sm-3">{% trans "Description" %}</div>
<div class="col-1 col-sm-1 d-none d-sm-block text-center">{% trans "OneTime Price"%}</div>
<div class="col-1 col-sm-1 text-right">{% trans "Recurring Price"%}</div>
<div class="col-1 col-sm-1 text-center"><span class="">{% trans "Currency"%}</span></div>
<div class="col-2 col-sm-2 text-center">{% trans "End Date"%}</div>
<div class="col-1 col-sm-1 text-center" >{% trans "Active"%}</div>
</div>
</div>
<!-- Title End -->
<!-- Transaction List
=============================== -->
<div class="transaction-list">
{% for order in object_list %}
<div class="transaction-item px-1 py-4" data-id={{order.id}}>
<div class="row align-items-center flex-row">
<div class="col-1 col-sm-1 text-center"> <span class="d-block text-3 font-weight-400">#{{order.id}}</span></div>
<div class="col-2 col-sm-2 text-center"> <span class="d-block text-3 font-weight-300">{{order.starting_date|date:"Y-m-d"}}</span></div>
<div class="col-3 col-sm-3"> <span class="d-block text-muted text-3">{{order.description}}</span></div>
<div class="col-1 col-sm-1 d-none d-sm-block text-right text-2"> <span class=" text-uppercase">{{order.one_time_price}}</span> </div>
<div class="col-1 col-sm-1 d-none d-sm-block text-right text-2"> <span class=" text-uppercase">{{order.recurring_price}}</span> </div>
<div class="col-1 col-sm-1 text-center text-2"><span class="">{{order.currency}}</span></div>
<div class="col-2 col-sm-2 d-none d-sm-block text-center text-2"> <span class=" text-uppercase">{{order.ending_date|date:"Y-m-d"}}</span> </div>
<div class="col-1 col-sm-1 text-center">
{% if order.is_closed %}
<span class="text-danger" data-toggle="tooltip" data-original-title="Closed"><i class="text-danger fas fa-times-circle"></i></span>
{% else %}
<span class="text-success" data-toggle="tooltip" data-original-title="Active"><i class="fas fa-check-circle"></i></span>
{% endif %}
<!-- <span class="float-right" data-toggle="tooltip" data-original-title="See Details"><i class="fas fa-bars"></i></span> -->
</div>
</div>
{% if not order.ending_date %}
<a href="#" class="cancel-subscription float-right text-1">{% trans "Cancel Subscription"%}</a>
{% endif %}
</div>
{%endfor%}
</div>
<!-- Transaction List End -->
<!-- Pagination will handled later
============================================= -->
<ul class="pagination justify-content-center mt-4 mb-0" style="display: none;">
<li class="page-item disabled"> <a class="page-link" href="#" tabindex="-1"><i class="fas fa-angle-left"></i></a> </li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active"> <a class="page-link" href="#">2 <span class="sr-only">(current)</span></a> </li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item d-flex align-content-center flex-wrap text-muted text-5 mx-1">......</li>
<li class="page-item"><a class="page-link" href="#">15</a></li>
<li class="page-item"> <a class="page-link" href="#"><i class="fas fa-angle-right"></i></a> </li>
</ul>
<!-- Paginations end -->
</div>
</div>
</div>
<div
class="modal fade"
tabindex="-1"
role="dialog"
aria-labelledby="mySmallModalLabel"
aria-hidden="true"
id="mi-modal"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">{% trans "Cancel Subscription"%}</h4>
</div>
<div class="modal-body">
<p>
{% trans "Are you sure that you want to cancel this subscription?."%} </p>
<p>
{% blocktrans %} The instance will be active till the end date of the last bill and will be deleted
after that. {% endblocktrans %}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="modal-btn-yes">
{% trans "Yes Cancel" %}
</button>
<button type="button" class="btn btn-secondary" id="modal-btn-no">
{% trans "Close" %}
</button>
</div>
</div>
</div>
</div>
<div class="alert" role="alert" id="result"></div>
<!-- /.banner -->
{% endblock %}
{% block js_extra %}
<script type="text/javascript">
var modalConfirm = function (callback) {
$(".cancel-subscription").on("click", function (event) {
$('.selected').removeClass('selected');
$(event.target).parent().addClass('selected');
$("#mi-modal").modal("show");
});
$("#modal-btn-yes").on("click", function () {
callback(true);
});
$("#modal-btn-no").on("click", function () {
callback(false);
$("#mi-modal").modal("hide");
});
};
modalConfirm(function (confirm) {
if (confirm) {
var selected_order = $('.selected').data('id');
$.ajax({
url: '{% url "matrix:orders" %}',
type: 'POST',
data: {'order_id': selected_order, 'csrfmiddlewaretoken': '{{ csrf_token }}',},
success: function (data) {
$("#mi-modal").modal("hide");
window.location.reload();
}
});
}
});
</script>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "matrixhosting/base.html" %}
{% load static i18n %}
{% load static i18n compress %}
{% block title %} Payments {% endblock %}
@ -41,15 +41,15 @@
================================ -->
<div class="col-12 mb-3" id="allFilters">
<div class="custom-control custom-radio custom-control-inline">
<input type="radio" id="allTransactions" name="allFilters" class="custom-control-input" checked="">
<input type="radio" id="allTransactions" name="filter" data-url="{% url 'matrix:billing' %}" class="custom-control-input" value="all" {% if not type %} checked=""{% endif %}>
<label class="custom-control-label" for="allTransactions">{% trans "All Transactions"%}</label>
</div>
<div class="custom-control custom-radio custom-control-inline">
<input type="radio" id="withdrawal" name="allFilters" class="custom-control-input">
<input type="radio" id="withdrawal" name="filter" class="custom-control-input" data-url="{% url 'matrix:billing' %}?type=withdraw" value="withdraw" {% if type == 'withdraw' %} checked=""{% endif %}>
<label class="custom-control-label" for="withdrawal">{% trans "Withdrawal"%}</label>
</div>
<div class="custom-control custom-radio custom-control-inline">
<input type="radio" id="deposit" name="allFilters" class="custom-control-input">
<input type="radio" id="deposit" name="filter" class="custom-control-input" data-url="{% url 'matrix:billing' %}?type=deposit" value="deposit" {% if type == 'deposit' %} checked=""{% endif %}>
<label class="custom-control-label" for="deposit">{% trans "Deposit"%}</label>
</div>
</div>
@ -84,7 +84,7 @@
<div class="transaction-item px-4 py-3" data-id={{payment.id}}>
<div class="row align-items-center flex-row">
<div class="col-1 col-sm-1 text-center"> <span class="d-block text-3 font-weight-400">#{{payment.id}}</span></div>
<div class="col-2 col-sm-2 text-center"> <span class="d-block text-3 font-weight-300">{{payment.timestamp|date:"M d, Y"}}</span> <span class="d-block text-1 font-weight-300 text-uppercase">{{payment.timestamp|date:"H:i"}}</span> </div>
<div class="col-2 col-sm-2 text-center"> <span class="d-block text-3 font-weight-300">{{payment.timestamp|date:"Y-m-d"}}</span> <span class="d-block text-1 font-weight-300 text-uppercase">{{payment.timestamp|date:"H:i"}}</span> </div>
<div class="col col-sm-4"> <span class="d-block text-muted text-3">{{payment.notes}}</span></div>
<div class="col-auto col-sm-2 d-none d-sm-block text-center text-2"> <span class=" text-uppercase">{{payment.type}}</span> </div>
<div class="col-3 col-sm-3 text-right text-3"> <span class="text-nowrap">{% if payment.type == 'withdraw' %}- {%else%}+ {%endif%}{{payment.amount}}</span> <span class="text-1 text-uppercase">({{payment.currency}})</span> </div>
@ -121,4 +121,7 @@
{% endblock %}
{% block js_extra %}
{% compress js %}
<script type="text/javascript" src="{% static 'matrixhosting/js/table.js' %}"></script>
{% endcompress %}
{% endblock js_extra %}

View File

@ -11,7 +11,7 @@ urlpatterns = [
path('order/confirm/', OrderDetailsView.as_view(), name='order_confirmation'),
path('order/success/', OrderSuccessView.as_view(), name='order_success'),
path('order/invoice/download', InvoiceDownloadView.as_view(), name='invoice_download'),
path('payments/', PaymentsView.as_view(), name='payments'),
path('dashboard/', Dashboard.as_view(), name='dashboard'),
path('billing/', PaymentsView.as_view(), name='billing'),
path('orders/', OrdersView.as_view(), name='orders'),
path('', IndexView.as_view(), name='index'),
]

View File

@ -0,0 +1,20 @@
import json
from uncloud_pay.models import Product, Order, ProductToRecurringPeriod
def finalize_order(request, customer, billing_address,
one_time_price, pricing_plan,
specs):
product = Product.objects.first()
recurring_period_product = ProductToRecurringPeriod.objects.filter(product=product, is_default=True).first()
order = Order.objects.create(
owner=request.user,
customer=customer,
billing_address=billing_address,
one_time_price=one_time_price,
pricing_plan=pricing_plan,
recurring_period= recurring_period_product.recurring_period,
product = product,
config=json.dumps(specs)
)
return order

View File

@ -1,7 +1,7 @@
import logging
import json
import decimal
from stripe.error import CardError
from django.shortcuts import redirect, render
from django.contrib import messages
from django.utils.translation import get_language, ugettext_lazy as _
@ -11,7 +11,7 @@ from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import FormView, DetailView
from django.views.generic.list import ListView
from matrixhosting.forms import InitialRequestForm, RequestHostedVMForm, BillingAddressForm
from matrixhosting.forms import InitialRequestForm, BillingAddressForm, RequestDomainsNamesForm
from django.urls import reverse
from django.conf import settings
from django.http import (
@ -79,11 +79,13 @@ class OrderPaymentView(FormView):
) if old_active else BillingAddressForm(
initial={'active': True, 'owner': self.request.user.id}
)
details_form = RequestHostedVMForm(
details_form = InitialRequestForm(
initial=self.request.session.get('order', {})
)
balance = get_balance_for_user(self.request.user)
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
#TODO optimize this part for better performance
uncloud_stripe.sync_cards_for_user(self.request.user)
cards = uncloud_stripe.get_customer_cards(customer_id)
context.update({
'matrix_vm_pricing': PricingPlan.get_by_name(self.request.session.get('pricing', {'name': 'unknown'})['name']),
@ -94,7 +96,6 @@ class OrderPaymentView(FormView):
'stripe_key': settings.STRIPE_PUBLIC_KEY,
'show_cards': True if balance < self.request.session.get('pricing')['total'] else False,
})
return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
@ -107,7 +108,7 @@ class OrderPaymentView(FormView):
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
details_form = RequestHostedVMForm(request.POST)
details_form = InitialRequestForm(request.POST)
billing_address_form = BillingAddressForm(request.POST)
if not details_form.is_valid() or not billing_address_form.is_valid():
context = self.get_context_data()
@ -142,10 +143,10 @@ class OrderPaymentView(FormView):
if 'error' in validate_result and validate_result['error']:
messages.add_message(
self.request, messages.ERROR, validate_result["error"],
extra_tags='vat_error'
extra_tags='error'
)
return HttpResponseRedirect(
reverse('matrix:payment') + '#vat_error'
reverse('matrix:payment')
)
self.request.session["vat_validation_status"] = validate_result["status"]
specs = details_form.cleaned_data
@ -157,7 +158,16 @@ class OrderPaymentView(FormView):
)
amount = get_balance_for_user(self.request.user) - decimal.Decimal(pricing["total"])
if (amount < 0):
payment_id = Payment.deposit(request.user, abs(amount), source='stripe')
try:
payment_id = Payment.deposit(request.user, abs(amount), source='stripe')
except CardError as e:
messages.add_message(
self.request, messages.ERROR, e.user_message,
extra_tags='error'
)
return HttpResponseRedirect(
reverse('matrix:payment')
)
self.request.session['pricing'] = pricing
self.request.session['order'] = specs
self.request.session['vat_validation_status'] = vat_validation_status
@ -172,64 +182,50 @@ class OrderDetailsView(DetailView):
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
def get_context_data(self, **kwargs):
context = {
'order': self.request.session.get('order'),
'pricing': self.request.session.get('pricing'),
'balance': get_balance_for_user(self.request.user)
}
return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
context = self.get_context_data()
context['domains_form'] = RequestDomainsNamesForm(initial={})
if ('order' not in request.session):
return HttpResponseRedirect(reverse('matrix:index'))
elif 'pricing' not in self.request.session or 'vat_validation_status' not in self.request.session:
return HttpResponseRedirect(reverse('matrix:payment'))
return HttpResponseRedirect(reverse('matrix:payment'))
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
customer = StripeCustomer.objects.get(owner=self.request.user)
billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
total = self.request.session['pricing']['total']
order = finalize_order(request, customer,
billing_address,
total,
PricingPlan.get_by_name(self.request.session['pricing']['name']),
request.session.get('order'))
if order:
bill = Bill.create_next_bill_for_user_address(billing_address)
self.request.session['bill_id'] = bill.id
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
if payment:
#Close the bill as the payment has been added
bill.close()
response = {
'status': True,
'redirect': (reverse('matrix:dashboard')),
'msg_title': str(_('Thank you for the order.')),
'msg_body': str(
_('Your VM will be up and running in a few moments.'
' We will send you a confirmation email as soon as'
' it is ready.'))
}
return JsonResponse(response)
def finalize_order(request, customer, billing_address,
one_time_price, pricing_plan,
specs):
product = Product.objects.first()
recurring_period_product = ProductToRecurringPeriod.objects.filter(product=product, is_default=True).first()
order = Order.objects.create(
owner=request.user,
customer=customer,
billing_address=billing_address,
one_time_price=one_time_price,
pricing_plan=pricing_plan,
recurring_period= recurring_period_product.recurring_period,
product = product,
config=json.dumps(specs)
)
return order
domains_form = RequestDomainsNamesForm(self.request.POST)
if domains_form.is_valid():
customer = StripeCustomer.objects.get(owner=self.request.user)
billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
total = self.request.session['pricing']['total']
self.request.session['order']['matrix_domain'] = 'ungleich.ch'
self.request.session['order']['homeserver_domain'] = domains_form.cleaned_data.get('homeserver_name') + ".matrix.ungleich.cloud"
self.request.session['order']['webclient_domain'] = domains_form.cleaned_data.get('webclient_name') + ".matrix.0co2.cloud"
self.request.session['order']['is_open_registration'] = domains_form.cleaned_data.get('is_open_registration')
order = finalize_order(request, customer,
billing_address,
total,
PricingPlan.get_by_name(self.request.session['pricing']['name']),
request.session.get('order'))
if order:
bill = Bill.create_next_bill_for_order(order)
self.request.session['bill_id'] = bill.id
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
if payment:
#Close the bill as the payment has been added
bill.close()
return HttpResponseRedirect(reverse('matrix:order_success'))
context = self.get_context_data()
context['domains_form'] = domains_form
return self.render_to_response(context)
class OrderSuccessView(DetailView):
template_name = "matrixhosting/order_success.html"
@ -263,27 +259,22 @@ class InvoiceDownloadView(View):
bill = Bill.objects.get(id=self.request.session.get('bill_id'))
if bill:
context['bill'] = bill
context['vat_rate'] = str(round(bill.vat_rate * 100, 2)) + '%'
context['vat_rate'] = str(round(bill.vat_rate * 100, 2))
context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
return context
def get(self, request):
cmd_options = {
'page_height': 240,
'page_width':175,
'orientation': 'Portrait',
'header_spacing': 65,
'header_line': False
}
cmd_options = settings.REPORT_FORMAT
return PDFTemplateResponse(request=request,
template=self.template,
filename = self.filename,
cmd_options= cmd_options,
footer_template= 'matrixhosting/includes/invoice_footer.html',
context= self.get_context_data())
class Dashboard(ListView):
template_name = "matrixhosting/dashboard.html"
class OrdersView(ListView):
template_name = "matrixhosting/orders.html"
model = Order
@method_decorator(login_required)
@ -318,10 +309,13 @@ class PaymentsView(ListView):
def get_context_data(self, **kwargs):
context = super(PaymentsView, self).get_context_data(**kwargs)
context.update({
'balance': get_balance_for_user(self.request.user)
'balance': get_balance_for_user(self.request.user),
'type': self.request.GET.get('type')
})
return context
def get_queryset(self):
if self.request.GET.get('type'):
return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type'))
return Payment.objects.filter(owner=self.request.user)

View File

@ -1,15 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ name }}-matrix
spec:
selector:
matchLabels:
app: {{ name }}-matrix
replicas: 1
template:
metadata:
labels:
app: {{ name }}-matrix
use-as-service: {{ name }}

View File

@ -11,6 +11,8 @@ EMAIL_HOST=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
GITLAB_SERVER=
GITLAB_YAML_DIR=
GITLAB_PROJECT_ID=
GITLAB_OAUTH_TOKEN=
GITLAB_AUTHOR_EMAIL=
GITLAB_AUTHOR_NAME=

View File

@ -202,10 +202,10 @@ COMPRESS_ENABLED = True
#VM Deployment TEMPLATE
GITLAB_SERVER = env('GITLAB_SERVER')
GITLAB_OAUTH_TOKEN = env('GITLAB_OAUTH_TOKEN')
GITLAB_PROJECT_ID = 388
GITLAB_PROJECT_ID = env('GITLAB_PROJECT_ID')
GITLAB_AUTHOR_EMAIL = env('GITLAB_AUTHOR_EMAIL')
GITLAB_AUTHOR_NAME = env('GITLAB_AUTHOR_NAME')
GITLAB_YAML_DIR = ''
GITLAB_YAML_DIR = env('GITLAB_YAML_DIR')
# XML-RPC interface of opennebula
OPENNEBULA_URL = 'https://opennebula.example.com:2634/RPC2'
@ -270,6 +270,15 @@ Q_CLUSTER = {
'db': 0, }
}
REPORT_FORMAT = {
'page_height': 200,
'page_width':175,
'orientation': 'Portrait',
'header_spacing': 65,
'margin_bottom':25,
'header_line': False,
}
# Overwrite settings with local settings, if existing
try:
from uncloud.local_settings import *

View File

@ -50,7 +50,7 @@ class BillAdmin(admin.ModelAdmin):
"uncloud_pay/bill.html.j2",
{
'bill': bill,
'bill_records': bill.billrecord_set.all()
'bill_records': bill.bill_records.all()
}
)
@ -68,12 +68,12 @@ class BillAdmin(admin.ModelAdmin):
return render(request, 'uncloud_pay/bill.html.j2',
{'bill': bill,
'bill_records': bill.billrecord_set.all()
'bill_records': bill.bill_records.all()
})
bill_html = render_to_string("bill.html.j2", {'bill': bill,
'bill_records': bill.billrecord_set.all()
'bill_records': bill.bill_records.all()
})
bytestring_to_pdf(bill_html.encode('utf-8'), output_file)

View File

@ -122,7 +122,7 @@ class Payment(models.Model):
@classmethod
def withdraw(cls, owner, amount, currency='CHF', notes=''):
cls.objects.create(owner=owner, type="withdraw", amount=amount,
return cls.objects.create(owner=owner, type="withdraw", amount=amount,
currency=currency, notes=notes)
@ -753,7 +753,7 @@ class Order(models.Model):
self.ending_date = timezone.now()
self.should_be_billed = False
self.save()
if self.instance_id:
if hasattr(self, 'instance_id'):
last_bill_record = BillRecord.objects.filter(order=self).order_by('id').last()
schedule('matrixhosting.tasks.delete_instance',
self.instance_id,
@ -790,13 +790,6 @@ class Order(models.Model):
else:
return False
@property
def is_closed(self):
if self.all_usage_billed and self.ending_date:
return True
else:
return False
@property
def is_recurring(self):
return self.recurring_price > 0
@ -833,7 +826,6 @@ class Order(models.Model):
new_order = self.__class__(owner=self.owner,
billing_address=self.billing_address,
description=self.description,
product=self.product,
config=config,
pricing_plan=self.pricing_plan,
@ -934,7 +926,7 @@ class Order(models.Model):
vat_rate = VATRate.get_vat_rate(self.billing_address)
vat_validation_status = "verified" if self.billing_address.vat_number_validated_on and self.billing_address.vat_number_verified else False
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = uncloud_pay.utils.apply_vat_discount(
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, vat_amount, discount = uncloud_pay.utils.apply_vat_discount(
recurring_price, self.pricing_plan,
vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
)
@ -1077,6 +1069,17 @@ class Bill(models.Model):
# This Customer Hasn't any active orders
return False
@classmethod
def create_next_bill_for_order(cls, order, ending_date=None):
"""
Create the next bill for a specific order of a user
"""
bill = cls.get_or_create_bill(order.billing_address, ending_date=ending_date)
print("??????????????????????????????")
print(bill.id)
order.create_bill_record(bill)
return bill
@classmethod
def get_or_create_bill(cls, billing_address, ending_date=None):
@ -1110,7 +1113,7 @@ class Bill(models.Model):
if not ending_date:
ending_date = end_of_month(starting_date)
if not bill:
bill = cls.objects.create(
owner=billing_address.owner,
@ -1192,6 +1195,9 @@ class BillRecord(models.Model):
return bill_line
def save(self, *args, **kwargs):
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
print(self.ending_date)
print(self.starting_date)
if self.ending_date < self.starting_date:
raise ValidationError("End date cannot be before starting date")

View File

@ -151,7 +151,7 @@ class OrderTestCase(TestCase):
order_config = json.dumps({
'cores': 2,
'memory':4,
'storage': 200
'storage': 200,
})
order1 = Order.objects.create(owner=self.user,
billing_address=self.ba,
@ -470,7 +470,7 @@ class BillTestCase(TestCase):
bill = Bill.create_next_bill_for_user_address(self.recurring_user_addr)
self.assertEqual(order.billrecord_set.count(), 1)
self.assertEqual(bill.billrecord_set.count(), 1)
self.assertEqual(bill.bill_records.count(), 1)
def test_new_bill_after_closing(self):

View File

@ -143,17 +143,15 @@ def get_order_total_with_vat(cores, memory, storage,
)
)
return None
subtotal = (
pricing.set_up_fees +
(decimal.Decimal(cores) * pricing.cores_unit_price) +
(decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(storage) * (pricing.storage_unit_price))
)
recurring_price = (decimal.Decimal(cores) * pricing.cores_unit_price) + \
(decimal.Decimal(memory) * pricing.ram_unit_price) + \
(decimal.Decimal(storage) * (pricing.storage_unit_price))
subtotal = pricing.set_up_fees + recurring_price
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, vat_amount, discount = \
apply_vat_discount(subtotal, pricing, vat_rate, vat_validation_status)
return {
"name": pricing.name,
'recurring_price': round(float(recurring_price), 2),
"subtotal": subtotal,
"discount": discount,
"vat": vat, "vat_percent": vat_percent,