dynamicweb/hosting/models.py

499 lines
18 KiB
Python
Raw Normal View History

2017-05-12 17:13:18 +00:00
import logging
import os
import pytz
2017-05-12 17:13:18 +00:00
from Crypto.PublicKey import RSA
from dateutil.relativedelta import relativedelta
from datetime import datetime
from django.db import models
2017-09-30 12:35:02 +00:00
from django.utils import timezone
from django.utils.functional import cached_property
from datacenterlight.models import VMPricing, VMTemplate
from membership.models import StripeCustomer, CustomUser
2017-08-20 19:11:46 +00:00
from utils.mixins import AssignPermissionsMixin
from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils
logger = logging.getLogger(__name__)
2017-05-13 11:47:53 +00:00
class HostingPlan(models.Model):
disk_size = models.FloatField(default=0.0)
cpu_cores = models.FloatField(default=0.0)
memory = models.FloatField(default=0.0)
def serialize(self):
return {
'id': self.id,
'cpu': self.cpu_cores,
2017-05-13 11:47:53 +00:00
'memory': self.memory,
'disk_size': self.disk_size,
'price': self.price(),
}
@classmethod
def get_serialized_configs(cls):
return [cfg.serialize()
for cfg in cls.objects.all()]
def price(self):
2017-05-14 00:17:48 +00:00
price = self.disk_size * 0.6
2017-05-13 11:47:53 +00:00
price += self.cpu_cores * 5
price += self.memory * 2
return price
class OrderDetail(AssignPermissionsMixin, models.Model):
2018-07-01 17:23:05 +00:00
vm_template = models.ForeignKey(
VMTemplate, blank=True, null=True, default=None,
on_delete=models.SET_NULL
)
cores = models.IntegerField(default=0)
memory = models.IntegerField(default=0)
hdd_size = models.IntegerField(default=0)
ssd_size = models.IntegerField(default=0)
def __str__(self):
2018-08-23 07:06:31 +00:00
return "Not available" if self.vm_template is None else (
"%s - %s, %s cores, %s GB RAM, %s GB SSD" % (
self.vm_template.name, self.vm_template.vm_type, self.cores,
self.memory, self.ssd_size
)
)
class GenericProduct(AssignPermissionsMixin, models.Model):
permissions = ('view_genericproduct',)
product_name = models.CharField(max_length=128, default="")
product_slug = models.SlugField(
2018-10-02 08:00:59 +00:00
unique=True,
help_text=(
'An mandatory unique slug for the product'
)
)
product_description = models.CharField(max_length=500, default="")
created_at = models.DateTimeField(auto_now_add=True)
product_price = models.DecimalField(max_digits=6, decimal_places=2)
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
product_is_subscription = models.BooleanField(default=True)
def __str__(self):
return self.product_name
def get_actual_price(self):
return round(
self.product_price + (self.product_price * self.product_vat), 2
)
class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined'
vm_id = models.IntegerField(default=0)
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
last4 = models.CharField(max_length=4)
2018-08-21 12:42:01 +00:00
cc_brand = models.CharField(max_length=128)
stripe_charge_id = models.CharField(max_length=100, null=True)
2017-05-12 17:13:18 +00:00
price = models.FloatField()
subscription_id = models.CharField(max_length=100, null=True)
2018-04-16 01:23:09 +00:00
vm_pricing = models.ForeignKey(VMPricing)
order_detail = models.ForeignKey(
OrderDetail, null=True, blank=True, default=None,
2018-07-01 17:23:05 +00:00
on_delete=models.SET_NULL
)
generic_product = models.ForeignKey(
GenericProduct, null=True, blank=True, default=None,
on_delete=models.SET_NULL
)
generic_payment_description = models.CharField(
max_length=500, null=True
)
permissions = ('view_hostingorder',)
class Meta:
permissions = (
('view_hostingorder', 'View Hosting Order'),
)
def __str__(self):
hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - "
"Specs: {} - Price: {}").format(
self.id, self.vm_id, self.customer.user.email, self.created_at,
self.order_detail, self.price
)
if self.generic_product_id is not None:
hosting_order_str += " - Generic Payment"
if self.stripe_charge_id is not None:
hosting_order_str += " - One time charge"
else:
hosting_order_str += " - Recurring"
return hosting_order_str
@cached_property
def status(self):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod
2018-04-21 16:57:43 +00:00
def create(cls, price=None, vm_id=0, customer=None,
billing_address=None, vm_pricing=None):
2017-05-12 17:13:18 +00:00
instance = cls.objects.create(
price=price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address,
vm_pricing=vm_pricing
2017-05-12 17:13:18 +00:00
)
instance.assign_permissions(customer.user)
return instance
def set_approved(self):
self.approved = True
self.save()
def set_stripe_charge(self, stripe_charge):
self.stripe_charge_id = stripe_charge.id
self.last4 = stripe_charge.source.last4
self.cc_brand = stripe_charge.source.brand
self.save()
def set_subscription_id(self, subscription_id, cc_details):
"""
When creating a Stripe subscription, we have subscription id.
We store this in the subscription_id field.
This method sets the subscription id
and the last4 and credit card brands used for this order.
:param subscription_id: Stripe's subscription id
:param cc_details: A dict containing card details
{last4, brand}
:return:
"""
self.subscription_id = subscription_id
self.last4 = cc_details.get('last4')
self.cc_brand = cc_details.get('brand')
self.save()
def get_cc_data(self):
return {
'last4': self.last4,
'cc_brand': self.cc_brand,
} if self.last4 and self.cc_brand else None
class UserHostingKey(models.Model):
user = models.ForeignKey(CustomUser)
public_key = models.TextField()
private_key = models.FileField(upload_to='private_keys', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
@staticmethod
def generate_RSA(bits=2048):
'''
Generate an RSA keypair with an exponent of 65537 in PEM format
param: bits The key length in bits
Return private key and public key
'''
new_key = RSA.generate(2048, os.urandom)
public_key = new_key.publickey().exportKey("OpenSSH")
private_key = new_key.exportKey("PEM")
return private_key, public_key
@classmethod
def generate_keys(cls):
private_key, public_key = cls.generate_RSA()
# self.public_key = public_key
# self.save(update_fields=['public_key'])
return private_key, public_key
2017-05-05 12:59:11 +00:00
class HostingBill(AssignPermissionsMixin, models.Model):
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
2017-05-06 12:44:08 +00:00
total_price = models.FloatField(default=0.0)
2017-05-05 12:59:11 +00:00
permissions = ('view_hostingbill',)
class Meta:
permissions = (
('view_hostingbill', 'View Hosting Bill'),
)
def __str__(self):
return "%s" % (self.customer.user.email)
2017-05-09 02:41:45 +00:00
@classmethod
def create(cls, customer=None, billing_address=None):
instance = cls.objects.create(customer=customer,
billing_address=billing_address)
2017-05-09 02:41:45 +00:00
return instance
2017-09-24 18:30:45 +00:00
2019-04-02 07:18:15 +00:00
class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
customer = models.ForeignKey(StripeCustomer)
order = models.ForeignKey(HostingOrder)
created = models.DateTimeField(help_text="When the invoice was created")
2019-04-02 07:18:15 +00:00
receipt_number = models.CharField(
help_text="The receipt number that is generated on Stripe",
max_length=100
2019-04-02 07:18:15 +00:00
)
invoice_number = models.CharField(
help_text="The invoice number that is generated on Stripe",
max_length=100
2019-04-02 07:18:15 +00:00
)
paid_at = models.DateTimeField(help_text="Date on which the bill was paid")
period_start = models.DateTimeField()
period_end = models.DateTimeField()
billing_reason = models.CharField(max_length=25)
discount = models.PositiveIntegerField()
total = models.IntegerField()
lines_data_count = models.IntegerField()
invoice_id = models.CharField(unique=True, max_length=100)
lines_meta_data_csv = models.TextField(default="")
subscription_ids_csv = models.TextField(default="")
2019-04-02 07:18:15 +00:00
permissions = ('view_monthlyhostingbill',)
class Meta:
permissions = (
('view_monthlyhostingbill', 'View Monthly Hosting'),
)
@classmethod
def create(cls, args):
# 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:
args['order'] = HostingOrder.objects.get(
subscription_id=sub_ids[0]
)
else:
logger.debug(
"More than one subscriptions"
"for MonthlyHostingBill {}".format(args['invoice_id'])
)
logger.debug("SUB_IDS=".format(','.join(sub_ids)))
logger.debug("Not importing invoices")
return
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:
args['order'] = HostingOrder.objects.get(vm_id=vm_ids[0])
else:
logger.debug(
"More than one VM_ID"
"for MonthlyHostingBill {}".format(args['invoice_id'])
)
logger.debug("VM_IDS=".format(','.join(vm_ids)))
logger.debug("Not importing invoices")
return
else:
logger.debug("Neither subscription id nor vm_id available")
logger.debug("Can't import invoice")
return
instance = cls.objects.create(
created=datetime.utcfromtimestamp(
args['created']).replace(tzinfo=pytz.utc),
receipt_number=(
args['receipt_number']
if args['receipt_number'] is not None else ''
),
paid_at=datetime.utcfromtimestamp(
args['paid_at']).replace(tzinfo=pytz.utc),
period_start=datetime.utcfromtimestamp(
args['period_start']).replace(tzinfo=pytz.utc),
period_end=datetime.utcfromtimestamp(
args['period_end']).replace(tzinfo=pytz.utc),
billing_reason=args['billing_reason'],
discount=args['discount'],
total=args['total'],
lines_data_count=args['lines_data_count'],
invoice_id=args['invoice_id'],
lines_meta_data_csv=args['lines_meta_data_csv'],
2019-04-03 07:12:14 +00:00
customer=args['customer'],
order=args['order'],
subscription_ids_csv=args['subscription_ids_csv'],
)
instance.assign_permissions(instance.stripe_customer.user)
return instance
2019-04-02 07:18:15 +00:00
2017-09-24 18:30:45 +00:00
class VMDetail(models.Model):
user = models.ForeignKey(CustomUser)
vm_id = models.IntegerField(default=0)
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)
ipv4 = models.TextField(default='')
ipv6 = models.TextField(default='')
created_at = models.DateTimeField(auto_now_add=True)
terminated_at = models.DateTimeField(null=True)
2017-09-30 12:25:49 +00:00
def end_date(self):
2017-09-30 12:35:02 +00:00
end_date = self.terminated_at if self.terminated_at else timezone.now()
2017-09-30 12:38:02 +00:00
months = relativedelta(end_date, self.created_at).months or 1
2017-09-30 12:40:34 +00:00
end_date = self.created_at + relativedelta(months=months, days=-1)
2017-09-30 12:25:49 +00:00
return end_date
class UserCardDetail(AssignPermissionsMixin, models.Model):
permissions = ('view_usercarddetail',)
2017-10-15 19:21:36 +00:00
stripe_customer = models.ForeignKey(StripeCustomer)
last4 = models.CharField(max_length=4)
2018-08-21 12:42:01 +00:00
brand = models.CharField(max_length=128)
card_id = models.CharField(max_length=100, blank=True, default='')
fingerprint = models.CharField(max_length=100)
exp_month = models.IntegerField(null=False)
exp_year = models.IntegerField(null=False)
preferred = models.BooleanField(default=False)
class Meta:
permissions = (
('view_usercarddetail', 'View User Card'),
)
@classmethod
def create(cls, stripe_customer=None, last4=None, brand=None,
fingerprint=None, exp_month=None, exp_year=None, card_id=None,
preferred=False):
instance = cls.objects.create(
stripe_customer=stripe_customer, last4=last4, brand=brand,
fingerprint=fingerprint, exp_month=exp_month, exp_year=exp_year,
card_id=card_id, preferred=preferred
)
instance.assign_permissions(stripe_customer.user)
return instance
@classmethod
def get_all_cards_list(cls, stripe_customer):
"""
Get all the cards of the given customer as a list
:param stripe_customer: The StripeCustomer object
:return: A list of all cards; an empty list if the customer object is
None
"""
cards_list = []
if stripe_customer is None:
return cards_list
user_card_details = UserCardDetail.objects.filter(
stripe_customer_id=stripe_customer.id
).order_by('-preferred', 'id')
for card in user_card_details:
cards_list.append({
'last4': card.last4, 'brand': card.brand, 'id': card.id,
'preferred': card.preferred
})
return cards_list
@classmethod
def get_or_create_user_card_detail(cls, stripe_customer, card_details):
"""
A method that checks if a UserCardDetail object exists already
based upon the given card_details and creates it for the given
customer if it does not exist. It returns the UserCardDetail object
matching the given card_details if it exists.
:param stripe_customer: The given StripeCustomer object to whom the
card object should belong to
:param card_details: A dictionary identifying a given card
:return: UserCardDetail object
"""
try:
if ('fingerprint' in card_details and 'exp_month' in card_details
and 'exp_year' in card_details):
card_detail = UserCardDetail.objects.get(
stripe_customer=stripe_customer,
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year']
)
else:
raise UserCardDetail.DoesNotExist()
except UserCardDetail.DoesNotExist:
2017-10-28 13:26:15 +00:00
preferred = False
if 'preferred' in card_details:
preferred = card_details['preferred']
card_detail = UserCardDetail.create(
stripe_customer=stripe_customer,
last4=card_details['last4'],
brand=card_details['brand'],
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year'],
2017-10-28 13:26:15 +00:00
card_id=card_details['card_id'],
preferred=preferred
)
return card_detail
2017-10-28 20:18:54 +00:00
@staticmethod
def set_default_card(stripe_api_cus_id, stripe_source_id):
"""
Sets the given stripe source as the default source for the given
Stripe customer
:param stripe_api_cus_id: Stripe customer id
:param stripe_source_id: The Stripe source id
:return:
"""
stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object']
cu.default_source = stripe_source_id
cu.save()
2017-10-28 20:18:54 +00:00
UserCardDetail.save_default_card_local(
stripe_api_cus_id, stripe_source_id
)
2017-10-28 20:18:54 +00:00
@staticmethod
def set_default_card_from_stripe(stripe_api_cus_id):
stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object']
default_source = cu.default_source
if default_source is not None:
2017-10-28 20:18:54 +00:00
UserCardDetail.save_default_card_local(
stripe_api_cus_id, default_source
)
2017-10-28 20:18:54 +00:00
@staticmethod
def save_default_card_local(stripe_api_cus_id, card_id):
stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id)
user_card_detail = UserCardDetail.objects.get(
stripe_customer=stripe_cust, card_id=card_id
)
for card in stripe_cust.usercarddetail_set.all():
card.preferred = False
card.save()
user_card_detail.preferred = True
user_card_detail.save()
2017-10-28 16:28:14 +00:00
@staticmethod
def get_user_card_details(stripe_customer, card_details):
2017-10-28 16:28:14 +00:00
"""
A utility function to check whether a StripeCustomer is already
associated with the card having given details
2017-10-28 16:28:14 +00:00
:param stripe_customer:
:param card_details:
2018-07-05 07:58:57 +00:00
:return: The UserCardDetails object if it exists, None otherwise
2017-10-28 16:28:14 +00:00
"""
try:
ucd = UserCardDetail.objects.get(
2017-10-28 16:28:14 +00:00
stripe_customer=stripe_customer,
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year']
)
return ucd
2017-10-28 16:28:14 +00:00
except UserCardDetail.DoesNotExist:
2018-07-05 07:58:57 +00:00
return None