From 1be7852e6a4fd1f4383a60ad56d7a8a66d45917b Mon Sep 17 00:00:00 2001 From: "M.Ravi" Date: Tue, 5 Dec 2023 15:58:47 +0530 Subject: [PATCH] add dot-env-sample --- dot-env-sample | 50 +++ dynamicweb2/ldap_manager.py | 282 -------------- dynamicweb2/stripe_utils.py | 573 ----------------------------- hosting/migrations/0001_initial.py | 148 -------- hosting/tests.py | 3 - 5 files changed, 50 insertions(+), 1006 deletions(-) create mode 100644 dot-env-sample delete mode 100644 dynamicweb2/ldap_manager.py delete mode 100644 dynamicweb2/stripe_utils.py delete mode 100644 hosting/migrations/0001_initial.py delete mode 100644 hosting/tests.py diff --git a/dot-env-sample b/dot-env-sample new file mode 100644 index 0000000..7527bdc --- /dev/null +++ b/dot-env-sample @@ -0,0 +1,50 @@ +# LDAP settings +LDAPSERVER="ldaps://" + +LDAP_ADMIN_DN="uid=..." +LDAP_ADMIN_PASSWORD=".." + +# Space separated list of search bases for users +LDAP_CUSTOMER_DN="" +LDAP_DEFAULT_START_UID=0 +LDAP_CUSTOMER_GROUP_ID=0 +ENTIRE_SEARCH_BASE="" +LDAP_USE_TLS=True + +STRIPE_API_PRIVATE_KEY='sk_test_xxx' # used in backend payment +STRIPE_API_PRIVATE_KEY_TEST='sk_test_xxx' # used in backend payment +STRIPE_API_PUBLIC_KEY='pk_test_xxx' # used in frontend to call from user browser + +SECRET_KEY=abc + +DCL_TEXT='Data Center Light' +DCL_SUPPORT_FROM_ADDRESS='' +SEND_EMAIL=False + +# Change this to modify the base price of a VM +# Note: Price in CHF, 1 represents 1 CHF +VM_BASE_PRICE=1 + +EMAIL_HOST='localhost' +EMAIL_PORT=25 +EMAIL_USE_TLS=True + +BYPASS_OPENNEBULA=False +# The oneadmin user name of the OpenNebula infrastructure +OPENNEBULA_USERNAME='' + +# The oneadmin password of the OpenNebula infrastructure +# The default credentials of the Sandbox OpenNebula VM is +# oneadmin:opennebula +OPENNEBULA_PASSWORD='' + + +# The protocol is generally http or https +OPENNEBULA_PROTOCOL='https' + +# The ip address or the domain name of the opennebula infrastructure +OPENNEBULA_DOMAIN='' + +# The port to connect in order to send an xmlrpc request. The default +# port is 2633 +OPENNEBULA_PORT='' \ No newline at end of file diff --git a/dynamicweb2/ldap_manager.py b/dynamicweb2/ldap_manager.py deleted file mode 100644 index d40e931..0000000 --- a/dynamicweb2/ldap_manager.py +++ /dev/null @@ -1,282 +0,0 @@ -import base64 -import hashlib -import random -import ldap3 -import logging -import unicodedata - -from django.conf import settings - -logger = logging.getLogger(__name__) - - -class LdapManager: - __instance = None - - def __new__(cls): - if LdapManager.__instance is None: - LdapManager.__instance = object.__new__(cls) - return LdapManager.__instance - - def __init__(self): - """ - Initialize the LDAP subsystem. - """ - self.rng = random.SystemRandom() - self.server = ldap3.Server(settings.AUTH_LDAP_SERVER) - - def get_admin_conn(self): - """ - Return a bound :class:`ldap3.Connection` instance which has write - permissions on the dn in which the user accounts reside. - """ - conn = self.get_conn(user=settings.LDAP_ADMIN_DN, - password=settings.LDAP_ADMIN_PASSWORD, - raise_exceptions=True) - conn.bind() - return conn - - def get_conn(self, **kwargs): - """ - Return an unbound :class:`ldap3.Connection` which talks to the configured - LDAP server. - - The *kwargs* are passed to the constructor of :class:`ldap3.Connection` and - can be used to set *user*, *password* and other useful arguments. - """ - return ldap3.Connection(self.server, **kwargs) - - def _ssha_password(self, password): - """ - Apply the SSHA password hashing scheme to the given *password*. - *password* must be a :class:`bytes` object, containing the utf-8 - encoded password. - - Return a :class:`bytes` object containing ``ascii``-compatible data - which can be used as LDAP value, e.g. after armoring it once more using - base64 or decoding it to unicode from ``ascii``. - """ - SALT_BYTES = 15 - - sha1 = hashlib.sha1() - salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, "little") - sha1.update(password) - sha1.update(salt) - - digest = sha1.digest() - passwd = b"{SSHA}" + base64.b64encode(digest + salt) - return passwd - - def create_user(self, user, password, firstname, lastname, email): - conn = self.get_admin_conn() - uidNumber = self._get_max_uid() + 1 - logger.debug("uidNumber={uidNumber}".format(uidNumber=uidNumber)) - user_exists = True - while user_exists: - user_exists, _ = self.check_user_exists( - "", - '(&(objectClass=inetOrgPerson)(objectClass=posixAccount)' - '(objectClass=top)(uidNumber={uidNumber}))'.format( - uidNumber=uidNumber - ) - ) - if user_exists: - logger.debug( - "{uid} exists. Trying next.".format(uid=uidNumber) - ) - uidNumber += 1 - logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) - self._set_max_uid(uidNumber) - try: - uid = user - conn.add("uid={uid},{customer_dn}".format( - uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN - ), - ["inetOrgPerson", "posixAccount", "ldapPublickey"], - { - "uid": [uid], - "sn": [lastname.encode("utf-8")], - "givenName": [firstname.encode("utf-8")], - "cn": [uid], - "displayName": ["{} {}".format(firstname, lastname).encode("utf-8")], - "uidNumber": [str(uidNumber)], - "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], - "loginShell": ["/bin/bash"], - "homeDirectory": ["/home/{}".format(unicodedata.normalize('NFKD', user).encode('ascii','ignore'))], - "mail": email.encode("utf-8"), - "userPassword": [self._ssha_password( - password.encode("utf-8") - )] - } - ) - logger.debug('Created user %s %s' % (user.encode('utf-8'), - uidNumber)) - except Exception as ex: - logger.debug('Could not create user %s' % user.encode('utf-8')) - logger.error("Exception: " + str(ex)) - raise Exception(ex) - finally: - conn.unbind() - - def change_password(self, uid, new_password): - """ - Changes the password of the user identified by user_dn - - :param uid: str The uid that identifies the user - :param new_password: str The new password string - :return: True if password was changed successfully False otherwise - """ - conn = self.get_admin_conn() - - # Make sure the user exists first to change his/her details - user_exists, entries = self.check_user_exists( - uid=uid, - search_base=settings.ENTIRE_SEARCH_BASE - ) - return_val = False - if user_exists: - try: - return_val = conn.modify( - entries[0].entry_dn, - { - "userpassword": ( - ldap3.MODIFY_REPLACE, - [self._ssha_password(new_password.encode("utf-8"))] - ) - } - ) - except Exception as ex: - logger.error("Exception: " + str(ex)) - else: - logger.error("User {} not found".format(uid)) - - conn.unbind() - return return_val - - - def change_user_details(self, uid, details): - """ - Updates the user details as per given values in kwargs of the user - identified by user_dn. - - Assumes that all attributes passed in kwargs are valid. - - :param uid: str The uid that identifies the user - :param details: dict A dictionary containing the new values - :return: True if user details were updated successfully False otherwise - """ - conn = self.get_admin_conn() - - # Make sure the user exists first to change his/her details - user_exists, entries = self.check_user_exists( - uid=uid, - search_base=settings.ENTIRE_SEARCH_BASE - ) - - return_val = False - if user_exists: - details_dict = {k: (ldap3.MODIFY_REPLACE, [v.encode("utf-8")]) for - k, v in details.items()} - try: - return_val = conn.modify(entries[0].entry_dn, details_dict) - msg = "success" - except Exception as ex: - msg = str(ex) - logger.error("Exception: " + msg) - finally: - conn.unbind() - else: - msg = "User {} not found".format(uid) - logger.error(msg) - conn.unbind() - return return_val, msg - - def check_user_exists(self, uid, search_filter="", attributes=None, - search_base=settings.LDAP_CUSTOMER_DN, search_attr="uid"): - """ - Check if the user with the given uid exists in the customer group. - - :param uid: str representing the user - :param search_filter: str representing the filter condition to find - users. If its empty, the search finds the user with - the given uid. - :param attributes: list A list of str representing all the attributes - to be obtained in the result entries - :param search_base: str - :return: tuple (bool, [ldap3.abstract.entry.Entry ..]) - A bool indicating if the user exists - A list of all entries obtained in the search - """ - conn = self.get_admin_conn() - entries = [] - try: - result = conn.search( - search_base=search_base, - search_filter=search_filter if len(search_filter) > 0 else - '(uid={uid})'.format(uid=uid), - attributes=attributes - ) - entries = conn.entries - finally: - conn.unbind() - return result, entries - - def delete_user(self, uid): - """ - Deletes the user with the given uid from ldap - - :param uid: str representing the user - :return: True if the delete was successful False otherwise - """ - conn = self.get_admin_conn() - try: - return_val = conn.delete( - ("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=uid), - ) - msg = "success" - except Exception as ex: - msg = str(ex) - logger.error("Exception: " + msg) - return_val = False - finally: - conn.unbind() - return return_val, msg - - def _set_max_uid(self, max_uid): - """ - a utility function to save max_uid value to a file - - :param max_uid: an integer representing the max uid - :return: - """ - with open(settings.LDAP_MAX_UID_FILE_PATH, 'w+') as handler: - handler.write(str(max_uid)) - - def _get_max_uid(self): - """ - A utility function to read the max uid value that was previously set - - :return: An integer representing the max uid value that was previously - set - """ - try: - with open(settings.LDAP_MAX_UID_FILE_PATH, 'r+') as handler: - try: - return_value = int(handler.read()) - except ValueError as ve: - logger.error( - "Error reading int value from {}. {}" - "Returning default value {} instead".format( - settings.LDAP_MAX_UID_FILE_PATH, - str(ve), - settings.LDAP_DEFAULT_START_UID - ) - ) - return_value = settings.LDAP_DEFAULT_START_UID - return return_value - except FileNotFoundError as fnfe: - logger.error("File not found : " + str(fnfe)) - return_value = settings.LDAP_DEFAULT_START_UID - logger.error("So, returning UID={}".format(return_value)) - return return_value - diff --git a/dynamicweb2/stripe_utils.py b/dynamicweb2/stripe_utils.py deleted file mode 100644 index 64b0223..0000000 --- a/dynamicweb2/stripe_utils.py +++ /dev/null @@ -1,573 +0,0 @@ -import logging -import re - -import stripe -from django.conf import settings - -# from datacenterlight.models import StripePlan - -stripe.api_key = settings.STRIPE_API_PRIVATE_KEY -logger = logging.getLogger(__name__) - - -def handleStripeError(f): - def handleProblems(*args, **kwargs): - response = { - 'paid': False, - 'response_object': None, - 'error': None - } - - common_message = "Currently it's not possible to make payments." - try: - response_object = f(*args, **kwargs) - response = { - 'response_object': response_object, - 'error': None - } - return response - except stripe.error.CardError as e: - # Since it's a decline, stripe.error.CardError will be caught - body = e.json_body - err = body['error'] - response.update({'error': err['message']}) - logger.error(str(e)) - return response - except stripe.error.RateLimitError as e: - logger.error(str(e)) - response.update( - {'error': "Too many requests made to the API too quickly"}) - return response - except stripe.error.InvalidRequestError as e: - logger.error(str(e)) - response.update({'error': str(e._message)}) - return response - except stripe.error.AuthenticationError as e: - # Authentication with Stripe's API failed - # (maybe you changed API keys recently) - logger.error(str(e)) - response.update({'error': str(e)}) - return response - except stripe.error.APIConnectionError as e: - logger.error(str(e)) - response.update({'error': str(e)}) - return response - except stripe.error.StripeError as e: - # maybe send email - logger.error(str(e)) - response.update({'error': str(e)}) - return response - except Exception as e: - # maybe send email - logger.error(str(e)) - response.update({'error': str(e)}) - return response - - return handleProblems - - -class StripeUtils(object): - CURRENCY = 'chf' - INTERVAL = 'month' - SUCCEEDED_STATUS = 'succeeded' - RESOURCE_ALREADY_EXISTS_ERROR_CODE = 'resource_already_exists' - STRIPE_NO_SUCH_PLAN = 'No such plan' - PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.' - PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.' - - def __init__(self): - self.stripe = stripe - - def update_customer_token(self, customer, token): - customer.source = token - customer.save() - - @handleStripeError - def associate_customer_card(self, stripe_customer_id, id_payment_method, - set_as_default=False): - customer = stripe.Customer.retrieve(stripe_customer_id) - stripe.PaymentMethod.attach( - id_payment_method, - customer=stripe_customer_id, - ) - if set_as_default: - customer.invoice_settings.default_payment_method = id_payment_method - customer.save() - return True - - @handleStripeError - def dissociate_customer_card(self, stripe_customer_id, card_id): - customer = stripe.Customer.retrieve(stripe_customer_id) - if card_id.startswith("pm"): - logger.debug("PaymentMethod %s detached %s" % (card_id, - stripe_customer_id)) - pm = stripe.PaymentMethod.retrieve(card_id) - stripe.PaymentMethod.detach(card_id) - pm.delete() - else: - logger.debug("card %s detached %s" % (card_id, stripe_customer_id)) - card = customer.sources.retrieve(card_id) - card.delete() - - @handleStripeError - def update_customer_card(self, customer_id, token): - customer = stripe.Customer.retrieve(customer_id) - current_card_token = customer.default_source - customer.sources.retrieve(current_card_token).delete() - customer.source = token - customer.save() - credit_card_raw_data = customer.sources.data.pop() - new_card_data = { - 'last4': credit_card_raw_data.last4, - 'brand': credit_card_raw_data.brand - } - return new_card_data - - @handleStripeError - def get_card_details(self, customer_id): - customer = stripe.Customer.retrieve(customer_id) - credit_card_raw_data = customer.sources.data.pop() - card_details = { - 'last4': credit_card_raw_data.last4, - 'brand': credit_card_raw_data.brand, - 'exp_month': credit_card_raw_data.exp_month, - 'exp_year': credit_card_raw_data.exp_year, - 'fingerprint': credit_card_raw_data.fingerprint, - 'card_id': credit_card_raw_data.id - } - return card_details - - @handleStripeError - def get_all_invoices(self, customer_id, created_gt): - return_list = [] - has_more_invoices = True - starting_after = False - while has_more_invoices: - if starting_after: - invoices = stripe.Invoice.list( - limit=10, customer=customer_id, created={'gt': created_gt}, - starting_after=starting_after - ) - else: - invoices = stripe.Invoice.list( - limit=10, customer=customer_id, created={'gt': created_gt} - ) - has_more_invoices = invoices.has_more - for invoice in invoices.data: - sub_ids = [] - for line in invoice.lines.data: - if line.type == 'subscription': - sub_ids.append(line.id) - elif line.type == 'invoiceitem': - sub_ids.append(line.subscription) - else: - sub_ids.append('') - invoice_details = { - 'created': invoice.created, - 'receipt_number': invoice.receipt_number, - 'invoice_number': invoice.number, - 'paid_at': invoice.status_transitions.paid_at if invoice.paid else 0, - 'period_start': invoice.period_start, - 'period_end': invoice.period_end, - 'billing_reason': invoice.billing_reason, - 'discount': invoice.discount.coupon.amount_off if invoice.discount else 0, - 'total': invoice.total, - # to see how many line items we have in this invoice and - # then later check if we have more than 1 - 'lines_data_count': len(invoice.lines.data) if invoice.lines.data is not None else 0, - 'invoice_id': invoice.id, - 'lines_meta_data_csv': ','.join( - [line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' for line in invoice.lines.data] - ), - 'subscription_ids_csv': ','.join(sub_ids), - 'line_items': invoice.lines.data - } - starting_after = invoice.id - return_list.append(invoice_details) - return return_list - - @handleStripeError - def get_cards_details_from_token(self, token): - stripe_token = stripe.Token.retrieve(token) - card_details = { - 'last4': stripe_token.card.last4, - 'brand': stripe_token.card.brand, - 'exp_month': stripe_token.card.exp_month, - 'exp_year': stripe_token.card.exp_year, - 'fingerprint': stripe_token.card.fingerprint, - 'card_id': stripe_token.card.id - } - return card_details - - @handleStripeError - def get_cards_details_from_payment_method(self, payment_method_id): - payment_method = stripe.PaymentMethod.retrieve(payment_method_id) - # payment_method does not always seem to have a card with id - # if that is the case, fallback to payment_method_id for card_id - card_id = payment_method_id - if hasattr(payment_method.card, 'id'): - card_id = payment_method.card.id - card_details = { - 'last4': payment_method.card.last4, - 'brand': payment_method.card.brand, - 'exp_month': payment_method.card.exp_month, - 'exp_year': payment_method.card.exp_year, - 'fingerprint': payment_method.card.fingerprint, - 'card_id': card_id - } - return card_details - - def check_customer(self, stripe_cus_api_id, user, token): - try: - customer = stripe.Customer.retrieve(stripe_cus_api_id) - except stripe.InvalidRequestError: - customer = self.create_customer(token, user.email, user.name) - user.stripecustomer.stripe_id = customer.get( - 'response_object').get('id') - user.stripecustomer.save() - if type(customer) is dict: - customer = customer['response_object'] - return customer - - @handleStripeError - def get_customer(self, stripe_api_cus_id): - customer = stripe.Customer.retrieve(stripe_api_cus_id) - # data = customer.get('response_object') - return customer - - @handleStripeError - def create_customer(self, id_payment_method, email, name=None): - if name is None or name.strip() == "": - name = email - customer = self.stripe.Customer.create( - payment_method=id_payment_method, - description=name, - email=email - ) - return customer - - @handleStripeError - def make_charge(self, amount=None, customer=None): - _amount = float(amount) - amount = int(_amount * 100) # stripe amount unit, in cents - charge = self.stripe.Charge.create( - amount=amount, # in cents - currency=self.CURRENCY, - customer=customer - ) - return charge - - # @handleStripeError - # def get_or_create_stripe_plan(self, amount, name, stripe_plan_id, - # interval=""): - # """ - # This function checks if a StripePlan with the given - # stripe_plan_id already exists. If it exists then the function - # returns this object otherwise it creates a new StripePlan and - # returns the new object. - # - # :param amount: The amount in CHF - # :param name: The name of the Stripe plan to be created. - # :param stripe_plan_id: The id of the Stripe plan to be - # created. Use get_stripe_plan_id_string function to - # obtain the name of the plan to be created - # :param interval: str representing the interval of the Plan - # Specifies billing frequency. Either day, week, month or year. - # Ref: https://stripe.com/docs/api/plans/create#create_plan-interval - # The default is month - # :return: The StripePlan object if it exists else creates a - # Plan object in Stripe and a local StripePlan and - # returns it. Returns None in case of Stripe error - # """ - # _amount = float(amount) - # amount = int(_amount * 100) # stripe amount unit, in cents - # stripe_plan_db_obj = None - # plan_interval = interval if interval is not "" else self.INTERVAL - # try: - # stripe_plan_db_obj = StripePlan.objects.get( - # stripe_plan_id=stripe_plan_id) - # except StripePlan.DoesNotExist: - # try: - # self.stripe.Plan.create( - # amount=amount, - # interval=plan_interval, - # name=name, - # currency=self.CURRENCY, - # id=stripe_plan_id) - # stripe_plan_db_obj = StripePlan.objects.create( - # stripe_plan_id=stripe_plan_id) - # except stripe.error.InvalidRequestError as e: - # logger.error(str(e)) - # logger.error("error_code = %s" % str(e.__dict__)) - # if self.RESOURCE_ALREADY_EXISTS_ERROR_CODE in e.error.code: - # logger.debug( - # self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id)) - # stripe_plan_db_obj, c = StripePlan.objects.get_or_create( - # stripe_plan_id=stripe_plan_id) - # if c: - # logger.debug("Created stripe plan %s" % stripe_plan_id) - # else: - # logger.debug("Plan %s exists already" % stripe_plan_id) - # return stripe_plan_db_obj - # - # @handleStripeError - # def delete_stripe_plan(self, stripe_plan_id): - # """ - # Deletes the Plan in Stripe and also deletes the local db copy - # of the plan if it exists - # - # :param stripe_plan_id: The stripe plan id that needs to be - # deleted - # :return: True if the plan was deleted successfully from - # Stripe, False otherwise. - # """ - # return_value = False - # try: - # plan = self.stripe.Plan.retrieve(stripe_plan_id) - # plan.delete() - # return_value = True - # StripePlan.objects.filter( - # stripe_plan_id=stripe_plan_id).all().delete() - # except stripe.error.InvalidRequestError as e: - # if self.STRIPE_NO_SUCH_PLAN in str(e): - # logger.debug( - # self.PLAN_DOES_NOT_EXIST_ERROR_MSG.format(stripe_plan_id)) - # return return_value - - @handleStripeError - def subscribe_customer_to_plan(self, customer, plans, trial_end=None, - coupon="", tax_rates=list(), - default_payment_method=""): - """ - Subscribes the given customer to the list of given plans - - :param default_payment_method: - :param tax_rates: - :param coupon: - :param customer: The stripe customer identifier - :param plans: A list of stripe plans. - :param trial_end: An integer representing when the Stripe subscription - is supposed to end - Ref: https://stripe.com/docs/api/python#create_subscription-items - e.g. - plans = [ - { - "plan": "dcl-v1-cpu-2-ram-5gb-ssd-10gb", - }, - ] - :return: The subscription StripeObject - """ - logger.debug("Subscribing %s to plan %s : coupon = %s" % ( - customer, str(plans), str(coupon) - )) - subscription_result = self.stripe.Subscription.create( - customer=customer, items=plans, trial_end=trial_end, - coupon=coupon, - default_tax_rates=tax_rates, - payment_behavior='allow_incomplete', - default_payment_method=default_payment_method - ) - logger.debug("Done subscribing") - return subscription_result - - @handleStripeError - def set_subscription_metadata(self, subscription_id, metadata): - subscription = stripe.Subscription.retrieve(subscription_id) - subscription.metadata = metadata - subscription.save() - - @handleStripeError - def unsubscribe_customer(self, subscription_id): - """ - Cancels a given subscription - - :param subscription_id: The Stripe subscription id string - :return: - """ - sub = stripe.Subscription.retrieve(subscription_id) - return sub.delete() - - @handleStripeError - def make_payment(self, customer, amount, token): - charge = self.stripe.Charge.create( - amount=amount, # in cents - currency=self.CURRENCY, - customer=customer - ) - return charge - - @staticmethod - def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None, - price=None, excl_vat=True): - """ - Returns the Stripe plan id string of the form - `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters - - :param cpu: The number of cores - :param ram: The size of the RAM in GB - :param ssd: The size of ssd storage in GB - :param hdd: The size of hdd storage in GB - :param version: The version of the Stripe plans - :param app: The application to which the stripe plan belongs - to. By default it is 'dcl' - :param price: The price for this plan - :return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb` - """ - dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu, - ram=ram, - ssd=ssd) - if hdd is not None: - dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format( - dcl_plan_string=dcl_plan_string, hdd=hdd) - stripe_plan_id_string = '{app}-v{version}-{plan}'.format( - app=app, - version=version, - plan=dcl_plan_string - ) - if price is not None: - stripe_plan_id_string = '{}-{}chf'.format( - stripe_plan_id_string, - round(price, 2) - ) - if excl_vat: - stripe_plan_id_string = '{}-{}'.format( - stripe_plan_id_string, - "excl_vat" - ) - return stripe_plan_id_string - - @staticmethod - def get_vm_config_from_stripe_id(stripe_id): - """ - Given a string like "dcl-v1-cpu-2-ram-5gb-ssd-10gb" return different - configuration params as a dict - - :param stripe_id|str - :return: dict - """ - pattern = re.compile(r'^dcl-v(\d+)-cpu-(\d+)-ram-(\d+\.?\d*)gb-ssd-(\d+)gb-?(\d*\.?\d*)(chf)?$') - match_res = pattern.match(stripe_id) - if match_res is not None: - price = None - try: - price=match_res.group(5) - except IndexError as ie: - logger.debug("Did not find price in {}".format(stripe_id)) - return { - 'version': match_res.group(1), - 'cores': match_res.group(2), - 'ram': match_res.group(3), - 'ssd': match_res.group(4), - 'price': price - } - - - @staticmethod - def get_stripe_plan_name(cpu, memory, disk_size, price, excl_vat=True): - """ - Returns the Stripe plan name - :return: - """ - if excl_vat: - return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ - "{price} CHF Excl. VAT".format( - cpu=cpu, - memory=memory, - disk_size=disk_size, - price=round(price, 2) - ) - else: - return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ - "{price} CHF".format( - cpu=cpu, - memory=memory, - disk_size=disk_size, - price=round(price, 2) - ) - - @handleStripeError - def set_subscription_meta_data(self, subscription_id, meta_data): - """ - Adds VM metadata to a subscription - :param subscription_id: Stripe identifier for the subscription - :param meta_data: A dict of meta data to be added - :return: - """ - subscription = stripe.Subscription.retrieve(subscription_id) - subscription.metadata = meta_data - subscription.save() - - @handleStripeError - def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number, - vat_type="eu_vat", country=""): - tax_ids_list = stripe.Customer.list_tax_ids( - stripe_customer_id, - limit=100, - ) - for tax_id_obj in tax_ids_list.data: - if self.compare_vat_numbers(tax_id_obj.value, vat_number): - logger.debug("tax id obj exists already") - return tax_id_obj - else: - logger.debug( - "{val1} is not equal to {val2} or {con1} not same as " - "{con2}".format(val1=tax_id_obj.value, val2=vat_number, - con1=tax_id_obj.country.lower(), - con2=country.lower().strip())) - logger.debug( - "tax id obj does not exist for {val}. Creating a new one".format( - val=vat_number - )) - tax_id_obj = stripe.Customer.create_tax_id( - stripe_customer_id, - type=vat_type, - value=vat_number, - ) - return tax_id_obj - - @handleStripeError - def get_payment_intent(self, amount, customer): - """ Create a stripe PaymentIntent of the given amount and return it - :param amount: the amount of payment_intent - :return: - """ - payment_intent_obj = stripe.PaymentIntent.create( - amount=amount, - currency='chf', - customer=customer, - setup_future_usage='off_session' - ) - return payment_intent_obj - - @handleStripeError - def get_available_payment_methods(self, customer): - """ Retrieves all payment methods of the given customer - :param customer: StripeCustomer object - :return: a list of available payment methods - """ - return_list = [] - if customer is None: - return return_list - cu = stripe.Customer.retrieve(customer.stripe_id) - pms = stripe.PaymentMethod.list( - customer=customer.stripe_id, - type="card", - ) - default_source = None - if cu.default_source: - default_source = cu.default_source - else: - default_source = cu.invoice_settings.default_payment_method - for pm in pms.data: - return_list.append({ - 'last4': pm.card.last4, 'brand': pm.card.brand, 'id': pm.id, - 'exp_year': pm.card.exp_year, - 'exp_month': '{:02d}'.format(pm.card.exp_month), - 'preferred': pm.id == default_source - }) - return return_list - - def compare_vat_numbers(self, vat1, vat2): - _vat1 = vat1.replace(" ", "").replace(".", "").replace("-","") - _vat2 = vat2.replace(" ", "").replace(".", "").replace("-","") - return True if _vat1 == _vat2 else False diff --git a/hosting/migrations/0001_initial.py b/hosting/migrations/0001_initial.py deleted file mode 100644 index 6c3a767..0000000 --- a/hosting/migrations/0001_initial.py +++ /dev/null @@ -1,148 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-21 11:58 - -from django.db import migrations, models -import django.db.models.deletion -import hosting.fields -import hosting.models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('sites', '0002_alter_domain_unique'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='BillingAddress', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cardholder_name', models.CharField(default='', max_length=100)), - ('street_address', models.CharField(max_length=100)), - ('city', models.CharField(max_length=50)), - ('postal_code', models.CharField(max_length=50)), - ('country', hosting.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2)), - ('vat_number', models.CharField(blank=True, default='', max_length=100)), - ('stripe_tax_id', models.CharField(blank=True, default='', max_length=100)), - ('vat_number_validated_on', models.DateTimeField(blank=True, null=True)), - ('vat_validation_status', models.CharField(blank=True, default='', max_length=25)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='CustomUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('name', models.CharField(max_length=50, validators=[hosting.models.validate_name])), - ('email', models.EmailField(max_length=254, unique=True)), - ('username', models.CharField(max_length=60, null=True, unique=True)), - ('validated', models.IntegerField(choices=[(0, 'Not validated'), (1, 'Validated')], default=0)), - ('in_ldap', models.BooleanField(default=False)), - ('validation_slug', models.CharField(db_index=True, default=hosting.models.get_validation_slug, max_length=50, unique=True)), - ('is_admin', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('import_stripe_bill_remark', models.TextField(default='', help_text='Indicates any issues while importing stripe bills')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to.', related_name='custom_user_set', to='auth.group', verbose_name='groups')), - ('site', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.site')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='custom_user_permission_set', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='GenericProduct', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('product_name', models.CharField(default='', max_length=128)), - ('product_slug', models.SlugField(help_text='An mandatory unique slug for the product', unique=True)), - ('product_description', models.CharField(default='', max_length=500)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('product_price', models.DecimalField(decimal_places=2, max_digits=6)), - ('product_vat', models.DecimalField(decimal_places=4, default=0, max_digits=6)), - ('product_is_subscription', models.BooleanField(default=True)), - ('product_subscription_interval', models.CharField(default='month', help_text='Choose between `year` and `month`', max_length=10)), - ('exclude_vat_calculations', models.BooleanField(default=False, help_text='When checked VAT calculations are excluded for this product')), - ], - bases=(hosting.models.AssignPermissionsMixin, models.Model), - ), - migrations.CreateModel( - name='OrderDetail', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cores', models.IntegerField(default=0)), - ('memory', models.IntegerField(default=0)), - ('hdd_size', models.IntegerField(default=0)), - ('ssd_size', models.IntegerField(default=0)), - ], - bases=(hosting.models.AssignPermissionsMixin, models.Model), - ), - migrations.CreateModel( - name='UserHostingKey', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('public_key', models.TextField()), - ('private_key', models.FileField(blank=True, upload_to='private_keys')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('name', models.CharField(max_length=100)), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.customuser')), - ], - ), - migrations.CreateModel( - name='UserBillingAddress', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cardholder_name', models.CharField(default='', max_length=100)), - ('street_address', models.CharField(max_length=100)), - ('city', models.CharField(max_length=50)), - ('postal_code', models.CharField(max_length=50)), - ('country', hosting.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2)), - ('vat_number', models.CharField(blank=True, default='', max_length=100)), - ('stripe_tax_id', models.CharField(blank=True, default='', max_length=100)), - ('vat_number_validated_on', models.DateTimeField(blank=True, null=True)), - ('vat_validation_status', models.CharField(blank=True, default='', max_length=25)), - ('current', models.BooleanField(default=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='billing_addresses', to='hosting.customuser')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='StripeCustomer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('stripe_id', models.CharField(max_length=100, unique=True)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='hosting.customuser')), - ], - ), - migrations.CreateModel( - name='HostingOrder', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('vm_id', models.IntegerField(default=0)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('approved', models.BooleanField(default=False)), - ('last4', models.CharField(max_length=4)), - ('cc_brand', models.CharField(max_length=128)), - ('stripe_charge_id', models.CharField(max_length=100, null=True)), - ('price', models.FloatField()), - ('subscription_id', models.CharField(max_length=100, null=True)), - ('generic_payment_description', models.CharField(max_length=500, null=True)), - ('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.billingaddress')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.stripecustomer')), - ('generic_product', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.genericproduct')), - ('order_detail', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.orderdetail')), - ], - options={ - 'permissions': (('u_view_hostingorder', 'View Hosting Order'),), - }, - bases=(hosting.models.AssignPermissionsMixin, models.Model), - ), - ] diff --git a/hosting/tests.py b/hosting/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/hosting/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here.