Merge remote-tracking branch 'mainRepo/master' into feature/6561/invoices-webhook
This commit is contained in:
commit
4147b8f891
40 changed files with 1094 additions and 142 deletions
|
|
@ -42,9 +42,18 @@ class Command(BaseCommand):
|
|||
exit(1)
|
||||
all_invoices = all_invoices_response['response_object']
|
||||
self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0)))
|
||||
num_invoice_created = 0
|
||||
for invoice in all_invoices:
|
||||
invoice['customer'] = user.stripecustomer
|
||||
MonthlyHostingBill.create(invoice)
|
||||
try:
|
||||
existing_mhb = MonthlyHostingBill.objects.get(invoice_id=invoice['invoice_id'])
|
||||
logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
|
||||
except MonthlyHostingBill.DoesNotExist as dne:
|
||||
logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
|
||||
num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
'Customer email %s does not have a stripe customer.' % email))
|
||||
|
|
|
|||
22
hosting/migrations/0053_hostingbilllineitem_stripe_plan.py
Normal file
22
hosting/migrations/0053_hostingbilllineitem_stripe_plan.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-04-20 10:10
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('datacenterlight', '0028_stripeplan_stripe_plan_name'),
|
||||
('hosting', '0052_hostingbilllineitem'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='hostingbilllineitem',
|
||||
name='stripe_plan',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='datacenterlight.StripePlan'),
|
||||
),
|
||||
]
|
||||
20
hosting/migrations/0054_auto_20190508_2141.py
Normal file
20
hosting/migrations/0054_auto_20190508_2141.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-05-08 21:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0053_hostingbilllineitem_stripe_plan'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vmdetail',
|
||||
name='configuration',
|
||||
field=models.CharField(default='', max_length=128),
|
||||
),
|
||||
]
|
||||
|
|
@ -10,7 +10,7 @@ from django.db import models
|
|||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from datacenterlight.models import VMPricing, VMTemplate
|
||||
from datacenterlight.models import VMPricing, VMTemplate, StripePlan
|
||||
from membership.models import StripeCustomer, CustomUser
|
||||
from utils.mixins import AssignPermissionsMixin
|
||||
from utils.models import BillingAddress
|
||||
|
|
@ -212,6 +212,15 @@ class UserHostingKey(models.Model):
|
|||
# self.save(update_fields=['public_key'])
|
||||
return private_key, public_key
|
||||
|
||||
def delete(self,*args,**kwargs):
|
||||
if bool(self.private_key) and os.path.isfile(self.private_key.path):
|
||||
logger.debug("Removing private key {}".format(self.private_key.path))
|
||||
os.remove(self.private_key.path)
|
||||
else:
|
||||
logger.debug("No private_key to remove")
|
||||
|
||||
super(UserHostingKey, self).delete(*args,**kwargs)
|
||||
|
||||
|
||||
class HostingBill(AssignPermissionsMixin, models.Model):
|
||||
customer = models.ForeignKey(StripeCustomer)
|
||||
|
|
@ -276,14 +285,17 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
# Try to infer the HostingOrder from subscription id or VM_ID
|
||||
if len(args['subscription_ids_csv']) > 0:
|
||||
sub_ids = [sub_id.strip() for sub_id in args['subscription_ids_csv'].split(",")]
|
||||
if len(sub_ids) == 1:
|
||||
set_sub_ids = set(sub_ids)
|
||||
if len(set_sub_ids) == 1:
|
||||
# the multiple line items belong to the same subscription
|
||||
sub_id = set_sub_ids.pop()
|
||||
try:
|
||||
args['order'] = HostingOrder.objects.get(
|
||||
subscription_id=sub_ids[0]
|
||||
subscription_id=sub_id
|
||||
)
|
||||
except HostingOrder.DoesNotExist as dne:
|
||||
logger.error("Hosting order for {} doesn't exist".format(
|
||||
sub_ids[0]
|
||||
sub_id
|
||||
))
|
||||
args['order'] = None
|
||||
else:
|
||||
|
|
@ -293,7 +305,7 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
)
|
||||
logger.debug("SUB_IDS={}".format(','.join(sub_ids)))
|
||||
logger.debug("Not importing invoices")
|
||||
return
|
||||
return None
|
||||
elif len(args['lines_meta_data_csv']) > 0:
|
||||
vm_ids = [vm_id.strip() for vm_id in args['lines_meta_data_csv'].split(",")]
|
||||
if len(vm_ids) == 1:
|
||||
|
|
@ -305,11 +317,11 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
)
|
||||
logger.debug("VM_IDS={}".format(','.join(vm_ids)))
|
||||
logger.debug("Not importing invoices")
|
||||
return
|
||||
return None
|
||||
else:
|
||||
logger.debug("Neither subscription id nor vm_id available")
|
||||
logger.debug("Can't import invoice")
|
||||
return
|
||||
return None
|
||||
|
||||
instance = cls.objects.create(
|
||||
created=datetime.utcfromtimestamp(
|
||||
|
|
@ -342,6 +354,27 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
if 'line_items' in args:
|
||||
line_items = args['line_items']
|
||||
for item in line_items:
|
||||
stripe_plan = None
|
||||
if item.type == "subscription" or item.type == "invoiceitem":
|
||||
# Check stripe plan and prepare it for linking to bill item
|
||||
stripe_plan_id = item.plan.id
|
||||
try:
|
||||
stripe_plan = StripePlan.objects.get(
|
||||
stripe_plan_id=stripe_plan_id
|
||||
)
|
||||
except StripePlan.DoesNotExist as dne:
|
||||
logger.error(
|
||||
"StripePlan %s doesn't exist" % stripe_plan_id
|
||||
)
|
||||
if stripe_plan_id is not None:
|
||||
# Create Stripe Plan because we don't have it
|
||||
stripe_plan = StripePlan.objects.create(
|
||||
stripe_plan_id=stripe_plan_id,
|
||||
stripe_plan_name=item.plan.name,
|
||||
amount=item.plan.amount,
|
||||
interval=item.plan.interval
|
||||
)
|
||||
logger.debug("Creatd StripePlan " + stripe_plan_id)
|
||||
line_item_instance = HostingBillLineItem.objects.create(
|
||||
monthly_hosting_bill=instance,
|
||||
amount=item.amount,
|
||||
|
|
@ -358,7 +391,8 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
# https://stripe.com/docs/api/invoiceitems/object#invoiceitem_object-unit_amount
|
||||
# So, for the time being I set the unit_amount to 0 if not
|
||||
# found in the line item
|
||||
unit_amount=item.unit_amount if hasattr(item, "unit_amount") else 0
|
||||
unit_amount=item.unit_amount if hasattr(item, "unit_amount") else 0,
|
||||
stripe_plan=stripe_plan
|
||||
)
|
||||
line_item_instance.assign_permissions(instance.customer.user)
|
||||
instance.assign_permissions(instance.customer.user)
|
||||
|
|
@ -390,14 +424,18 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
if len(self.lines_meta_data_csv) > 0:
|
||||
vm_ids = [vm_id.strip() for vm_id in
|
||||
self.lines_meta_data_csv.split(",")]
|
||||
if len(vm_ids) == 1:
|
||||
return vm_ids[0]
|
||||
unique_vm_ids=set(vm_ids)
|
||||
unique_vm_ids.discard("")
|
||||
if len(unique_vm_ids) == 1:
|
||||
vm_id = unique_vm_ids.pop()
|
||||
logger.debug("Getting invoice for {}".format(vm_id))
|
||||
return vm_id
|
||||
else:
|
||||
logger.debug(
|
||||
"More than one VM_ID"
|
||||
"for MonthlyHostingBill {}".format(self.invoice_id)
|
||||
)
|
||||
logger.debug("VM_IDS=".format(','.join(vm_ids)))
|
||||
logger.debug("VM_IDS={}".format(unique_vm_ids))
|
||||
return return_value
|
||||
|
||||
def get_period_start(self):
|
||||
|
|
@ -427,7 +465,10 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model):
|
|||
"""
|
||||
Corresponds to InvoiceItem object of Stripe
|
||||
"""
|
||||
monthly_hosting_bill = models.ForeignKey(MonthlyHostingBill)
|
||||
monthly_hosting_bill = models.ForeignKey(MonthlyHostingBill,
|
||||
on_delete=models.CASCADE)
|
||||
stripe_plan = models.ForeignKey(StripePlan, null=True,
|
||||
on_delete=models.CASCADE)
|
||||
amount = models.PositiveSmallIntegerField()
|
||||
description = models.CharField(max_length=255)
|
||||
discountable = models.BooleanField()
|
||||
|
|
@ -444,6 +485,55 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model):
|
|||
('view_hostingbilllineitem', 'View Monthly Hosting Bill Line Item'),
|
||||
)
|
||||
|
||||
def amount_in_chf(self):
|
||||
"""
|
||||
Returns amount in chf. The amount in this model is in cents (as in
|
||||
Stripe). Hence we multiply it by 0.01 to obtain the result
|
||||
|
||||
:return:
|
||||
"""
|
||||
return self.amount * 0.01
|
||||
|
||||
def unit_amount_in_chf(self):
|
||||
"""
|
||||
Returns unit amount in chf. If its 0, we obtain it from amount and
|
||||
quantity.
|
||||
|
||||
:return:
|
||||
"""
|
||||
if self.unit_amount == 0:
|
||||
return round((self.amount / self.quantity) * 0.01, 2)
|
||||
else:
|
||||
return self.unit_amount * 0.01
|
||||
|
||||
def get_item_detail_str(self):
|
||||
"""
|
||||
Returns line item html string representation
|
||||
:return:
|
||||
"""
|
||||
item_detail = ""
|
||||
# metadata is a dict; a dict with nothing has two chars at least {}
|
||||
if self.metadata is not None and len(self.metadata) > 2:
|
||||
try:
|
||||
vm_dict = json.loads(self.metadata)
|
||||
item_detail = "VM ID: {}<br/>".format(vm_dict["VM_ID"])
|
||||
except ValueError as ve:
|
||||
logger.error(
|
||||
"Could not parse VM in metadata {}. Detail {}".format(
|
||||
self.metadata, str(ve)
|
||||
)
|
||||
)
|
||||
vm_conf = StripeUtils.get_vm_config_from_stripe_id(
|
||||
self.stripe_plan.stripe_plan_id
|
||||
)
|
||||
if vm_conf is not None:
|
||||
item_detail += ("<b>Cores</b>: {}<br/><b>RAM</b>: {} GB<br/>"
|
||||
"<b>SSD</b>: {} GB<br/>").format(
|
||||
vm_conf['cores'], int(float(vm_conf['ram'])),
|
||||
vm_conf['ssd']
|
||||
)
|
||||
return item_detail
|
||||
|
||||
def get_vm_id(self):
|
||||
"""
|
||||
If VM_ID is set in the metadata extract and return it as integer
|
||||
|
|
@ -464,7 +554,7 @@ class VMDetail(models.Model):
|
|||
disk_size = models.FloatField(default=0.0)
|
||||
cores = models.FloatField(default=0.0)
|
||||
memory = models.FloatField(default=0.0)
|
||||
configuration = models.CharField(default='', max_length=25)
|
||||
configuration = models.CharField(default='', max_length=128)
|
||||
ipv4 = models.TextField(default='')
|
||||
ipv6 = models.TextField(default='')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
|
|
|||
|
|
@ -112,4 +112,17 @@
|
|||
|
||||
.dcl-place-order-text {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
tr.border_bottom td {
|
||||
border-bottom:1pt solid #eee;
|
||||
}
|
||||
|
||||
tr.grand-total-padding td {
|
||||
padding-top: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -89,80 +89,91 @@
|
|||
<div>
|
||||
<h4>{% trans "Invoice summary" %}</h4>
|
||||
{% if vm %}
|
||||
<p>
|
||||
<strong>{% trans "Product" %}:</strong>
|
||||
{% if vm.name %}
|
||||
{{ vm.name }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
{% if period_start %}
|
||||
{% if line_items %}
|
||||
<table>
|
||||
<tr><th style="width: 35%">Product</th><th style="width: 20%">Period</th><th style="text-align: center; width: 10%">Qty</th><th align="center" style="width: 10%; text-align: center;">Unit Price</th><th style="width: 10%; text-align: right;">Total</th></tr>
|
||||
{% for line_item in line_items %}
|
||||
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf}}</td></tr>
|
||||
|
||||
{% endfor %}
|
||||
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf}}</td></tr>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>
|
||||
<span>{% trans "Period" %}: </span>
|
||||
<span>
|
||||
<span class="locale_date"
|
||||
data-format="YYYY/MM/DD">{{ period_start|date:'Y-m-d h:i a' }}</span> - <span
|
||||
class="locale_date" data-format="YYYY/MM/DD">{{ period_end|date:'Y-m-d h:i a' }}</span>
|
||||
</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<span>{% trans "Cores" %}: </span>
|
||||
{% if vm.cores %}
|
||||
<strong class="pull-right">{{vm.cores|floatformat}}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{vm.cpu|floatformat}}</strong>
|
||||
<strong>{% trans "Product" %}:</strong>
|
||||
{% if vm.name %}
|
||||
{{ vm.name }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Memory" %}: </span>
|
||||
<strong class="pull-right">{{vm.memory}} GB</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Disk space" %}: </span>
|
||||
<strong class="pull-right">{{vm.disk_size}} GB</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% if vm.vat > 0 or vm.discount.amount > 0 %}
|
||||
<div class="col-sm-6">
|
||||
<div class="subtotal-price">
|
||||
{% if vm.vat > 0 %}
|
||||
<p>
|
||||
<strong>{% trans "Subtotal" %} </strong>
|
||||
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}}
|
||||
CHF</strong>
|
||||
</p>
|
||||
<p>
|
||||
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%)
|
||||
</small>
|
||||
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if vm.discount.amount > 0 %}
|
||||
<p class="text-primary">
|
||||
{%trans "Discount" as discount_name %}
|
||||
<strong>{{ vm.discount.name|default:discount_name }} </strong>
|
||||
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
{% if period_start %}
|
||||
<p>
|
||||
<span>{% trans "Period" %}: </span>
|
||||
<span>
|
||||
<span class="locale_date"
|
||||
data-format="YYYY/MM/DD">{{ period_start|date:'Y-m-d h:i a' }}</span> - <span
|
||||
class="locale_date" data-format="YYYY/MM/DD">{{ period_end|date:'Y-m-d h:i a' }}</span>
|
||||
</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<span>{% trans "Cores" %}: </span>
|
||||
{% if vm.cores %}
|
||||
<strong class="pull-right">{{vm.cores|floatformat}}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{vm.cpu|floatformat}}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Memory" %}: </span>
|
||||
<strong class="pull-right">{{vm.memory}} GB</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Disk space" %}: </span>
|
||||
<strong class="pull-right">{{vm.disk_size}} GB</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% if vm.vat > 0 or vm.discount.amount > 0 %}
|
||||
<div class="col-sm-6">
|
||||
<div class="subtotal-price">
|
||||
{% if vm.vat > 0 %}
|
||||
<p>
|
||||
<strong>{% trans "Subtotal" %} </strong>
|
||||
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}}
|
||||
CHF</strong>
|
||||
</p>
|
||||
<p>
|
||||
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%)
|
||||
</small>
|
||||
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if vm.discount.amount > 0 %}
|
||||
<p class="text-primary">
|
||||
{%trans "Discount" as discount_name %}
|
||||
<strong>{{ vm.discount.name|default:discount_name }} </strong>
|
||||
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-6">
|
||||
<p class="total-price">
|
||||
<strong>{% trans "Total" %} </strong>
|
||||
<strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
|
||||
CHF</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-6">
|
||||
<p class="total-price">
|
||||
<strong>{% trans "Total" %} </strong>
|
||||
<strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
|
||||
CHF</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
<strong>{% trans "Product" %}:</strong>
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@
|
|||
<tr>
|
||||
<td class="xs-td-inline" data-header="{% trans 'VM ID' %}">{{ invoice.order.vm_id }}</td>
|
||||
<td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
|
||||
{% with line_items|get_value_from_dict:invoice.invoice_number as line_items_to_show %}
|
||||
<td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ line_items_to_show.0.period_start | date:'Y-m-d' }} — {{ line_items_to_show.0.period_end | date:'Y-m-d' }}</td>
|
||||
{% with period|get_value_from_dict:invoice.invoice_number as period_to_show %}
|
||||
<td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ period_to_show.period_start | date:'Y-m-d' }} — {{ period_to_show.period_end | date:'Y-m-d' }}</td>
|
||||
{% endwith %}
|
||||
<td class="xs-td-inline" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
|
||||
<td class="text-right last-td">
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ from .views import (
|
|||
HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
|
||||
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
|
||||
SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView,
|
||||
InvoiceListView, InvoiceDetailView
|
||||
InvoiceListView, InvoiceDetailView, CheckUserVM
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'index/?$', IndexView.as_view(), name='index'),
|
||||
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
|
||||
url(r'checkvm/?$', CheckUserVM.as_view(), name='check_vm'),
|
||||
url(r'dashboard/?$', DashboardView.as_view(), name='dashboard'),
|
||||
url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'),
|
||||
url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'),
|
||||
|
|
|
|||
|
|
@ -28,13 +28,16 @@ from django.views.generic import (
|
|||
)
|
||||
from guardian.mixins import PermissionRequiredMixin
|
||||
from oca.pool import WrongIdError
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from stored_messages.api import mark_read
|
||||
from stored_messages.models import Message
|
||||
from stored_messages.settings import stored_messages_settings
|
||||
|
||||
from datacenterlight.cms_models import DCLCalculatorPluginModel
|
||||
from datacenterlight.models import VMTemplate, VMPricing
|
||||
from datacenterlight.utils import create_vm, get_cms_integration
|
||||
from datacenterlight.utils import create_vm, get_cms_integration, check_otp
|
||||
from hosting.models import UserCardDetail
|
||||
from membership.models import CustomUser, StripeCustomer
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
|
|
@ -66,9 +69,12 @@ from .models import (
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
|
||||
backend connection error. please try again in a few \
|
||||
minutes."
|
||||
|
||||
|
||||
decorators = [never_cache]
|
||||
|
||||
|
||||
|
|
@ -1185,19 +1191,26 @@ class InvoiceListView(LoginRequiredMixin, ListView):
|
|||
customer__user=self.request.user
|
||||
)
|
||||
ips_dict = {}
|
||||
line_items_dict = {}
|
||||
line_item_period_dict = {}
|
||||
for mhb in mhbs:
|
||||
try:
|
||||
vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id)
|
||||
ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
|
||||
line_items_dict[mhb.invoice_number] = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb)
|
||||
all_line_items = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb)
|
||||
for line_item in all_line_items:
|
||||
if line_item.get_item_detail_str() != "":
|
||||
line_item_period_dict[mhb.invoice_number] = {
|
||||
"period_start": line_item.period_start,
|
||||
"period_end": line_item.period_end
|
||||
}
|
||||
break
|
||||
except VMDetail.DoesNotExist as dne:
|
||||
ips_dict[mhb.invoice_number] = ['--']
|
||||
logger.debug("VMDetail for {} doesn't exist".format(
|
||||
mhb.order.vm_id
|
||||
))
|
||||
context['line_items'] = line_items_dict
|
||||
context['ips'] = ips_dict
|
||||
context['period'] = line_item_period_dict
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
@ -1295,8 +1308,8 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|||
# fallback to get it from the infrastructure
|
||||
try:
|
||||
manager = OpenNebulaManager(
|
||||
email=self.request.email,
|
||||
password=self.request.password
|
||||
email=self.request.user.email,
|
||||
password=self.request.user.password
|
||||
)
|
||||
vm = manager.get_vm(vm_id)
|
||||
context['vm'] = VirtualMachineSerializer(vm).data
|
||||
|
|
@ -1335,6 +1348,9 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|||
context['total_in_chf'] = obj.total_in_chf()
|
||||
context['invoice_number'] = obj.invoice_number
|
||||
context['discount_on_stripe'] = obj.discount_in_chf()
|
||||
if obj.lines_data_count > 1:
|
||||
# special case, we pass the details of each of the line items
|
||||
context['line_items'] = obj.hostingbilllineitem_set.all()
|
||||
return context
|
||||
else:
|
||||
raise Http404
|
||||
|
|
@ -1771,3 +1787,39 @@ def forbidden_view(request, exception=None, reason=''):
|
|||
'again.')
|
||||
messages.add_message(request, messages.ERROR, err_msg)
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
|
||||
class CheckUserVM(APIView):
|
||||
renderer_classes = (JSONRenderer, )
|
||||
|
||||
def get(self, request):
|
||||
try:
|
||||
email = request.data['email']
|
||||
ip = request.data['ip']
|
||||
user = request.data['user']
|
||||
realm = request.data['realm']
|
||||
token = request.data['token']
|
||||
if realm != settings.READ_VM_REALM:
|
||||
return Response("User not allowed", 403)
|
||||
response = check_otp(user, realm, token)
|
||||
if response != 200:
|
||||
return Response('Invalid token', 403)
|
||||
manager = OpenNebulaManager()
|
||||
# not the best way to lookup vms by ip
|
||||
# TODO: make this optimal
|
||||
vms = manager.get_vms()
|
||||
users_vms = [vm for vm in vms if vm.uname == email]
|
||||
if len(users_vms) == 0:
|
||||
return Response('No VM found with the given email address',
|
||||
404)
|
||||
for vm in users_vms:
|
||||
for nic in vm.template.nics:
|
||||
if hasattr(nic, 'ip6_global'):
|
||||
if nic.ip6_global == ip:
|
||||
return Response('success', 200)
|
||||
elif hasattr(nic, 'ip'):
|
||||
if nic.ip == ip:
|
||||
return Response('success', 200)
|
||||
return Response('No VM found matching the ip address provided', 404)
|
||||
except KeyError:
|
||||
return Response('Not enough data provided', 400)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue