uselotus__lotus METASEP

billing/management/commands/wait_for_db.py METASEP
from django.core.management.base import BaseCommand
from django.db import connections
import time
from django.db.utils import OperationalError


class Command(BaseCommand):
    "Django command to pause execution until the database is available"

    def handle(self, *args, **options):
        "Pause execution until the database is available"

        db_connection = None
        while not db_connection:
            try:
                db_connection = connections["default"]
            except OperationalError:
                time.sleep(1)
        print("Database is available")

billing/management/commands/initadmin.py METASEP
from django.core.management.base import BaseCommand
from django.db import connections
from django.db.utils import OperationalError
from ...models import User
from dotenv import load_dotenv
import os

load_dotenv()


class Command(BaseCommand):
    def handle(self, *args, **options):
        username = os.getenv("DJANGO_SUPERUSER_USERNAME")
        email = os.getenv("DJANGO_SUPERUSER_EMAIL")
        password = os.getenv("DJANGO_SUPERUSER_PASSWORD")

        if not User.objects.filter(username=username).exists():
            print("Creating account for %s (%s)" % (username, email))
            admin = User.objects.create_superuser(
                email=email, username=username, password=password
            )
        else:
            print("Admin account has already been initialized.")

billing/management/commands/__init__.py METASEP

billing/views/views.py METASEP
from django.forms.models import model_to_dict
import dateutil.parser as parser
from ..models import Customer, Event, Subscription, BillingPlan
from ..serializers import EventSerializer, SubscriptionSerializer, CustomerSerializer
from rest_framework.views import APIView
from django_q.tasks import async_task
from ..tasks import generate_invoice

from rest_framework import viewsets
from ..permissions import HasUserAPIKey
from rest_framework.response import Response

# Create your views here.


class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.all()
    serializer_class = EventSerializer


class SubscriptionViewSet(viewsets.ModelViewSet):
    queryset = Subscription.objects.all()
    serializer_class = SubscriptionSerializer


class SubscriptionView(APIView):
    permission_classes = [HasUserAPIKey]

    def get(self, request, format=None):
        """
        List active subscriptions. If customer_id is provided, only return subscriptions for that customer.
        """
        if "customer_id" in request.query_params:
            customer_id = request.query_params["customer_id"]
            customer = Customer.objects.get(customer_id=customer_id)
            subscriptions = Subscription.objects.filter(
                customer=customer, status="active"
            )
            serializer = SubscriptionSerializer(subscriptions, many=True)
            return Response(serializer.data)
        else:
            subscriptions = Subscription.objects.filter(status="active")
            serializer = SubscriptionSerializer(subscriptions, many=True)
            return Response(serializer.data)

    def post(self, request, format=None):
        """
        Create a new subscription, joining a customer and a plan.
        """
        data = request.data
        customer_qs = Customer.objects.filter(customer_id=data["customer_id"])
        start_date = parser.parse(data["start_date"])

        if len(customer_qs) < 1:
            return Response(
                {
                    "error": "Customer with customer_id {} does not exist".format(
                        data["customer_id"]
                    )
                },
                status=400,
            )
        else:
            customer = customer_qs[0]
        plan_qs = BillingPlan.objects.filter(plan_id=data["plan_id"])
        if len(plan_qs) < 1:
            return Response(
                {
                    "error": "Plan with plan_id {} does not exist".format(
                        data["plan_id"]
                    )
                },
                status=400,
            )
        else:
            plan = plan_qs[0]
            end_date = plan.subscription_end_date(start_date)

        subscription = Subscription.objects.create(
            customer=customer,
            start_date=start_date,
            end_date=end_date,
            billing_plan=plan,
            status="active",
        )
        subscription.save()

        serializer_context = {
            "request": request,
        }

        return Response("Subscription Created", status=201)


class CustomerView(APIView):

    permission_classes = [HasUserAPIKey]

    def get(self, request, format=None):
        """
        Return a list of all customers.
        """
        customers = Customer.objects.all()
        serializer = CustomerSerializer(customers, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        """
        Create a new customer.
        """

        serializer = CustomerSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)


class UsageView(APIView):

    permission_classes = [HasUserAPIKey]

    def get(self, request, format=None):
        """
        Return current usage for a customer during a given billing period.
        """
        customer_id = request.query_params["customer_id"]
        customer = Customer.objects.get(customer_id=customer_id)

        customer_subscriptions = Subscription.objects.filter(
            customer=customer, status="active"
        )

        usage_summary = {}

        for subscription in customer_subscriptions:

            plan = subscription.billing_plan
            plan_start_timestamp = plan.start_timestamp
            plan_end_timestamp = plan.end_timestamp
            event_name = plan.billable_metric.event_name
            aggregation_type = plan.billable_metric.aggregation_type

            events = Event.objects.filter(
                event_name=event_name,
                time_created__gte=plan_start_timestamp,
                time_created__lte=plan_end_timestamp,
            )

            subtotal_usage = 0.0
            if aggregation_type == "count":
                subtotal_usage = len(events)
            elif aggregation_type == "sum":
                property_name = plan.billable_metric.property_name
                for event in events:
                    subtotal_usage += float(event.properties[property_name])

            usage_summary[plan.name] = {
                "subtotal_usage": subtotal_usage,
                "billing_start_date": plan_start_timestamp,
                "billing_end_date": plan_end_timestamp,
            }

        usage_summary["# of Active Subscriptions"] = len(usage_summary)

        return Response(usage_summary)

billing/views/invoice_views.py METASEP

billing/tests/tests.py METASEP
from django.test import TestCase

# Create your tests here.

billing/tests/test_track.py METASEP
from django.test import TestCase
from ..models import Customer, Event
import uuid
import json
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from django.core.serializers.json import DjangoJSONEncoder
from rest_framework_api_key.models import APIKey
from billing.models import User, APIToken

from ..track import track_event


class TrackEventTest(TestCase):
    """
    Testcases for the track_event function view.
    """

    def setUp(self):

        user_object = User.objects.create_user(username="test", email="")
        Customer.objects.create(customer_id="7fa09280-957c-4a5f-925a-6a3498a1d299")
        api_key, key = APIToken.objects.create_key(name="test-api-ke", user=user_object)
        self.authorization_header = {
            "Authorization": "Api-Key" + " " + key,
        }

        self.client = APIClient()
        self.client.credentials(HTTP_AUTHORIZATION="Api-Key" + " " + key)
        idempotency_id = uuid.uuid4()

        self.valid_payload = {
            "event_name": "Emails",
            "properties": {"email": ""},
            "idempotency_id": idempotency_id,
            "time_created": "2022-07-25T01:11:42.535Z",
            "customer_id": "7fa09280-957c-4a5f-925a-6a3498a1d299",
        }
        self.invalid_payload = {
            "event_name": "Emails",
            "properties": {"email": ""},
            "idempotency_id": idempotency_id,
            "time_created": "2022-07-25T01:11:42.535Z",
            "customer_id": "7fa09280-957c-3w5f-925a-6a3498a1d299",
        }

    def test_track_event_if_customer_does_not_exist(self):
        """
        Test that track_event returns bad request with message "Customer does not exist" if the customer's customer_id does not exist in the database.
        """
        response = self.client.post(
            reverse("track_event"),
            data=json.dumps(self.invalid_payload, cls=DjangoJSONEncoder),
            content_type="application/json",
            **self.authorization_header,
        )
        self.assertEqual(response.content, b"Customer does not exist")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_valid_track_event_creates_event(self):
        """
        Test that track_event creates an event in the database.
        """
        self.assertEqual(
            Event.objects.all()
            .filter(idempotency_id=self.valid_payload["idempotency_id"])
            .count(),
            0,
        )
        response = self.client.post(
            reverse("track_event"),
            data=json.dumps(self.valid_payload, cls=DjangoJSONEncoder),
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            Event.objects.all()
            .filter(idempotency_id=self.valid_payload["idempotency_id"])
            .count(),
            1,
        )

    def test_idempotency_duplicate_does_not_create_multiple_events(self):
        """
        Test that track_event does not create multiple events in the database if the idempotency_id is duplicated.
        """
        self.assertEqual(
            Event.objects.all()
            .filter(idempotency_id=self.valid_payload["idempotency_id"])
            .count(),
            0,
        )
        response = self.client.post(
            reverse("track_event"),
            data=json.dumps(self.valid_payload, cls=DjangoJSONEncoder),
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            Event.objects.all()
            .filter(idempotency_id=self.valid_payload["idempotency_id"])
            .count(),
            1,
        )
        response = self.client.post(
            reverse("track_event"),
            data=json.dumps(self.valid_payload, cls=DjangoJSONEncoder),
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            Event.objects.all()
            .filter(idempotency_id=self.valid_payload["idempotency_id"])
            .count(),
            1,
        )

billing/tests/test_subscriptions.py METASEP
from django.test import TestCase
from ..models import Customer, Subscription, BillingPlan, BillableMetric
import uuid
import json
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from django.core.serializers.json import DjangoJSONEncoder
from rest_framework_api_key.models import APIKey
from billing.models import User, APIToken


class SubscriptionTest(TestCase):
    """
    Testcases for the subscription views.
    """

    def setUp(self):

        user_object = User.objects.create_user(username="test", email="")
        Customer.objects.create(customer_id="7fa09280-957c-4a5f-925a-6a3498a1d299")
        api_key, key = APIToken.objects.create_key(name="test-api-ke", user=user_object)
        self.authorization_header = {
            "Authorization": "Api-Key" + " " + key,
        }
        metric = BillableMetric.objects.create(
            event_name="Emails", property_name="amount", aggregation_type="count"
        )
        BillingPlan.objects.create(
            billable_metric=metric, metric_amount=1, name="Standard", plan_id="1"
        )

        self.client = APIClient()
        self.client.credentials(HTTP_AUTHORIZATION="Api-Key" + " " + key)

        self.valid_payload = {
            "plan_id": "1",
            "start_date": "2022-07-25T01:11:42.535Z",
            "customer_id": "7fa09280-957c-4a5f-925a-6a3498a1d299",
        }
        self.invalid_payload_start_date = {
            "plan_id": "1",
            "start_date": "2022-07-25T01:11:42.535Z",
            "customer_id": "7fa09280-957c-3w5f-925a-6a3498a1d299",
        }

    def test_subscription_create_success(self):
        """
        Test that subscription post view works correctly.
        """
        response = self.client.post(
            reverse("subscription"),
            data=json.dumps(self.valid_payload, cls=DjangoJSONEncoder),
            content_type="application/json",
            **self.authorization_header,
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        subscription = Subscription.objects.all().filter(billing_plan="1")
        self.assertEqual(len(subscription), 1)

billing/tests/__init__.py METASEP

billing/migrations/__init__.py METASEP

billing/migrations/0001_initial.py METASEP
# Generated by Django 4.0.6 on 2022-07-31 17:21

from decimal import Decimal
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import djmoney.models.fields
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            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"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="first name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="last name"
                    ),
                ),
                (
                    "email",
                    models.EmailField(
                        blank=True, max_length=254, verbose_name="email address"
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=200)),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "verbose_name": "user",
                "verbose_name_plural": "users",
                "abstract": False,
            },
            managers=[
                ("objects", django.contrib.auth.models.UserManager()),
            ],
        ),
        migrations.CreateModel(
            name="BillableMetric",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("event_name", models.CharField(max_length=200)),
                ("property_name", models.CharField(max_length=200, null=True)),
                (
                    "aggregation_type",
                    models.CharField(
                        choices=[("count", "Count"), ("sum", "Sum"), ("max", "Max")],
                        default="count",
                        max_length=10,
                    ),
                ),
            ],
        ),
        migrations.CreateModel(
            name="BillingPlan",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                (
                    "plan_id",
                    models.CharField(
                        blank=True, default=uuid.uuid4, max_length=36, unique=True
                    ),
                ),
                ("time_created", models.DateTimeField()),
                ("currency", models.CharField(default="USD", max_length=30)),
                (
                    "interval",
                    models.CharField(
                        choices=[
                            ("week", "Week"),
                            ("month", "Month"),
                            ("year", "Year"),
                        ],
                        default="month",
                        max_length=5,
                    ),
                ),
                (
                    "base_rate_currency",
                    djmoney.models.fields.CurrencyField(
                        choices=[
                            ("XUA", "ADB Unit of Account"),
                            ("AFN", "Afghan Afghani"),
                            ("AFA", "Afghan Afghani (1927–2002)"),
                            ("ALL", "Albanian Lek"),
                            ("ALK", "Albanian Lek (1946–1965)"),
                            ("DZD", "Algerian Dinar"),
                            ("ADP", "Andorran Peseta"),
                            ("AOA", "Angolan Kwanza"),
                            ("AOK", "Angolan Kwanza (1977–1991)"),
                            ("AON", "Angolan New Kwanza (1990–2000)"),
                            ("AOR", "Angolan Readjusted Kwanza (1995–1999)"),
                            ("ARA", "Argentine Austral"),
                            ("ARS", "Argentine Peso"),
                            ("ARM", "Argentine Peso (1881–1970)"),
                            ("ARP", "Argentine Peso (1983–1985)"),
                            ("ARL", "Argentine Peso Ley (1970–1983)"),
                            ("AMD", "Armenian Dram"),
                            ("AWG", "Aruban Florin"),
                            ("AUD", "Australian Dollar"),
                            ("ATS", "Austrian Schilling"),
                            ("AZN", "Azerbaijani Manat"),
                            ("AZM", "Azerbaijani Manat (1993–2006)"),
                            ("BSD", "Bahamian Dollar"),
                            ("BHD", "Bahraini Dinar"),
                            ("BDT", "Bangladeshi Taka"),
                            ("BBD", "Barbadian Dollar"),
                            ("BYN", "Belarusian Ruble"),
                            ("BYB", "Belarusian Ruble (1994–1999)"),
                            ("BYR", "Belarusian Ruble (2000–2016)"),
                            ("BEF", "Belgian Franc"),
                            ("BEC", "Belgian Franc (convertible)"),
                            ("BEL", "Belgian Franc (financial)"),
                            ("BZD", "Belize Dollar"),
                            ("BMD", "Bermudan Dollar"),
                            ("BTN", "Bhutanese Ngultrum"),
                            ("BOB", "Bolivian Boliviano"),
                            ("BOL", "Bolivian Boliviano (1863–1963)"),
                            ("BOV", "Bolivian Mvdol"),
                            ("BOP", "Bolivian Peso"),
                            ("BAM", "Bosnia-Herzegovina Convertible Mark"),
                            ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"),
                            ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"),
                            ("BWP", "Botswanan Pula"),
                            ("BRC", "Brazilian Cruzado (1986–1989)"),
                            ("BRZ", "Brazilian Cruzeiro (1942–1967)"),
                            ("BRE", "Brazilian Cruzeiro (1990–1993)"),
                            ("BRR", "Brazilian Cruzeiro (1993–1994)"),
                            ("BRN", "Brazilian New Cruzado (1989–1990)"),
                            ("BRB", "Brazilian New Cruzeiro (1967–1986)"),
                            ("BRL", "Brazilian Real"),
                            ("GBP", "British Pound"),
                            ("BND", "Brunei Dollar"),
                            ("BGL", "Bulgarian Hard Lev"),
                            ("BGN", "Bulgarian Lev"),
                            ("BGO", "Bulgarian Lev (1879–1952)"),
                            ("BGM", "Bulgarian Socialist Lev"),
                            ("BUK", "Burmese Kyat"),
                            ("BIF", "Burundian Franc"),
                            ("XPF", "CFP Franc"),
                            ("KHR", "Cambodian Riel"),
                            ("CAD", "Canadian Dollar"),
                            ("CVE", "Cape Verdean Escudo"),
                            ("KYD", "Cayman Islands Dollar"),
                            ("XAF", "Central African CFA Franc"),
                            ("CLE", "Chilean Escudo"),
                            ("CLP", "Chilean Peso"),
                            ("CLF", "Chilean Unit of Account (UF)"),
                            ("CNX", "Chinese People’s Bank Dollar"),
                            ("CNY", "Chinese Yuan"),
                            ("CNH", "Chinese Yuan (offshore)"),
                            ("COP", "Colombian Peso"),
                            ("COU", "Colombian Real Value Unit"),
                            ("KMF", "Comorian Franc"),
                            ("CDF", "Congolese Franc"),
                            ("CRC", "Costa Rican Colón"),
                            ("HRD", "Croatian Dinar"),
                            ("HRK", "Croatian Kuna"),
                            ("CUC", "Cuban Convertible Peso"),
                            ("CUP", "Cuban Peso"),
                            ("CYP", "Cypriot Pound"),
                            ("CZK", "Czech Koruna"),
                            ("CSK", "Czechoslovak Hard Koruna"),
                            ("DKK", "Danish Krone"),
                            ("DJF", "Djiboutian Franc"),
                            ("DOP", "Dominican Peso"),
                            ("NLG", "Dutch Guilder"),
                            ("XCD", "East Caribbean Dollar"),
                            ("DDM", "East German Mark"),
                            ("ECS", "Ecuadorian Sucre"),
                            ("ECV", "Ecuadorian Unit of Constant Value"),
                            ("EGP", "Egyptian Pound"),
                            ("GQE", "Equatorial Guinean Ekwele"),
                            ("ERN", "Eritrean Nakfa"),
                            ("EEK", "Estonian Kroon"),
                            ("ETB", "Ethiopian Birr"),
                            ("EUR", "Euro"),
                            ("XBA", "European Composite Unit"),
                            ("XEU", "European Currency Unit"),
                            ("XBB", "European Monetary Unit"),
                            ("XBC", "European Unit of Account (XBC)"),
                            ("XBD", "European Unit of Account (XBD)"),
                            ("FKP", "Falkland Islands Pound"),
                            ("FJD", "Fijian Dollar"),
                            ("FIM", "Finnish Markka"),
                            ("FRF", "French Franc"),
                            ("XFO", "French Gold Franc"),
                            ("XFU", "French UIC-Franc"),
                            ("GMD", "Gambian Dalasi"),
                            ("GEK", "Georgian Kupon Larit"),
                            ("GEL", "Georgian Lari"),
                            ("DEM", "German Mark"),
                            ("GHS", "Ghanaian Cedi"),
                            ("GHC", "Ghanaian Cedi (1979–2007)"),
                            ("GIP", "Gibraltar Pound"),
                            ("XAU", "Gold"),
                            ("GRD", "Greek Drachma"),
                            ("GTQ", "Guatemalan Quetzal"),
                            ("GWP", "Guinea-Bissau Peso"),
                            ("GNF", "Guinean Franc"),
                            ("GNS", "Guinean Syli"),
                            ("GYD", "Guyanaese Dollar"),
                            ("HTG", "Haitian Gourde"),
                            ("HNL", "Honduran Lempira"),
                            ("HKD", "Hong Kong Dollar"),
                            ("HUF", "Hungarian Forint"),
                            ("IMP", "IMP"),
                            ("ISK", "Icelandic Króna"),
                            ("ISJ", "Icelandic Króna (1918–1981)"),
                            ("INR", "Indian Rupee"),
                            ("IDR", "Indonesian Rupiah"),
                            ("IRR", "Iranian Rial"),
                            ("IQD", "Iraqi Dinar"),
                            ("IEP", "Irish Pound"),
                            ("ILS", "Israeli New Shekel"),
                            ("ILP", "Israeli Pound"),
                            ("ILR", "Israeli Shekel (1980–1985)"),
                            ("ITL", "Italian Lira"),
                            ("JMD", "Jamaican Dollar"),
                            ("JPY", "Japanese Yen"),
                            ("JOD", "Jordanian Dinar"),
                            ("KZT", "Kazakhstani Tenge"),
                            ("KES", "Kenyan Shilling"),
                            ("KWD", "Kuwaiti Dinar"),
                            ("KGS", "Kyrgystani Som"),
                            ("LAK", "Laotian Kip"),
                            ("LVL", "Latvian Lats"),
                            ("LVR", "Latvian Ruble"),
                            ("LBP", "Lebanese Pound"),
                            ("LSL", "Lesotho Loti"),
                            ("LRD", "Liberian Dollar"),
                            ("LYD", "Libyan Dinar"),
                            ("LTL", "Lithuanian Litas"),
                            ("LTT", "Lithuanian Talonas"),
                            ("LUL", "Luxembourg Financial Franc"),
                            ("LUC", "Luxembourgian Convertible Franc"),
                            ("LUF", "Luxembourgian Franc"),
                            ("MOP", "Macanese Pataca"),
                            ("MKD", "Macedonian Denar"),
                            ("MKN", "Macedonian Denar (1992–1993)"),
                            ("MGA", "Malagasy Ariary"),
                            ("MGF", "Malagasy Franc"),
                            ("MWK", "Malawian Kwacha"),
                            ("MYR", "Malaysian Ringgit"),
                            ("MVR", "Maldivian Rufiyaa"),
                            ("MVP", "Maldivian Rupee (1947–1981)"),
                            ("MLF", "Malian Franc"),
                            ("MTL", "Maltese Lira"),
                            ("MTP", "Maltese Pound"),
                            ("MRU", "Mauritanian Ouguiya"),
                            ("MRO", "Mauritanian Ouguiya (1973–2017)"),
                            ("MUR", "Mauritian Rupee"),
                            ("MXV", "Mexican Investment Unit"),
                            ("MXN", "Mexican Peso"),
                            ("MXP", "Mexican Silver Peso (1861–1992)"),
                            ("MDC", "Moldovan Cupon"),
                            ("MDL", "Moldovan Leu"),
                            ("MCF", "Monegasque Franc"),
                            ("MNT", "Mongolian Tugrik"),
                            ("MAD", "Moroccan Dirham"),
                            ("MAF", "Moroccan Franc"),
                            ("MZE", "Mozambican Escudo"),
                            ("MZN", "Mozambican Metical"),
                            ("MZM", "Mozambican Metical (1980–2006)"),
                            ("MMK", "Myanmar Kyat"),
                            ("NAD", "Namibian Dollar"),
                            ("NPR", "Nepalese Rupee"),
                            ("ANG", "Netherlands Antillean Guilder"),
                            ("TWD", "New Taiwan Dollar"),
                            ("NZD", "New Zealand Dollar"),
                            ("NIO", "Nicaraguan Córdoba"),
                            ("NIC", "Nicaraguan Córdoba (1988–1991)"),
                            ("NGN", "Nigerian Naira"),
                            ("KPW", "North Korean Won"),
                            ("NOK", "Norwegian Krone"),
                            ("OMR", "Omani Rial"),
                            ("PKR", "Pakistani Rupee"),
                            ("XPD", "Palladium"),
                            ("PAB", "Panamanian Balboa"),
                            ("PGK", "Papua New Guinean Kina"),
                            ("PYG", "Paraguayan Guarani"),
                            ("PEI", "Peruvian Inti"),
                            ("PEN", "Peruvian Sol"),
                            ("PES", "Peruvian Sol (1863–1965)"),
                            ("PHP", "Philippine Peso"),
                            ("XPT", "Platinum"),
                            ("PLN", "Polish Zloty"),
                            ("PLZ", "Polish Zloty (1950–1995)"),
                            ("PTE", "Portuguese Escudo"),
                            ("GWE", "Portuguese Guinea Escudo"),
                            ("QAR", "Qatari Rial"),
                            ("XRE", "RINET Funds"),
                            ("RHD", "Rhodesian Dollar"),
                            ("RON", "Romanian Leu"),
                            ("ROL", "Romanian Leu (1952–2006)"),
                            ("RUB", "Russian Ruble"),
                            ("RUR", "Russian Ruble (1991–1998)"),
                            ("RWF", "Rwandan Franc"),
                            ("SVC", "Salvadoran Colón"),
                            ("WST", "Samoan Tala"),
                            ("SAR", "Saudi Riyal"),
                            ("RSD", "Serbian Dinar"),
                            ("CSD", "Serbian Dinar (2002–2006)"),
                            ("SCR", "Seychellois Rupee"),
                            ("SLL", "Sierra Leonean Leone"),
                            ("XAG", "Silver"),
                            ("SGD", "Singapore Dollar"),
                            ("SKK", "Slovak Koruna"),
                            ("SIT", "Slovenian Tolar"),
                            ("SBD", "Solomon Islands Dollar"),
                            ("SOS", "Somali Shilling"),
                            ("ZAR", "South African Rand"),
                            ("ZAL", "South African Rand (financial)"),
                            ("KRH", "South Korean Hwan (1953–1962)"),
                            ("KRW", "South Korean Won"),
                            ("KRO", "South Korean Won (1945–1953)"),
                            ("SSP", "South Sudanese Pound"),
                            ("SUR", "Soviet Rouble"),
                            ("ESP", "Spanish Peseta"),
                            ("ESA", "Spanish Peseta (A account)"),
                            ("ESB", "Spanish Peseta (convertible account)"),
                            ("XDR", "Special Drawing Rights"),
                            ("LKR", "Sri Lankan Rupee"),
                            ("SHP", "St. Helena Pound"),
                            ("XSU", "Sucre"),
                            ("SDD", "Sudanese Dinar (1992–2007)"),
                            ("SDG", "Sudanese Pound"),
                            ("SDP", "Sudanese Pound (1957–1998)"),
                            ("SRD", "Surinamese Dollar"),
                            ("SRG", "Surinamese Guilder"),
                            ("SZL", "Swazi Lilangeni"),
                            ("SEK", "Swedish Krona"),
                            ("CHF", "Swiss Franc"),
                            ("SYP", "Syrian Pound"),
                            ("STN", "São Tomé & Príncipe Dobra"),
                            ("STD", "São Tomé & Príncipe Dobra (1977–2017)"),
                            ("TVD", "TVD"),
                            ("TJR", "Tajikistani Ruble"),
                            ("TJS", "Tajikistani Somoni"),
                            ("TZS", "Tanzanian Shilling"),
                            ("XTS", "Testing Currency Code"),
                            ("THB", "Thai Baht"),
                            (
                                "XXX",
                                "The codes assigned for transactions where no currency is involved",
                            ),
                            ("TPE", "Timorese Escudo"),
                            ("TOP", "Tongan Paʻanga"),
                            ("TTD", "Trinidad & Tobago Dollar"),
                            ("TND", "Tunisian Dinar"),
                            ("TRY", "Turkish Lira"),
                            ("TRL", "Turkish Lira (1922–2005)"),
                            ("TMT", "Turkmenistani Manat"),
                            ("TMM", "Turkmenistani Manat (1993–2009)"),
                            ("USD", "US Dollar"),
                            ("USN", "US Dollar (Next day)"),
                            ("USS", "US Dollar (Same day)"),
                            ("UGX", "Ugandan Shilling"),
                            ("UGS", "Ugandan Shilling (1966–1987)"),
                            ("UAH", "Ukrainian Hryvnia"),
                            ("UAK", "Ukrainian Karbovanets"),
                            ("AED", "United Arab Emirates Dirham"),
                            ("UYW", "Uruguayan Nominal Wage Index Unit"),
                            ("UYU", "Uruguayan Peso"),
                            ("UYP", "Uruguayan Peso (1975–1993)"),
                            ("UYI", "Uruguayan Peso (Indexed Units)"),
                            ("UZS", "Uzbekistani Som"),
                            ("VUV", "Vanuatu Vatu"),
                            ("VES", "Venezuelan Bolívar"),
                            ("VEB", "Venezuelan Bolívar (1871–2008)"),
                            ("VEF", "Venezuelan Bolívar (2008–2018)"),
                            ("VND", "Vietnamese Dong"),
                            ("VNN", "Vietnamese Dong (1978–1985)"),
                            ("CHE", "WIR Euro"),
                            ("CHW", "WIR Franc"),
                            ("XOF", "West African CFA Franc"),
                            ("YDD", "Yemeni Dinar"),
                            ("YER", "Yemeni Rial"),
                            ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"),
                            ("YUD", "Yugoslavian Hard Dinar (1966–1990)"),
                            ("YUM", "Yugoslavian New Dinar (1994–2002)"),
                            ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"),
                            ("ZWN", "ZWN"),
                            ("ZRN", "Zairean New Zaire (1993–1998)"),
                            ("ZRZ", "Zairean Zaire (1971–1993)"),
                            ("ZMW", "Zambian Kwacha"),
                            ("ZMK", "Zambian Kwacha (1968–2012)"),
                            ("ZWD", "Zimbabwean Dollar (1980–2008)"),
                            ("ZWR", "Zimbabwean Dollar (2008)"),
                            ("ZWL", "Zimbabwean Dollar (2009)"),
                        ],
                        default="USD",
                        editable=False,
                        max_length=3,
                    ),
                ),
                (
                    "base_rate",
                    djmoney.models.fields.MoneyField(
                        decimal_places=2,
                        default=Decimal("0.0"),
                        default_currency="USD",
                        max_digits=8,
                    ),
                ),
                ("pay_in_advance", models.BooleanField(default=False)),
                (
                    "starter_metric_quatity",
                    models.IntegerField(blank=True, default=0, null=True),
                ),
                (
                    "metric_amount_currency",
                    djmoney.models.fields.CurrencyField(
                        choices=[
                            ("XUA", "ADB Unit of Account"),
                            ("AFN", "Afghan Afghani"),
                            ("AFA", "Afghan Afghani (1927–2002)"),
                            ("ALL", "Albanian Lek"),
                            ("ALK", "Albanian Lek (1946–1965)"),
                            ("DZD", "Algerian Dinar"),
                            ("ADP", "Andorran Peseta"),
                            ("AOA", "Angolan Kwanza"),
                            ("AOK", "Angolan Kwanza (1977–1991)"),
                            ("AON", "Angolan New Kwanza (1990–2000)"),
                            ("AOR", "Angolan Readjusted Kwanza (1995–1999)"),
                            ("ARA", "Argentine Austral"),
                            ("ARS", "Argentine Peso"),
                            ("ARM", "Argentine Peso (1881–1970)"),
                            ("ARP", "Argentine Peso (1983–1985)"),
                            ("ARL", "Argentine Peso Ley (1970–1983)"),
                            ("AMD", "Armenian Dram"),
                            ("AWG", "Aruban Florin"),
                            ("AUD", "Australian Dollar"),
                            ("ATS", "Austrian Schilling"),
                            ("AZN", "Azerbaijani Manat"),
                            ("AZM", "Azerbaijani Manat (1993–2006)"),
                            ("BSD", "Bahamian Dollar"),
                            ("BHD", "Bahraini Dinar"),
                            ("BDT", "Bangladeshi Taka"),
                            ("BBD", "Barbadian Dollar"),
                            ("BYN", "Belarusian Ruble"),
                            ("BYB", "Belarusian Ruble (1994–1999)"),
                            ("BYR", "Belarusian Ruble (2000–2016)"),
                            ("BEF", "Belgian Franc"),
                            ("BEC", "Belgian Franc (convertible)"),
                            ("BEL", "Belgian Franc (financial)"),
                            ("BZD", "Belize Dollar"),
                            ("BMD", "Bermudan Dollar"),
                            ("BTN", "Bhutanese Ngultrum"),
                            ("BOB", "Bolivian Boliviano"),
                            ("BOL", "Bolivian Boliviano (1863–1963)"),
                            ("BOV", "Bolivian Mvdol"),
                            ("BOP", "Bolivian Peso"),
                            ("BAM", "Bosnia-Herzegovina Convertible Mark"),
                            ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"),
                            ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"),
                            ("BWP", "Botswanan Pula"),
                            ("BRC", "Brazilian Cruzado (1986–1989)"),
                            ("BRZ", "Brazilian Cruzeiro (1942–1967)"),
                            ("BRE", "Brazilian Cruzeiro (1990–1993)"),
                            ("BRR", "Brazilian Cruzeiro (1993–1994)"),
                            ("BRN", "Brazilian New Cruzado (1989–1990)"),
                            ("BRB", "Brazilian New Cruzeiro (1967–1986)"),
                            ("BRL", "Brazilian Real"),
                            ("GBP", "British Pound"),
                            ("BND", "Brunei Dollar"),
                            ("BGL", "Bulgarian Hard Lev"),
                            ("BGN", "Bulgarian Lev"),
                            ("BGO", "Bulgarian Lev (1879–1952)"),
                            ("BGM", "Bulgarian Socialist Lev"),
                            ("BUK", "Burmese Kyat"),
                            ("BIF", "Burundian Franc"),
                            ("XPF", "CFP Franc"),
                            ("KHR", "Cambodian Riel"),
                            ("CAD", "Canadian Dollar"),
                            ("CVE", "Cape Verdean Escudo"),
                            ("KYD", "Cayman Islands Dollar"),
                            ("XAF", "Central African CFA Franc"),
                            ("CLE", "Chilean Escudo"),
                            ("CLP", "Chilean Peso"),
                            ("CLF", "Chilean Unit of Account (UF)"),
                            ("CNX", "Chinese People’s Bank Dollar"),
                            ("CNY", "Chinese Yuan"),
                            ("CNH", "Chinese Yuan (offshore)"),
                            ("COP", "Colombian Peso"),
                            ("COU", "Colombian Real Value Unit"),
                            ("KMF", "Comorian Franc"),
                            ("CDF", "Congolese Franc"),
                            ("CRC", "Costa Rican Colón"),
                            ("HRD", "Croatian Dinar"),
                            ("HRK", "Croatian Kuna"),
                            ("CUC", "Cuban Convertible Peso"),
                            ("CUP", "Cuban Peso"),
                            ("CYP", "Cypriot Pound"),
                            ("CZK", "Czech Koruna"),
                            ("CSK", "Czechoslovak Hard Koruna"),
                            ("DKK", "Danish Krone"),
                            ("DJF", "Djiboutian Franc"),
                            ("DOP", "Dominican Peso"),
                            ("NLG", "Dutch Guilder"),
                            ("XCD", "East Caribbean Dollar"),
                            ("DDM", "East German Mark"),
                            ("ECS", "Ecuadorian Sucre"),
                            ("ECV", "Ecuadorian Unit of Constant Value"),
                            ("EGP", "Egyptian Pound"),
                            ("GQE", "Equatorial Guinean Ekwele"),
                            ("ERN", "Eritrean Nakfa"),
                            ("EEK", "Estonian Kroon"),
                            ("ETB", "Ethiopian Birr"),
                            ("EUR", "Euro"),
                            ("XBA", "European Composite Unit"),
                            ("XEU", "European Currency Unit"),
                            ("XBB", "European Monetary Unit"),
                            ("XBC", "European Unit of Account (XBC)"),
                            ("XBD", "European Unit of Account (XBD)"),
                            ("FKP", "Falkland Islands Pound"),
                            ("FJD", "Fijian Dollar"),
                            ("FIM", "Finnish Markka"),
                            ("FRF", "French Franc"),
                            ("XFO", "French Gold Franc"),
                            ("XFU", "French UIC-Franc"),
                            ("GMD", "Gambian Dalasi"),
                            ("GEK", "Georgian Kupon Larit"),
                            ("GEL", "Georgian Lari"),
                            ("DEM", "German Mark"),
                            ("GHS", "Ghanaian Cedi"),
                            ("GHC", "Ghanaian Cedi (1979–2007)"),
                            ("GIP", "Gibraltar Pound"),
                            ("XAU", "Gold"),
                            ("GRD", "Greek Drachma"),
                            ("GTQ", "Guatemalan Quetzal"),
                            ("GWP", "Guinea-Bissau Peso"),
                            ("GNF", "Guinean Franc"),
                            ("GNS", "Guinean Syli"),
                            ("GYD", "Guyanaese Dollar"),
                            ("HTG", "Haitian Gourde"),
                            ("HNL", "Honduran Lempira"),
                            ("HKD", "Hong Kong Dollar"),
                            ("HUF", "Hungarian Forint"),
                            ("IMP", "IMP"),
                            ("ISK", "Icelandic Króna"),
                            ("ISJ", "Icelandic Króna (1918–1981)"),
                            ("INR", "Indian Rupee"),
                            ("IDR", "Indonesian Rupiah"),
                            ("IRR", "Iranian Rial"),
                            ("IQD", "Iraqi Dinar"),
                            ("IEP", "Irish Pound"),
                            ("ILS", "Israeli New Shekel"),
                            ("ILP", "Israeli Pound"),
                            ("ILR", "Israeli Shekel (1980–1985)"),
                            ("ITL", "Italian Lira"),
                            ("JMD", "Jamaican Dollar"),
                            ("JPY", "Japanese Yen"),
                            ("JOD", "Jordanian Dinar"),
                            ("KZT", "Kazakhstani Tenge"),
                            ("KES", "Kenyan Shilling"),
                            ("KWD", "Kuwaiti Dinar"),
                            ("KGS", "Kyrgystani Som"),
                            ("LAK", "Laotian Kip"),
                            ("LVL", "Latvian Lats"),
                            ("LVR", "Latvian Ruble"),
                            ("LBP", "Lebanese Pound"),
                            ("LSL", "Lesotho Loti"),
                            ("LRD", "Liberian Dollar"),
                            ("LYD", "Libyan Dinar"),
                            ("LTL", "Lithuanian Litas"),
                            ("LTT", "Lithuanian Talonas"),
                            ("LUL", "Luxembourg Financial Franc"),
                            ("LUC", "Luxembourgian Convertible Franc"),
                            ("LUF", "Luxembourgian Franc"),
                            ("MOP", "Macanese Pataca"),
                            ("MKD", "Macedonian Denar"),
                            ("MKN", "Macedonian Denar (1992–1993)"),
                            ("MGA", "Malagasy Ariary"),
                            ("MGF", "Malagasy Franc"),
                            ("MWK", "Malawian Kwacha"),
                            ("MYR", "Malaysian Ringgit"),
                            ("MVR", "Maldivian Rufiyaa"),
                            ("MVP", "Maldivian Rupee (1947–1981)"),
                            ("MLF", "Malian Franc"),
                            ("MTL", "Maltese Lira"),
                            ("MTP", "Maltese Pound"),
                            ("MRU", "Mauritanian Ouguiya"),
                            ("MRO", "Mauritanian Ouguiya (1973–2017)"),
                            ("MUR", "Mauritian Rupee"),
                            ("MXV", "Mexican Investment Unit"),
                            ("MXN", "Mexican Peso"),
                            ("MXP", "Mexican Silver Peso (1861–1992)"),
                            ("MDC", "Moldovan Cupon"),
                            ("MDL", "Moldovan Leu"),
                            ("MCF", "Monegasque Franc"),
                            ("MNT", "Mongolian Tugrik"),
                            ("MAD", "Moroccan Dirham"),
                            ("MAF", "Moroccan Franc"),
                            ("MZE", "Mozambican Escudo"),
                            ("MZN", "Mozambican Metical"),
                            ("MZM", "Mozambican Metical (1980–2006)"),
                            ("MMK", "Myanmar Kyat"),
                            ("NAD", "Namibian Dollar"),
                            ("NPR", "Nepalese Rupee"),
                            ("ANG", "Netherlands Antillean Guilder"),
                            ("TWD", "New Taiwan Dollar"),
                            ("NZD", "New Zealand Dollar"),
                            ("NIO", "Nicaraguan Córdoba"),
                            ("NIC", "Nicaraguan Córdoba (1988–1991)"),
                            ("NGN", "Nigerian Naira"),
                            ("KPW", "North Korean Won"),
                            ("NOK", "Norwegian Krone"),
                            ("OMR", "Omani Rial"),
                            ("PKR", "Pakistani Rupee"),
                            ("XPD", "Palladium"),
                            ("PAB", "Panamanian Balboa"),
                            ("PGK", "Papua New Guinean Kina"),
                            ("PYG", "Paraguayan Guarani"),
                            ("PEI", "Peruvian Inti"),
                            ("PEN", "Peruvian Sol"),
                            ("PES", "Peruvian Sol (1863–1965)"),
                            ("PHP", "Philippine Peso"),
                            ("XPT", "Platinum"),
                            ("PLN", "Polish Zloty"),
                            ("PLZ", "Polish Zloty (1950–1995)"),
                            ("PTE", "Portuguese Escudo"),
                            ("GWE", "Portuguese Guinea Escudo"),
                            ("QAR", "Qatari Rial"),
                            ("XRE", "RINET Funds"),
                            ("RHD", "Rhodesian Dollar"),
                            ("RON", "Romanian Leu"),
                            ("ROL", "Romanian Leu (1952–2006)"),
                            ("RUB", "Russian Ruble"),
                            ("RUR", "Russian Ruble (1991–1998)"),
                            ("RWF", "Rwandan Franc"),
                            ("SVC", "Salvadoran Colón"),
                            ("WST", "Samoan Tala"),
                            ("SAR", "Saudi Riyal"),
                            ("RSD", "Serbian Dinar"),
                            ("CSD", "Serbian Dinar (2002–2006)"),
                            ("SCR", "Seychellois Rupee"),
                            ("SLL", "Sierra Leonean Leone"),
                            ("XAG", "Silver"),
                            ("SGD", "Singapore Dollar"),
                            ("SKK", "Slovak Koruna"),
                            ("SIT", "Slovenian Tolar"),
                            ("SBD", "Solomon Islands Dollar"),
                            ("SOS", "Somali Shilling"),
                            ("ZAR", "South African Rand"),
                            ("ZAL", "South African Rand (financial)"),
                            ("KRH", "South Korean Hwan (1953–1962)"),
                            ("KRW", "South Korean Won"),
                            ("KRO", "South Korean Won (1945–1953)"),
                            ("SSP", "South Sudanese Pound"),
                            ("SUR", "Soviet Rouble"),
                            ("ESP", "Spanish Peseta"),
                            ("ESA", "Spanish Peseta (A account)"),
                            ("ESB", "Spanish Peseta (convertible account)"),
                            ("XDR", "Special Drawing Rights"),
                            ("LKR", "Sri Lankan Rupee"),
                            ("SHP", "St. Helena Pound"),
                            ("XSU", "Sucre"),
                            ("SDD", "Sudanese Dinar (1992–2007)"),
                            ("SDG", "Sudanese Pound"),
                            ("SDP", "Sudanese Pound (1957–1998)"),
                            ("SRD", "Surinamese Dollar"),
                            ("SRG", "Surinamese Guilder"),
                            ("SZL", "Swazi Lilangeni"),
                            ("SEK", "Swedish Krona"),
                            ("CHF", "Swiss Franc"),
                            ("SYP", "Syrian Pound"),
                            ("STN", "São Tomé & Príncipe Dobra"),
                            ("STD", "São Tomé & Príncipe Dobra (1977–2017)"),
                            ("TVD", "TVD"),
                            ("TJR", "Tajikistani Ruble"),
                            ("TJS", "Tajikistani Somoni"),
                            ("TZS", "Tanzanian Shilling"),
                            ("XTS", "Testing Currency Code"),
                            ("THB", "Thai Baht"),
                            (
                                "XXX",
                                "The codes assigned for transactions where no currency is involved",
                            ),
                            ("TPE", "Timorese Escudo"),
                            ("TOP", "Tongan Paʻanga"),
                            ("TTD", "Trinidad & Tobago Dollar"),
                            ("TND", "Tunisian Dinar"),
                            ("TRY", "Turkish Lira"),
                            ("TRL", "Turkish Lira (1922–2005)"),
                            ("TMT", "Turkmenistani Manat"),
                            ("TMM", "Turkmenistani Manat (1993–2009)"),
                            ("USD", "US Dollar"),
                            ("USN", "US Dollar (Next day)"),
                            ("USS", "US Dollar (Same day)"),
                            ("UGX", "Ugandan Shilling"),
                            ("UGS", "Ugandan Shilling (1966–1987)"),
                            ("UAH", "Ukrainian Hryvnia"),
                            ("UAK", "Ukrainian Karbovanets"),
                            ("AED", "United Arab Emirates Dirham"),
                            ("UYW", "Uruguayan Nominal Wage Index Unit"),
                            ("UYU", "Uruguayan Peso"),
                            ("UYP", "Uruguayan Peso (1975–1993)"),
                            ("UYI", "Uruguayan Peso (Indexed Units)"),
                            ("UZS", "Uzbekistani Som"),
                            ("VUV", "Vanuatu Vatu"),
                            ("VES", "Venezuelan Bolívar"),
                            ("VEB", "Venezuelan Bolívar (1871–2008)"),
                            ("VEF", "Venezuelan Bolívar (2008–2018)"),
                            ("VND", "Vietnamese Dong"),
                            ("VNN", "Vietnamese Dong (1978–1985)"),
                            ("CHE", "WIR Euro"),
                            ("CHW", "WIR Franc"),
                            ("XOF", "West African CFA Franc"),
                            ("YDD", "Yemeni Dinar"),
                            ("YER", "Yemeni Rial"),
                            ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"),
                            ("YUD", "Yugoslavian Hard Dinar (1966–1990)"),
                            ("YUM", "Yugoslavian New Dinar (1994–2002)"),
                            ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"),
                            ("ZWN", "ZWN"),
                            ("ZRN", "Zairean New Zaire (1993–1998)"),
                            ("ZRZ", "Zairean Zaire (1971–1993)"),
                            ("ZMW", "Zambian Kwacha"),
                            ("ZMK", "Zambian Kwacha (1968–2012)"),
                            ("ZWD", "Zimbabwean Dollar (1980–2008)"),
                            ("ZWR", "Zimbabwean Dollar (2008)"),
                            ("ZWL", "Zimbabwean Dollar (2009)"),
                        ],
                        default="USD",
                        editable=False,
                        max_length=3,
                        null=True,
                    ),
                ),
                (
                    "metric_amount",
                    djmoney.models.fields.MoneyField(
                        blank=True,
                        decimal_places=10,
                        default_currency="USD",
                        max_digits=14,
                        null=True,
                    ),
                ),
                ("name", models.CharField(default=" ", max_length=200)),
                (
                    "description",
                    models.CharField(blank=True, default=" ", max_length=256),
                ),
                (
                    "billable_metric",
                    models.ForeignKey(
                        blank=True,
                        null=True,
                        on_delete=django.db.models.deletion.CASCADE,
                        to="billing.billablemetric",
                    ),
                ),
            ],
        ),
        migrations.CreateModel(
            name="Customer",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("name", models.CharField(max_length=100)),
                ("company_name", models.CharField(default=" ", max_length=30)),
                ("customer_id", models.CharField(max_length=40, unique=True)),
                ("billing_id", models.CharField(default=uuid.uuid4, max_length=40)),
                ("billing_configuration", models.JSONField(blank=True, default=dict)),
                (
                    "balance_currency",
                    djmoney.models.fields.CurrencyField(
                        choices=[
                            ("XUA", "ADB Unit of Account"),
                            ("AFN", "Afghan Afghani"),
                            ("AFA", "Afghan Afghani (1927–2002)"),
                            ("ALL", "Albanian Lek"),
                            ("ALK", "Albanian Lek (1946–1965)"),
                            ("DZD", "Algerian Dinar"),
                            ("ADP", "Andorran Peseta"),
                            ("AOA", "Angolan Kwanza"),
                            ("AOK", "Angolan Kwanza (1977–1991)"),
                            ("AON", "Angolan New Kwanza (1990–2000)"),
                            ("AOR", "Angolan Readjusted Kwanza (1995–1999)"),
                            ("ARA", "Argentine Austral"),
                            ("ARS", "Argentine Peso"),
                            ("ARM", "Argentine Peso (1881–1970)"),
                            ("ARP", "Argentine Peso (1983–1985)"),
                            ("ARL", "Argentine Peso Ley (1970–1983)"),
                            ("AMD", "Armenian Dram"),
                            ("AWG", "Aruban Florin"),
                            ("AUD", "Australian Dollar"),
                            ("ATS", "Austrian Schilling"),
                            ("AZN", "Azerbaijani Manat"),
                            ("AZM", "Azerbaijani Manat (1993–2006)"),
                            ("BSD", "Bahamian Dollar"),
                            ("BHD", "Bahraini Dinar"),
                            ("BDT", "Bangladeshi Taka"),
                            ("BBD", "Barbadian Dollar"),
                            ("BYN", "Belarusian Ruble"),
                            ("BYB", "Belarusian Ruble (1994–1999)"),
                            ("BYR", "Belarusian Ruble (2000–2016)"),
                            ("BEF", "Belgian Franc"),
                            ("BEC", "Belgian Franc (convertible)"),
                            ("BEL", "Belgian Franc (financial)"),
                            ("BZD", "Belize Dollar"),
                            ("BMD", "Bermudan Dollar"),
                            ("BTN", "Bhutanese Ngultrum"),
                            ("BOB", "Bolivian Boliviano"),
                            ("BOL", "Bolivian Boliviano (1863–1963)"),
                            ("BOV", "Bolivian Mvdol"),
                            ("BOP", "Bolivian Peso"),
                            ("BAM", "Bosnia-Herzegovina Convertible Mark"),
                            ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"),
                            ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"),
                            ("BWP", "Botswanan Pula"),
                            ("BRC", "Brazilian Cruzado (1986–1989)"),
                            ("BRZ", "Brazilian Cruzeiro (1942–1967)"),
                            ("BRE", "Brazilian Cruzeiro (1990–1993)"),
                            ("BRR", "Brazilian Cruzeiro (1993–1994)"),
                            ("BRN", "Brazilian New Cruzado (1989–1990)"),
                            ("BRB", "Brazilian New Cruzeiro (1967–1986)"),
                            ("BRL", "Brazilian Real"),
                            ("GBP", "British Pound"),
                            ("BND", "Brunei Dollar"),
                            ("BGL", "Bulgarian Hard Lev"),
                            ("BGN", "Bulgarian Lev"),
                            ("BGO", "Bulgarian Lev (1879–1952)"),
                            ("BGM", "Bulgarian Socialist Lev"),
                            ("BUK", "Burmese Kyat"),
                            ("BIF", "Burundian Franc"),
                            ("XPF", "CFP Franc"),
                            ("KHR", "Cambodian Riel"),
                            ("CAD", "Canadian Dollar"),
                            ("CVE", "Cape Verdean Escudo"),
                            ("KYD", "Cayman Islands Dollar"),
                            ("XAF", "Central African CFA Franc"),
                            ("CLE", "Chilean Escudo"),
                            ("CLP", "Chilean Peso"),
                            ("CLF", "Chilean Unit of Account (UF)"),
                            ("CNX", "Chinese People’s Bank Dollar"),
                            ("CNY", "Chinese Yuan"),
                            ("CNH", "Chinese Yuan (offshore)"),
                            ("COP", "Colombian Peso"),
                            ("COU", "Colombian Real Value Unit"),
                            ("KMF", "Comorian Franc"),
                            ("CDF", "Congolese Franc"),
                            ("CRC", "Costa Rican Colón"),
                            ("HRD", "Croatian Dinar"),
                            ("HRK", "Croatian Kuna"),
                            ("CUC", "Cuban Convertible Peso"),
                            ("CUP", "Cuban Peso"),
                            ("CYP", "Cypriot Pound"),
                            ("CZK", "Czech Koruna"),
                            ("CSK", "Czechoslovak Hard Koruna"),
                            ("DKK", "Danish Krone"),
                            ("DJF", "Djiboutian Franc"),
                            ("DOP", "Dominican Peso"),
                            ("NLG", "Dutch Guilder"),
                            ("XCD", "East Caribbean Dollar"),
                            ("DDM", "East German Mark"),
                            ("ECS", "Ecuadorian Sucre"),
                            ("ECV", "Ecuadorian Unit of Constant Value"),
                            ("EGP", "Egyptian Pound"),
                            ("GQE", "Equatorial Guinean Ekwele"),
                            ("ERN", "Eritrean Nakfa"),
                            ("EEK", "Estonian Kroon"),
                            ("ETB", "Ethiopian Birr"),
                            ("EUR", "Euro"),
                            ("XBA", "European Composite Unit"),
                            ("XEU", "European Currency Unit"),
                            ("XBB", "European Monetary Unit"),
                            ("XBC", "European Unit of Account (XBC)"),
                            ("XBD", "European Unit of Account (XBD)"),
                            ("FKP", "Falkland Islands Pound"),
                            ("FJD", "Fijian Dollar"),
                            ("FIM", "Finnish Markka"),
                            ("FRF", "French Franc"),
                            ("XFO", "French Gold Franc"),
                            ("XFU", "French UIC-Franc"),
                            ("GMD", "Gambian Dalasi"),
                            ("GEK", "Georgian Kupon Larit"),
                            ("GEL", "Georgian Lari"),
                            ("DEM", "German Mark"),
                            ("GHS", "Ghanaian Cedi"),
                            ("GHC", "Ghanaian Cedi (1979–2007)"),
                            ("GIP", "Gibraltar Pound"),
                            ("XAU", "Gold"),
                            ("GRD", "Greek Drachma"),
                            ("GTQ", "Guatemalan Quetzal"),
                            ("GWP", "Guinea-Bissau Peso"),
                            ("GNF", "Guinean Franc"),
                            ("GNS", "Guinean Syli"),
                            ("GYD", "Guyanaese Dollar"),
                            ("HTG", "Haitian Gourde"),
                            ("HNL", "Honduran Lempira"),
                            ("HKD", "Hong Kong Dollar"),
                            ("HUF", "Hungarian Forint"),
                            ("IMP", "IMP"),
                            ("ISK", "Icelandic Króna"),
                            ("ISJ", "Icelandic Króna (1918–1981)"),
                            ("INR", "Indian Rupee"),
                            ("IDR", "Indonesian Rupiah"),
                            ("IRR", "Iranian Rial"),
                            ("IQD", "Iraqi Dinar"),
                            ("IEP", "Irish Pound"),
                            ("ILS", "Israeli New Shekel"),
                            ("ILP", "Israeli Pound"),
                            ("ILR", "Israeli Shekel (1980–1985)"),
                            ("ITL", "Italian Lira"),
                            ("JMD", "Jamaican Dollar"),
                            ("JPY", "Japanese Yen"),
                            ("JOD", "Jordanian Dinar"),
                            ("KZT", "Kazakhstani Tenge"),
                            ("KES", "Kenyan Shilling"),
                            ("KWD", "Kuwaiti Dinar"),
                            ("KGS", "Kyrgystani Som"),
                            ("LAK", "Laotian Kip"),
                            ("LVL", "Latvian Lats"),
                            ("LVR", "Latvian Ruble"),
                            ("LBP", "Lebanese Pound"),
                            ("LSL", "Lesotho Loti"),
                            ("LRD", "Liberian Dollar"),
                            ("LYD", "Libyan Dinar"),
                            ("LTL", "Lithuanian Litas"),
                            ("LTT", "Lithuanian Talonas"),
                            ("LUL", "Luxembourg Financial Franc"),
                            ("LUC", "Luxembourgian Convertible Franc"),
                            ("LUF", "Luxembourgian Franc"),
                            ("MOP", "Macanese Pataca"),
                            ("MKD", "Macedonian Denar"),
                            ("MKN", "Macedonian Denar (1992–1993)"),
                            ("MGA", "Malagasy Ariary"),
                            ("MGF", "Malagasy Franc"),
                            ("MWK", "Malawian Kwacha"),
                            ("MYR", "Malaysian Ringgit"),
                            ("MVR", "Maldivian Rufiyaa"),
                            ("MVP", "Maldivian Rupee (1947–1981)"),
                            ("MLF", "Malian Franc"),
                            ("MTL", "Maltese Lira"),
                            ("MTP", "Maltese Pound"),
                            ("MRU", "Mauritanian Ouguiya"),
                            ("MRO", "Mauritanian Ouguiya (1973–2017)"),
                            ("MUR", "Mauritian Rupee"),
                            ("MXV", "Mexican Investment Unit"),
                            ("MXN", "Mexican Peso"),
                            ("MXP", "Mexican Silver Peso (1861–1992)"),
                            ("MDC", "Moldovan Cupon"),
                            ("MDL", "Moldovan Leu"),
                            ("MCF", "Monegasque Franc"),
                            ("MNT", "Mongolian Tugrik"),
                            ("MAD", "Moroccan Dirham"),
                            ("MAF", "Moroccan Franc"),
                            ("MZE", "Mozambican Escudo"),
                            ("MZN", "Mozambican Metical"),
                            ("MZM", "Mozambican Metical (1980–2006)"),
                            ("MMK", "Myanmar Kyat"),
                            ("NAD", "Namibian Dollar"),
                            ("NPR", "Nepalese Rupee"),
                            ("ANG", "Netherlands Antillean Guilder"),
                            ("TWD", "New Taiwan Dollar"),
                            ("NZD", "New Zealand Dollar"),
                            ("NIO", "Nicaraguan Córdoba"),
                            ("NIC", "Nicaraguan Córdoba (1988–1991)"),
                            ("NGN", "Nigerian Naira"),
                            ("KPW", "North Korean Won"),
                            ("NOK", "Norwegian Krone"),
                            ("OMR", "Omani Rial"),
                            ("PKR", "Pakistani Rupee"),
                            ("XPD", "Palladium"),
                            ("PAB", "Panamanian Balboa"),
                            ("PGK", "Papua New Guinean Kina"),
                            ("PYG", "Paraguayan Guarani"),
                            ("PEI", "Peruvian Inti"),
                            ("PEN", "Peruvian Sol"),
                            ("PES", "Peruvian Sol (1863–1965)"),
                            ("PHP", "Philippine Peso"),
                            ("XPT", "Platinum"),
                            ("PLN", "Polish Zloty"),
                            ("PLZ", "Polish Zloty (1950–1995)"),
                            ("PTE", "Portuguese Escudo"),
                            ("GWE", "Portuguese Guinea Escudo"),
                            ("QAR", "Qatari Rial"),
                            ("XRE", "RINET Funds"),
                            ("RHD", "Rhodesian Dollar"),
                            ("RON", "Romanian Leu"),
                            ("ROL", "Romanian Leu (1952–2006)"),
                            ("RUB", "Russian Ruble"),
                            ("RUR", "Russian Ruble (1991–1998)"),
                            ("RWF", "Rwandan Franc"),
                            ("SVC", "Salvadoran Colón"),
                            ("WST", "Samoan Tala"),
                            ("SAR", "Saudi Riyal"),
                            ("RSD", "Serbian Dinar"),
                            ("CSD", "Serbian Dinar (2002–2006)"),
                            ("SCR", "Seychellois Rupee"),
                            ("SLL", "Sierra Leonean Leone"),
                            ("XAG", "Silver"),
                            ("SGD", "Singapore Dollar"),
                            ("SKK", "Slovak Koruna"),
                            ("SIT", "Slovenian Tolar"),
                            ("SBD", "Solomon Islands Dollar"),
                            ("SOS", "Somali Shilling"),
                            ("ZAR", "South African Rand"),
                            ("ZAL", "South African Rand (financial)"),
                            ("KRH", "South Korean Hwan (1953–1962)"),
                            ("KRW", "South Korean Won"),
                            ("KRO", "South Korean Won (1945–1953)"),
                            ("SSP", "South Sudanese Pound"),
                            ("SUR", "Soviet Rouble"),
                            ("ESP", "Spanish Peseta"),
                            ("ESA", "Spanish Peseta (A account)"),
                            ("ESB", "Spanish Peseta (convertible account)"),
                            ("XDR", "Special Drawing Rights"),
                            ("LKR", "Sri Lankan Rupee"),
                            ("SHP", "St. Helena Pound"),
                            ("XSU", "Sucre"),
                            ("SDD", "Sudanese Dinar (1992–2007)"),
                            ("SDG", "Sudanese Pound"),
                            ("SDP", "Sudanese Pound (1957–1998)"),
                            ("SRD", "Surinamese Dollar"),
                            ("SRG", "Surinamese Guilder"),
                            ("SZL", "Swazi Lilangeni"),
                            ("SEK", "Swedish Krona"),
                            ("CHF", "Swiss Franc"),
                            ("SYP", "Syrian Pound"),
                            ("STN", "São Tomé & Príncipe Dobra"),
                            ("STD", "São Tomé & Príncipe Dobra (1977–2017)"),
                            ("TVD", "TVD"),
                            ("TJR", "Tajikistani Ruble"),
                            ("TJS", "Tajikistani Somoni"),
                            ("TZS", "Tanzanian Shilling"),
                            ("XTS", "Testing Currency Code"),
                            ("THB", "Thai Baht"),
                            (
                                "XXX",
                                "The codes assigned for transactions where no currency is involved",
                            ),
                            ("TPE", "Timorese Escudo"),
                            ("TOP", "Tongan Paʻanga"),
                            ("TTD", "Trinidad & Tobago Dollar"),
                            ("TND", "Tunisian Dinar"),
                            ("TRY", "Turkish Lira"),
                            ("TRL", "Turkish Lira (1922–2005)"),
                            ("TMT", "Turkmenistani Manat"),
                            ("TMM", "Turkmenistani Manat (1993–2009)"),
                            ("USD", "US Dollar"),
                            ("USN", "US Dollar (Next day)"),
                            ("USS", "US Dollar (Same day)"),
                            ("UGX", "Ugandan Shilling"),
                            ("UGS", "Ugandan Shilling (1966–1987)"),
                            ("UAH", "Ukrainian Hryvnia"),
                            ("UAK", "Ukrainian Karbovanets"),
                            ("AED", "United Arab Emirates Dirham"),
                            ("UYW", "Uruguayan Nominal Wage Index Unit"),
                            ("UYU", "Uruguayan Peso"),
                            ("UYP", "Uruguayan Peso (1975–1993)"),
                            ("UYI", "Uruguayan Peso (Indexed Units)"),
                            ("UZS", "Uzbekistani Som"),
                            ("VUV", "Vanuatu Vatu"),
                            ("VES", "Venezuelan Bolívar"),
                            ("VEB", "Venezuelan Bolívar (1871–2008)"),
                            ("VEF", "Venezuelan Bolívar (2008–2018)"),
                            ("VND", "Vietnamese Dong"),
                            ("VNN", "Vietnamese Dong (1978–1985)"),
                            ("CHE", "WIR Euro"),
                            ("CHW", "WIR Franc"),
                            ("XOF", "West African CFA Franc"),
                            ("YDD", "Yemeni Dinar"),
                            ("YER", "Yemeni Rial"),
                            ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"),
                            ("YUD", "Yugoslavian Hard Dinar (1966–1990)"),
                            ("YUM", "Yugoslavian New Dinar (1994–2002)"),
                            ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"),
                            ("ZWN", "ZWN"),
                            ("ZRN", "Zairean New Zaire (1993–1998)"),
                            ("ZRZ", "Zairean Zaire (1971–1993)"),
                            ("ZMW", "Zambian Kwacha"),
                            ("ZMK", "Zambian Kwacha (1968–2012)"),
                            ("ZWD", "Zimbabwean Dollar (1980–2008)"),
                            ("ZWR", "Zimbabwean Dollar (2008)"),
                            ("ZWL", "Zimbabwean Dollar (2009)"),
                        ],
                        default="USD",
                        editable=False,
                        max_length=3,
                    ),
                ),
                (
                    "balance",
                    djmoney.models.fields.MoneyField(
                        decimal_places=2,
                        default=Decimal("0"),
                        default_currency="USD",
                        max_digits=10,
                    ),
                ),
                ("currency", models.CharField(default="USD", max_length=3)),
            ],
        ),
        migrations.CreateModel(
            name="Subscription",
            fields=[
                (
                    "id",
                    models.CharField(
                        default=uuid.uuid4,
                        max_length=36,
                        primary_key=True,
                        serialize=False,
                    ),
                ),
                ("start_date", models.DateField()),
                ("end_date", models.DateField(auto_now=True)),
                (
                    "status",
                    models.CharField(
                        choices=[("active", "Active"), ("ended", "Ended")],
                        default="active",
                        max_length=6,
                    ),
                ),
                (
                    "billing_plan",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="billing.billingplan",
                    ),
                ),
                (
                    "customer",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="billing.customer",
                    ),
                ),
            ],
        ),
        migrations.CreateModel(
            name="Invoice",
            fields=[
                (
                    "id",
                    models.CharField(
                        default=uuid.uuid4,
                        max_length=36,
                        primary_key=True,
                        serialize=False,
                    ),
                ),
                (
                    "cost_due_currency",
                    djmoney.models.fields.CurrencyField(
                        choices=[
                            ("XUA", "ADB Unit of Account"),
                            ("AFN", "Afghan Afghani"),
                            ("AFA", "Afghan Afghani (1927–2002)"),
                            ("ALL", "Albanian Lek"),
                            ("ALK", "Albanian Lek (1946–1965)"),
                            ("DZD", "Algerian Dinar"),
                            ("ADP", "Andorran Peseta"),
                            ("AOA", "Angolan Kwanza"),
                            ("AOK", "Angolan Kwanza (1977–1991)"),
                            ("AON", "Angolan New Kwanza (1990–2000)"),
                            ("AOR", "Angolan Readjusted Kwanza (1995–1999)"),
                            ("ARA", "Argentine Austral"),
                            ("ARS", "Argentine Peso"),
                            ("ARM", "Argentine Peso (1881–1970)"),
                            ("ARP", "Argentine Peso (1983–1985)"),
                            ("ARL", "Argentine Peso Ley (1970–1983)"),
                            ("AMD", "Armenian Dram"),
                            ("AWG", "Aruban Florin"),
                            ("AUD", "Australian Dollar"),
                            ("ATS", "Austrian Schilling"),
                            ("AZN", "Azerbaijani Manat"),
                            ("AZM", "Azerbaijani Manat (1993–2006)"),
                            ("BSD", "Bahamian Dollar"),
                            ("BHD", "Bahraini Dinar"),
                            ("BDT", "Bangladeshi Taka"),
                            ("BBD", "Barbadian Dollar"),
                            ("BYN", "Belarusian Ruble"),
                            ("BYB", "Belarusian Ruble (1994–1999)"),
                            ("BYR", "Belarusian Ruble (2000–2016)"),
                            ("BEF", "Belgian Franc"),
                            ("BEC", "Belgian Franc (convertible)"),
                            ("BEL", "Belgian Franc (financial)"),
                            ("BZD", "Belize Dollar"),
                            ("BMD", "Bermudan Dollar"),
                            ("BTN", "Bhutanese Ngultrum"),
                            ("BOB", "Bolivian Boliviano"),
                            ("BOL", "Bolivian Boliviano (1863–1963)"),
                            ("BOV", "Bolivian Mvdol"),
                            ("BOP", "Bolivian Peso"),
                            ("BAM", "Bosnia-Herzegovina Convertible Mark"),
                            ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"),
                            ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"),
                            ("BWP", "Botswanan Pula"),
                            ("BRC", "Brazilian Cruzado (1986–1989)"),
                            ("BRZ", "Brazilian Cruzeiro (1942–1967)"),
                            ("BRE", "Brazilian Cruzeiro (1990–1993)"),
                            ("BRR", "Brazilian Cruzeiro (1993–1994)"),
                            ("BRN", "Brazilian New Cruzado (1989–1990)"),
                            ("BRB", "Brazilian New Cruzeiro (1967–1986)"),
                            ("BRL", "Brazilian Real"),
                            ("GBP", "British Pound"),
                            ("BND", "Brunei Dollar"),
                            ("BGL", "Bulgarian Hard Lev"),
                            ("BGN", "Bulgarian Lev"),
                            ("BGO", "Bulgarian Lev (1879–1952)"),
                            ("BGM", "Bulgarian Socialist Lev"),
                            ("BUK", "Burmese Kyat"),
                            ("BIF", "Burundian Franc"),
                            ("XPF", "CFP Franc"),
                            ("KHR", "Cambodian Riel"),
                            ("CAD", "Canadian Dollar"),
                            ("CVE", "Cape Verdean Escudo"),
                            ("KYD", "Cayman Islands Dollar"),
                            ("XAF", "Central African CFA Franc"),
                            ("CLE", "Chilean Escudo"),
                            ("CLP", "Chilean Peso"),
                            ("CLF", "Chilean Unit of Account (UF)"),
                            ("CNX", "Chinese People’s Bank Dollar"),
                            ("CNY", "Chinese Yuan"),
                            ("CNH", "Chinese Yuan (offshore)"),
                            ("COP", "Colombian Peso"),
                            ("COU", "Colombian Real Value Unit"),
                            ("KMF", "Comorian Franc"),
                            ("CDF", "Congolese Franc"),
                            ("CRC", "Costa Rican Colón"),
                            ("HRD", "Croatian Dinar"),
                            ("HRK", "Croatian Kuna"),
                            ("CUC", "Cuban Convertible Peso"),
                            ("CUP", "Cuban Peso"),
                            ("CYP", "Cypriot Pound"),
                            ("CZK", "Czech Koruna"),
                            ("CSK", "Czechoslovak Hard Koruna"),
                            ("DKK", "Danish Krone"),
                            ("DJF", "Djiboutian Franc"),
                            ("DOP", "Dominican Peso"),
                            ("NLG", "Dutch Guilder"),
                            ("XCD", "East Caribbean Dollar"),
                            ("DDM", "East German Mark"),
                            ("ECS", "Ecuadorian Sucre"),
                            ("ECV", "Ecuadorian Unit of Constant Value"),
                            ("EGP", "Egyptian Pound"),
                            ("GQE", "Equatorial Guinean Ekwele"),
                            ("ERN", "Eritrean Nakfa"),
                            ("EEK", "Estonian Kroon"),
                            ("ETB", "Ethiopian Birr"),
                            ("EUR", "Euro"),
                            ("XBA", "European Composite Unit"),
                            ("XEU", "European Currency Unit"),
                            ("XBB", "European Monetary Unit"),
                            ("XBC", "European Unit of Account (XBC)"),
                            ("XBD", "European Unit of Account (XBD)"),
                            ("FKP", "Falkland Islands Pound"),
                            ("FJD", "Fijian Dollar"),
                            ("FIM", "Finnish Markka"),
                            ("FRF", "French Franc"),
                            ("XFO", "French Gold Franc"),
                            ("XFU", "French UIC-Franc"),
                            ("GMD", "Gambian Dalasi"),
                            ("GEK", "Georgian Kupon Larit"),
                            ("GEL", "Georgian Lari"),
                            ("DEM", "German Mark"),
                            ("GHS", "Ghanaian Cedi"),
                            ("GHC", "Ghanaian Cedi (1979–2007)"),
                            ("GIP", "Gibraltar Pound"),
                            ("XAU", "Gold"),
                            ("GRD", "Greek Drachma"),
                            ("GTQ", "Guatemalan Quetzal"),
                            ("GWP", "Guinea-Bissau Peso"),
                            ("GNF", "Guinean Franc"),
                            ("GNS", "Guinean Syli"),
                            ("GYD", "Guyanaese Dollar"),
                            ("HTG", "Haitian Gourde"),
                            ("HNL", "Honduran Lempira"),
                            ("HKD", "Hong Kong Dollar"),
                            ("HUF", "Hungarian Forint"),
                            ("IMP", "IMP"),
                            ("ISK", "Icelandic Króna"),
                            ("ISJ", "Icelandic Króna (1918–1981)"),
                            ("INR", "Indian Rupee"),
                            ("IDR", "Indonesian Rupiah"),
                            ("IRR", "Iranian Rial"),
                            ("IQD", "Iraqi Dinar"),
                            ("IEP", "Irish Pound"),
                            ("ILS", "Israeli New Shekel"),
                            ("ILP", "Israeli Pound"),
                            ("ILR", "Israeli Shekel (1980–1985)"),
                            ("ITL", "Italian Lira"),
                            ("JMD", "Jamaican Dollar"),
                            ("JPY", "Japanese Yen"),
                            ("JOD", "Jordanian Dinar"),
                            ("KZT", "Kazakhstani Tenge"),
                            ("KES", "Kenyan Shilling"),
                            ("KWD", "Kuwaiti Dinar"),
                            ("KGS", "Kyrgystani Som"),
                            ("LAK", "Laotian Kip"),
                            ("LVL", "Latvian Lats"),
                            ("LVR", "Latvian Ruble"),
                            ("LBP", "Lebanese Pound"),
                            ("LSL", "Lesotho Loti"),
                            ("LRD", "Liberian Dollar"),
                            ("LYD", "Libyan Dinar"),
                            ("LTL", "Lithuanian Litas"),
                            ("LTT", "Lithuanian Talonas"),
                            ("LUL", "Luxembourg Financial Franc"),
                            ("LUC", "Luxembourgian Convertible Franc"),
                            ("LUF", "Luxembourgian Franc"),
                            ("MOP", "Macanese Pataca"),
                            ("MKD", "Macedonian Denar"),
                            ("MKN", "Macedonian Denar (1992–1993)"),
                            ("MGA", "Malagasy Ariary"),
                            ("MGF", "Malagasy Franc"),
                            ("MWK", "Malawian Kwacha"),
                            ("MYR", "Malaysian Ringgit"),
                            ("MVR", "Maldivian Rufiyaa"),
                            ("MVP", "Maldivian Rupee (1947–1981)"),
                            ("MLF", "Malian Franc"),
                            ("MTL", "Maltese Lira"),
                            ("MTP", "Maltese Pound"),
                            ("MRU", "Mauritanian Ouguiya"),
                            ("MRO", "Mauritanian Ouguiya (1973–2017)"),
                            ("MUR", "Mauritian Rupee"),
                            ("MXV", "Mexican Investment Unit"),
                            ("MXN", "Mexican Peso"),
                            ("MXP", "Mexican Silver Peso (1861–1992)"),
                            ("MDC", "Moldovan Cupon"),
                            ("MDL", "Moldovan Leu"),
                            ("MCF", "Monegasque Franc"),
                            ("MNT", "Mongolian Tugrik"),
                            ("MAD", "Moroccan Dirham"),
                            ("MAF", "Moroccan Franc"),
                            ("MZE", "Mozambican Escudo"),
                            ("MZN", "Mozambican Metical"),
                            ("MZM", "Mozambican Metical (1980–2006)"),
                            ("MMK", "Myanmar Kyat"),
                            ("NAD", "Namibian Dollar"),
                            ("NPR", "Nepalese Rupee"),
                            ("ANG", "Netherlands Antillean Guilder"),
                            ("TWD", "New Taiwan Dollar"),
                            ("NZD", "New Zealand Dollar"),
                            ("NIO", "Nicaraguan Córdoba"),
                            ("NIC", "Nicaraguan Córdoba (1988–1991)"),
                            ("NGN", "Nigerian Naira"),
                            ("KPW", "North Korean Won"),
                            ("NOK", "Norwegian Krone"),
                            ("OMR", "Omani Rial"),
                            ("PKR", "Pakistani Rupee"),
                            ("XPD", "Palladium"),
                            ("PAB", "Panamanian Balboa"),
                            ("PGK", "Papua New Guinean Kina"),
                            ("PYG", "Paraguayan Guarani"),
                            ("PEI", "Peruvian Inti"),
                            ("PEN", "Peruvian Sol"),
                            ("PES", "Peruvian Sol (1863–1965)"),
                            ("PHP", "Philippine Peso"),
                            ("XPT", "Platinum"),
                            ("PLN", "Polish Zloty"),
                            ("PLZ", "Polish Zloty (1950–1995)"),
                            ("PTE", "Portuguese Escudo"),
                            ("GWE", "Portuguese Guinea Escudo"),
                            ("QAR", "Qatari Rial"),
                            ("XRE", "RINET Funds"),
                            ("RHD", "Rhodesian Dollar"),
                            ("RON", "Romanian Leu"),
                            ("ROL", "Romanian Leu (1952–2006)"),
                            ("RUB", "Russian Ruble"),
                            ("RUR", "Russian Ruble (1991–1998)"),
                            ("RWF", "Rwandan Franc"),
                            ("SVC", "Salvadoran Colón"),
                            ("WST", "Samoan Tala"),
                            ("SAR", "Saudi Riyal"),
                            ("RSD", "Serbian Dinar"),
                            ("CSD", "Serbian Dinar (2002–2006)"),
                            ("SCR", "Seychellois Rupee"),
                            ("SLL", "Sierra Leonean Leone"),
                            ("XAG", "Silver"),
                            ("SGD", "Singapore Dollar"),
                            ("SKK", "Slovak Koruna"),
                            ("SIT", "Slovenian Tolar"),
                            ("SBD", "Solomon Islands Dollar"),
                            ("SOS", "Somali Shilling"),
                            ("ZAR", "South African Rand"),
                            ("ZAL", "South African Rand (financial)"),
                            ("KRH", "South Korean Hwan (1953–1962)"),
                            ("KRW", "South Korean Won"),
                            ("KRO", "South Korean Won (1945–1953)"),
                            ("SSP", "South Sudanese Pound"),
                            ("SUR", "Soviet Rouble"),
                            ("ESP", "Spanish Peseta"),
                            ("ESA", "Spanish Peseta (A account)"),
                            ("ESB", "Spanish Peseta (convertible account)"),
                            ("XDR", "Special Drawing Rights"),
                            ("LKR", "Sri Lankan Rupee"),
                            ("SHP", "St. Helena Pound"),
                            ("XSU", "Sucre"),
                            ("SDD", "Sudanese Dinar (1992–2007)"),
                            ("SDG", "Sudanese Pound"),
                            ("SDP", "Sudanese Pound (1957–1998)"),
                            ("SRD", "Surinamese Dollar"),
                            ("SRG", "Surinamese Guilder"),
                            ("SZL", "Swazi Lilangeni"),
                            ("SEK", "Swedish Krona"),
                            ("CHF", "Swiss Franc"),
                            ("SYP", "Syrian Pound"),
                            ("STN", "São Tomé & Príncipe Dobra"),
                            ("STD", "São Tomé & Príncipe Dobra (1977–2017)"),
                            ("TVD", "TVD"),
                            ("TJR", "Tajikistani Ruble"),
                            ("TJS", "Tajikistani Somoni"),
                            ("TZS", "Tanzanian Shilling"),
                            ("XTS", "Testing Currency Code"),
                            ("THB", "Thai Baht"),
                            (
                                "XXX",
                                "The codes assigned for transactions where no currency is involved",
                            ),
                            ("TPE", "Timorese Escudo"),
                            ("TOP", "Tongan Paʻanga"),
                            ("TTD", "Trinidad & Tobago Dollar"),
                            ("TND", "Tunisian Dinar"),
                            ("TRY", "Turkish Lira"),
                            ("TRL", "Turkish Lira (1922–2005)"),
                            ("TMT", "Turkmenistani Manat"),
                            ("TMM", "Turkmenistani Manat (1993–2009)"),
                            ("USD", "US Dollar"),
                            ("USN", "US Dollar (Next day)"),
                            ("USS", "US Dollar (Same day)"),
                            ("UGX", "Ugandan Shilling"),
                            ("UGS", "Ugandan Shilling (1966–1987)"),
                            ("UAH", "Ukrainian Hryvnia"),
                            ("UAK", "Ukrainian Karbovanets"),
                            ("AED", "United Arab Emirates Dirham"),
                            ("UYW", "Uruguayan Nominal Wage Index Unit"),
                            ("UYU", "Uruguayan Peso"),
                            ("UYP", "Uruguayan Peso (1975–1993)"),
                            ("UYI", "Uruguayan Peso (Indexed Units)"),
                            ("UZS", "Uzbekistani Som"),
                            ("VUV", "Vanuatu Vatu"),
                            ("VES", "Venezuelan Bolívar"),
                            ("VEB", "Venezuelan Bolívar (1871–2008)"),
                            ("VEF", "Venezuelan Bolívar (2008–2018)"),
                            ("VND", "Vietnamese Dong"),
                            ("VNN", "Vietnamese Dong (1978–1985)"),
                            ("CHE", "WIR Euro"),
                            ("CHW", "WIR Franc"),
                            ("XOF", "West African CFA Franc"),
                            ("YDD", "Yemeni Dinar"),
                            ("YER", "Yemeni Rial"),
                            ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"),
                            ("YUD", "Yugoslavian Hard Dinar (1966–1990)"),
                            ("YUM", "Yugoslavian New Dinar (1994–2002)"),
                            ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"),
                            ("ZWN", "ZWN"),
                            ("ZRN", "Zairean New Zaire (1993–1998)"),
                            ("ZRZ", "Zairean Zaire (1971–1993)"),
                            ("ZMW", "Zambian Kwacha"),
                            ("ZMK", "Zambian Kwacha (1968–2012)"),
                            ("ZWD", "Zimbabwean Dollar (1980–2008)"),
                            ("ZWR", "Zimbabwean Dollar (2008)"),
                            ("ZWL", "Zimbabwean Dollar (2009)"),
                        ],
                        default="USD",
                        editable=False,
                        max_length=3,
                        null=True,
                    ),
                ),
                (
                    "cost_due",
                    djmoney.models.fields.MoneyField(
                        decimal_places=2,
                        default_currency="USD",
                        max_digits=10,
                        null=True,
                    ),
                ),
                ("currency", models.CharField(default="USD", max_length=10)),
                ("issue_date", models.DateTimeField(auto_now=True, max_length=100)),
                ("customer_name", models.CharField(max_length=100)),
                ("customer_billing_id", models.CharField(max_length=40)),
                (
                    "invoice_pdf",
                    models.FileField(blank=True, null=True, upload_to="invoices/"),
                ),
                (
                    "line_items",
                    django.contrib.postgres.fields.ArrayField(
                        base_field=models.JSONField(), blank=True, null=True, size=None
                    ),
                ),
                (
                    "subscription",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.PROTECT,
                        to="billing.subscription",
                    ),
                ),
            ],
        ),
        migrations.CreateModel(
            name="Event",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("event_name", models.CharField(max_length=200)),
                ("time_created", models.DateTimeField()),
                ("properties", models.JSONField(default=dict)),
                ("idempotency_id", models.CharField(max_length=255, unique=True)),
                (
                    "customer",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="billing.customer",
                    ),
                ),
            ],
            options={
                "ordering": ["idempotency_id"],
            },
        ),
        migrations.CreateModel(
            name="APIToken",
            fields=[
                (
                    "id",
                    models.CharField(
                        editable=False,
                        max_length=150,
                        primary_key=True,
                        serialize=False,
                        unique=True,
                    ),
                ),
                ("prefix", models.CharField(editable=False, max_length=8, unique=True)),
                ("hashed_key", models.CharField(editable=False, max_length=150)),
                ("created", models.DateTimeField(auto_now_add=True, db_index=True)),
                (
                    "name",
                    models.CharField(
                        default=None,
                        help_text="A free-form name for the API key. Need not be unique. 50 characters max.",
                        max_length=50,
                    ),
                ),
                (
                    "revoked",
                    models.BooleanField(
                        blank=True,
                        default=False,
                        help_text="If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)",
                    ),
                ),
                (
                    "expiry_date",
                    models.DateTimeField(
                        blank=True,
                        help_text="Once API key expires, clients cannot use it anymore.",
                        null=True,
                        verbose_name="Expires",
                    ),
                ),
                (
                    "user",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to=settings.AUTH_USER_MODEL,
                    ),
                ),
            ],
            options={
                "verbose_name": "API Token",
                "verbose_name_plural": "API Tokens",
            },
        ),
    ]

billing/management/__init__.py METASEP

lotus/wsgi.py METASEP
"""
WSGI config for lotus project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lotus.settings")

application = get_wsgi_application()

lotus/urls.py METASEP
"""lotus URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from rest_framework import routers
from billing.views.views import (
    EventViewSet,
    SubscriptionViewSet,
    CustomerView,
    SubscriptionView,
    UsageView,
)
from billing import track

router = routers.DefaultRouter()
router.register(r"event", EventViewSet)

urlpatterns = [
    path("grappelli/", include("grappelli.urls")),
    path("admin/", admin.site.urls),
    path("api/", include(router.urls)),
    path("api/customers", CustomerView.as_view(), name="customer"),
    path("api/customers/", CustomerView.as_view(), name="customer"),
    path("api/subscriptions", SubscriptionView.as_view(), name="subscription"),
    path("track/", track.track_event, name="track_event"),
    path("track", track.track_event, name="track_event"),
    path("api/usage", UsageView.as_view(), name="usage"),
]

lotus/settings.py METASEP
"""
Django settings for lotus project.

Generated by 'django-admin startproject' using Django 4.0.6.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from pathlib import Path
from celery.schedules import crontab
from dotenv import load_dotenv
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

load_dotenv()


sentry_sdk.init(
    dsn=os.environ["SENTRY_DSN"],
    integrations=[
        DjangoIntegration(),
    ],
    # Set traces_sample_rate to 1.0 to capture 100%
    # of transactions for performance monitoring.
    # We recommend adjusting this value in production.
    traces_sample_rate=1.0,
    # If you wish to associate users to errors (assuming you are using
    # django.contrib.auth) you may enable sending PII data.
    send_default_pii=True,
)


# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ["SECRET_KEY"]

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

try:
    ALLOWED_HOSTS = ["*"]
except KeyError:
    ALLOWED_HOSTS = ["127.0.0.1", "localhost"]

# Application definition

INSTALLED_APPS = [
    "grappelli",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework",
    "rest_framework_api_key",
    "billing",
    "djmoney",
    "corsheaders",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "lotus.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "lotus.wsgi.application"


AUTH_USER_MODEL = "billing.User"

# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": os.getenv("POSTGRES_NAME", "lotus"),
        "USER": os.getenv("POSTGRES_USER", "db_user"),
        "PASSWORD": "",
        "HOST": os.environ["DATABASE_HOST"],
        "PORT": 5432,
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Celery Settings
CELERY_BROKER_URL = "redis://redis:6379"
CELERY_RESULT_BACKEND = "redis://redis:6379"
CELERY_ACCEPT_CONTENT = ["application/json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_TIMEZONE = "America/New_York"

REDIS_HOST = "localhost"
REDIS_PORT = 6379

# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = "static/"

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "billing.permissions.HasUserAPIKey",
    ]
}


# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True


CELERY_BEAT_SCHEDULE = {
    "calculate_invoice": {
        "task": "billing.tasks.calculate_invoice",
        "schedule": crontab(hour="0"),
    },
}

lotus/celery.py METASEP
import os

from celery import Celery

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lotus.settings")

app = Celery("lotus")

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
#   should have a `CELERY_` prefix.
app.config_from_object("django.conf:settings", namespace="CELERY")

# Load task modules from all registered Django apps.
app.autodiscover_tasks()


@app.task(bind=True, ignore_result=True)
def debug_task(self):
    print(f"Request: {self.request!r}")

lotus/asgi.py METASEP
"""
ASGI config for lotus project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lotus.settings")

application = get_asgi_application()

lotus/__init__.py METASEP
from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app

__all__ = ("celery_app",)

billing/track.py METASEP
from re import S
from .models import Event, Customer, BillingPlan, APIToken
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse, HttpRequest
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from rest_framework.parsers import JSONParser
from django.db import IntegrityError
from django.views.decorators.csrf import csrf_exempt
import json
import base64
from urllib.parse import urlparse
from typing import Dict, Union, List
from rest_framework.decorators import permission_classes
from .permissions import HasUserAPIKey
from rest_framework.permissions import IsAuthenticated


def load_event(request: HttpRequest) -> Union[None, Dict]:
    """
    Loads an event from the request body.
    """
    if request.method != "POST":
        event_data = request.GET.get("data")
    else:

        if request.content_type == "application/json":
            try:
                event_data = json.load(request)
            except json.JSONDecodeError:
                # if not, it's probably base64 encoded from other libraries
                event_data = json.load(
                    base64.b64decode(event_data + "===")
                    .decode("utf8", "surrogatepass")
                    .encode("utf-16", "surrogatepass")
                )
        else:
            event_data = request.POST
    if event_data is None:
        return None

    return event_data


def ingest_event(request, data: dict, customer: Customer) -> None:

    idepotency_id_query = (
        Event.objects.all().filter(idempotency_id=data["idempotency_id"]).count()
    )
    if idepotency_id_query > 0:
        return HttpResponseBadRequest("An event record already exists", status=409)

    db_event = Event.objects.create(
        event_name=data["event_name"],
        idempotency_id=data["idempotency_id"],
        properties=data["properties"],
        customer=customer,
        time_created=data["time_created"],
    )
    db_event.save()


@csrf_exempt
def track_event(request):
    # Check Permissions
    permissions = HasUserAPIKey()
    if not (permissions.has_permission(request, "track_event")):
        return HttpResponseBadRequest("Invalid API Key or No API Key provided")

    data = load_event(request)
    if not data:
        return HttpResponseBadRequest("No data provided")
    customer_id = data["customer_id"]
    try:
        customer = Customer.objects.get(customer_id=customer_id)
    except Customer.DoesNotExist:
        return HttpResponseBadRequest("Customer does not exist")

    if isinstance(data, list):
        for i in data:
            try:
                ingest_event(request=request, data=i, customer=customer)
            except KeyError:
                return JsonResponse(
                    {
                        "code": "validation",
                        "message": "You need to set a distinct_id.",
                        "item": data,
                    },
                    status=400,
                )
    else:
        ingest_event(request=request, data=data, customer=customer)

    return JsonResponse({"status": "Success"}, status=200)

billing/tasks.py METASEP
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from datetime import datetime
from .models import Subscription
from .generate_invoice import generate_invoice


@shared_task
def calculate_invoice():
    ending_subscriptions = Subscription.objects.filter(
        status="active", end_date__lte=datetime.now()
    )
    for subscription in ending_subscriptions:
        # Generate the invoice
        try:
            generate_invoice(subscription)
        except Exception as e:
            print(e)
            print("Error generating invoice for subscription {}".format(subscription))
            continue
        # Renew the subscription
        subscription.start_date = datetime.now()
        subscription.end_date = subscription.billing_plan.subscription_end_date(
            subscription.start_date
        )
        subscription.save()

billing/serializers.py METASEP
from rest_framework import serializers

from .models import Event, Customer, Subscription, Invoice


class InvoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Invoice
        fields = "__all__"


class EventSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Event
        fields = (
            "customer",
            "event_name",
            "time_created",
            "properties",
            "idempotency_id",
        )


class CustomerSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Customer
        fields = (
            "name",
            "company_name",
            "customer_id",
            "billing_id",
            "balance",
            "billing_configuration",
        )


class SubscriptionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subscription
        fields = (
            "customer",
            "billing_plan",
            "start_date",
            "end_date",
            "status",
        )

billing/permissions.py METASEP
from rest_framework_api_key.permissions import BaseHasAPIKey
from .models import APIToken


class HasUserAPIKey(BaseHasAPIKey):
    model = APIToken

billing/models.py METASEP
from email.mime import base
from operator import mod
from django.db import models
import uuid
from model_utils import Choices
from djmoney.models.fields import MoneyField
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.utils.translation import gettext_lazy as _
from moneyed import Money
from rest_framework_api_key.models import AbstractAPIKey
from dateutil.relativedelta import relativedelta
from dateutil.parser import isoparse
from django.contrib.postgres.fields import ArrayField
import jsonfield

# Create your models here.


class Customer(models.Model):

    """
    Customer Model

    This model represents a customer.

    Attributes:
        name (str): The name of the customer.
        company_name (str): The company name of the customer.
        customer_id (str): The external id of the customer in the backend system.
        billing_id (str): The billing id of the customer, internal to Lotus.
    """

    name = models.CharField(max_length=100)
    company_name = models.CharField(max_length=30, default=" ")
    customer_id = models.CharField(max_length=40, unique=True)
    billing_id = models.CharField(max_length=40, default=uuid.uuid4)
    billing_configuration = models.JSONField(default=dict, blank=True)

    balance = MoneyField(
        default=0, max_digits=10, decimal_places=2, default_currency="USD"
    )  # balance in currency that a customer currently has during this billing period, negative means they owe money, postive is a credit towards their invoice
    currency = models.CharField(max_length=3, default="USD")

    def __str__(self) -> str:
        return str(self.name) + " " + str(self.customer_id)


class Event(models.Model):
    """
    Event object. An explanation of the Event's fields follows:
    event_name: The type of event that occurred.
    time_created: The time at which the event occurred.
    customer: The customer that the event occurred to.
    idempotency_id: A unique identifier for the event.
    """

    customer: models.ForeignKey = models.ForeignKey(
        Customer, on_delete=models.CASCADE, null=False
    )
    event_name = models.CharField(max_length=200, null=False)
    time_created: models.DateTimeField = models.DateTimeField()
    properties: models.JSONField = models.JSONField(default=dict)
    idempotency_id: models.CharField = models.CharField(max_length=255, unique=True)

    class Meta:
        ordering = ["idempotency_id"]

    def __str__(self):
        return str(self.event_name) + "-" + str(self.idempotency_id)


class BillableMetric(models.Model):
    class AGGREGATION_TYPES(object):
        COUNT = "count"
        SUM = "sum"
        MAX = "max"

    AGGREGATION_CHOICES = Choices(
        (AGGREGATION_TYPES.COUNT, _("Count")),
        (AGGREGATION_TYPES.SUM, _("Sum")),
        (AGGREGATION_TYPES.MAX, _("Max")),
    )

    event_name = models.CharField(max_length=200, null=False)
    property_name = models.CharField(max_length=200, null=True)
    aggregation_type = models.CharField(
        max_length=10,
        choices=AGGREGATION_CHOICES,
        default=AGGREGATION_CHOICES.count,
    )

    def get_aggregation_type(self):
        return self.aggregation_type


class BillingPlan(models.Model):
    """
    Billing_ID: Id for this specific plan
    time_created: self-explanatory
    currency: self-explanatory
    interval: determines whether plan charges weekly, monthly, or yearly
    base_rate: amount to charge every week, month, or year (depending on choice of interval)
    billable_metrics: a json containing a list of billable_metrics objects
    """

    plan_id = models.CharField(
        max_length=36, blank=True, unique=True, default=uuid.uuid4
    )

    time_created: models.DateTimeField = models.DateTimeField(auto_now=True)
    currency = models.CharField(max_length=30, default="USD")  # 30 is arbitrary

    INTERVAL_CHOICES = Choices(
        ("week", _("Week")),
        ("month", _("Month")),
        ("year", _("Year")),
    )

    interval = models.CharField(
        max_length=5,
        choices=INTERVAL_CHOICES,
        default=INTERVAL_CHOICES.month,
    )

    base_rate = MoneyField(
        decimal_places=2, max_digits=8, default_currency="USD", default=0.0
    )
    pay_in_advance = models.BooleanField(default=False)
    # Need to figure out how to make this a list of BillableMetrics
    billable_metric = models.ForeignKey(
        BillableMetric, on_delete=models.CASCADE, null=True, blank=True
    )
    starter_metric_quatity = models.IntegerField(default=0, null=True, blank=True)
    metric_amount = MoneyField(
        decimal_places=10, max_digits=14, default_currency="USD", null=True, blank=True
    )
    name = models.CharField(max_length=200, default=" ")
    description = models.CharField(max_length=256, default=" ", blank=True)

    def subscription_end_date(self, start_date):
        start_date_parsed = start_date
        if self.interval == "week":
            return start_date_parsed + relativedelta(weeks=+1)
        elif self.interval == "month":
            return start_date_parsed + relativedelta(months=+1)
        elif self.interval == "year":
            return start_date_parsed + relativedelta(years=+1)
        else:
            return None

    def get_usage_cost_count_aggregation(self, num_events):
        return max(0, self.metric_amount * (self.starter_metric_quantity - num_events))

    def __str__(self) -> str:
        return str(self.name) + ":" + str(self.plan_id)


class Subscription(models.Model):
    """
    Subscription object. An explanation of the Subscription's fields follows:
    customer: The customer that the subscription belongs to.
    plan_name: The name of the plan that the subscription is for.
    start_date: The date at which the subscription started.
    end_date: The date at which the subscription will end.
    status: The status of the subscription, active or ended.

    """

    STATUSES = Choices(
        ("active", _("Active")),
        ("ended", _("Ended")),
    )

    id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4)

    customer: models.ForeignKey = models.ForeignKey(Customer, on_delete=models.CASCADE)
    billing_plan = models.ForeignKey(BillingPlan, on_delete=models.CASCADE)
    start_date = models.DateField()
    end_date = models.DateField(auto_now=True)
    status = models.CharField(max_length=6, choices=STATUSES, default=STATUSES.active)

    def __str__(self):
        return str(self.customer) + " " + str(self.billing_plan)


class Invoice(models.Model):

    id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4)
    cost_due = MoneyField(
        max_digits=10, decimal_places=2, null=True, default_currency="USD"
    )
    currency = models.CharField(max_length=10, default="USD")
    issue_date = models.DateTimeField(max_length=100, auto_now=True)

    customer_name = models.CharField(max_length=100)
    customer_billing_id = models.CharField(max_length=40)

    invoice_pdf = models.FileField(upload_to="invoices/", null=True, blank=True)

    subscription = models.ForeignKey(Subscription, on_delete=models.PROTECT)

    line_items = ArrayField(base_field=models.JSONField(), null=True, blank=True)


class User(AbstractUser):

    company_name = models.CharField(max_length=200, default=" ")


class APIToken(AbstractAPIKey):

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=200, default="latest_token")

    def __str__(self):
        return str(self.name) + " " + str(self.user)

    class Meta:
        verbose_name = "API Token"
        verbose_name_plural = "API Tokens"

billing/generate_invoice.py METASEP
from .models import Event, Customer, Subscription, Invoice


def generate_invoice(subscription):
    """
    Generate an invoice for a subscription.
    """
    # Get the customer
    customer_obj = subscription.customer

    # Get the billing plan
    billing_plan = subscription.billing_plan

    billable_metric = billing_plan.billable_metric
    aggregation_type = billable_metric.get_aggregation_type()

    # Get the events for the subscription
    events = Event.objects.all().filter(event_name=billable_metric.event_name)

    if aggregation_type == "count":
        # Get the total number of events
        num_events = len(events)
        usage_cost = billing_plan.get_usage_cost_count_aggregation(num_events)

    else:
        usage_cost = 0.0

    # Get the total cost of the subscription
    total_cost = billing_plan.base_rate + usage_cost

    # Create the invoice
    invoice = Invoice(
        cost_due=total_cost,
        currency=billing_plan.currency,
        issue_date=subscription.end_date,
        subscription=subscription,
        customer_name=customer_obj.name,
        customer_billing_id=customer_obj.billing_id,
    )
    invoice.save()

    # Create the events for the invoice
    return invoice

billing/apps.py METASEP
from django.apps import AppConfig


class BillingConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "billing"

billing/admin.py METASEP
from django.contrib import admin
from .models import (
    BillingPlan,
    Customer,
    Event,
    Subscription,
    User,
    APIToken,
    BillableMetric,
    Invoice,
)
from rest_framework_api_key.admin import APIKeyModelAdmin


# Register your models here.
admin.site.register(Customer)
admin.site.register(Event)
admin.site.register(BillingPlan)
admin.site.register(Subscription)
admin.site.register(Invoice)
admin.site.register(BillableMetric)
admin.site.register(
    User,
)


@admin.register(APIToken)
class UserAPIKeyModelAdmin(APIKeyModelAdmin):
    pass

billing/__init__.py METASEP

requirements.txt METASEP
#
# These requirements were autogenerated by pipenv
# To regenerate from the project's Pipfile, run:
#
#    pipenv lock --requirements
#

-i https://pypi.org/simple
asgiref==3.5.2; python_version >= '3.7'
django==4.0.6
djangorestframework==3.13.1
pytz==2022.1
sqlparse==0.4.2; python_version >= '3.5'

renovate.json METASEP
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:base"
  ]
}

docker-compose.yaml METASEP

services:


  db:
    image: postgres:14
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_USER=db_user
      - POSTGRES_DB=lotus
      - POSTGRES_HOST=db

  lotus:
    container_name: lotus_django
    build:
      context: .
    ports:
      - "8000:8000"
    volumes:
      - ./lotus:/lotus
    command: >
      sh -c "python3 manage.py wait_for_db &&
             python3 manage.py migrate &&
             python3 manage.py initadmin &&
             python3 manage.py runserver 0.0.0.0:8000
             "
    env_file:
    - .env
    depends_on:
      - db
  redis:
    image: redis:7-alpine
    command: redis-server

  celery:
    restart: always
    build:
      context: .
    command: celery -A lotus worker -l info
    volumes:
      - ./lotus:/lotus
    env_file:
      - ./.env
    depends_on:
      - db
      - redis
      - lotus
  celery-beat:
    build:
      context: .
    command: celery -A lotus beat -l info
    volumes:
      - ./lotus:/lotus
    environment:
      - ./.env
    depends_on:
      - redis
      - db
volumes:
  pgdata:

README.md METASEP
# lotus

Pipfile.lock METASEP
{
    "_meta": {
        "hash": {
            "sha256": "9b8b73479244ae19b98170c7bc912f06f39ac3e11836546318b3083f06ccb437"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.9"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "amqp": {
            "hashes": [
                "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2",
                "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==5.1.1"
        },
        "arrow": {
            "hashes": [
                "sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b",
                "sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==1.2.2"
        },
        "asgiref": {
            "hashes": [
                "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4",
                "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==3.5.2"
        },
        "babel": {
            "hashes": [
                "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51",
                "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==2.10.3"
        },
        "billiard": {
            "hashes": [
                "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547",
                "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"
            ],
            "version": "==3.6.4.0"
        },
        "blessed": {
            "hashes": [
                "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b",
                "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"
            ],
            "markers": "python_version >= '2.7'",
            "version": "==1.19.1"
        },
        "celery": {
            "hashes": [
                "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14",
                "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d"
            ],
            "index": "pypi",
            "version": "==5.2.7"
        },
        "certifi": {
            "hashes": [
                "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
                "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==2022.6.15"
        },
        "cfgv": {
            "hashes": [
                "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426",
                "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"
            ],
            "markers": "python_full_version >= '3.6.1'",
            "version": "==3.3.1"
        },
        "click": {
            "hashes": [
                "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
                "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==8.1.3"
        },
        "click-didyoumean": {
            "hashes": [
                "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
                "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
            ],
            "markers": "python_version < '4' and python_full_version >= '3.6.2'",
            "version": "==0.3.0"
        },
        "click-plugins": {
            "hashes": [
                "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b",
                "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"
            ],
            "version": "==1.1.1"
        },
        "click-repl": {
            "hashes": [
                "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b",
                "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"
            ],
            "version": "==0.2.0"
        },
        "distlib": {
            "hashes": [
                "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe",
                "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"
            ],
            "version": "==0.3.5"
        },
        "django": {
            "hashes": [
                "sha256:a67a793ff6827fd373555537dca0da293a63a316fe34cb7f367f898ccca3c3ae",
                "sha256:ca54ebedfcbc60d191391efbf02ba68fb52165b8bf6ccd6fe71f098cac1fe59e"
            ],
            "index": "pypi",
            "version": "==4.0.6"
        },
        "django-cors-headers": {
            "hashes": [
                "sha256:37e42883b5f1f2295df6b4bba96eb2417a14a03270cb24b2a07f021cd4487cf4",
                "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"
            ],
            "index": "pypi",
            "version": "==3.13.0"
        },
        "django-grappelli": {
            "hashes": [
                "sha256:7d60304200f4ea202862fda3b0efaa91c70280841ca22b1b22f49f1017ed2afc",
                "sha256:a98883407f995896f4c4d108ef3ef6cfc00e581211738c5a13c536950a95bcc8"
            ],
            "index": "pypi",
            "version": "==3.0.3"
        },
        "django-loginas": {
            "hashes": [
                "sha256:2d609ddcc68ff840b3e59baa58ac58b870cd1aef491aaab8cd796d022d961c40",
                "sha256:8d81f31d993b3c3c9f9018b385bfbdf74ead320e0a4b18493141d0e2dbc8ad6b"
            ],
            "index": "pypi",
            "version": "==0.3.10"
        },
        "django-model-utils": {
            "hashes": [
                "sha256:a768a25c80514e0ad4e4a6f9c02c44498985f36c5dfdea47b5b1e8cf994beba6",
                "sha256:e7a95e102f9c9653427eadab980d5d59e1dea972913b9c9e01ac37f86bba0ddf"
            ],
            "index": "pypi",
            "version": "==4.2.0"
        },
        "django-money": {
            "hashes": [
                "sha256:1f2a760d5da28de8e1beb05bfc66db0c29b4329c7e382cf69f2577315da3ee91",
                "sha256:4213604537d0e61abb5a7bbb39f0c90bd347a6605c67e96868f5fa9fbbe40278"
            ],
            "index": "pypi",
            "version": "==3.0.0"
        },
        "django-picklefield": {
            "hashes": [
                "sha256:c786cbeda78d6def2b43bff4840d19787809c8909f7ad683961703060398d356",
                "sha256:d77c504df7311e8ec14e8b779f10ca6fec74de6c7f8e2c136e1ef60cf955125d"
            ],
            "markers": "python_version >= '3'",
            "version": "==3.1"
        },
        "django-q": {
            "hashes": [
                "sha256:1b74ce3a8931990b136903e3a7bc9b07243282a2b5355117246f05ed5d076e68",
                "sha256:5c6b4d530aa3aabf9c6aa57376da1ca2abf89a1562b77038b7a04e52a4a0a91b"
            ],
            "index": "pypi",
            "version": "==1.3.9"
        },
        "djangorestframework": {
            "hashes": [
                "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee",
                "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"
            ],
            "index": "pypi",
            "version": "==3.13.1"
        },
        "djangorestframework-api-key": {
            "hashes": [
                "sha256:41b7c988f356d62410fb42b9982a3017be035b84d68f187e127c0b52e6557b19",
                "sha256:c9884e52f2802994a02781eaba0a63a628a150ed55d58603d5c9c57a6657de43"
            ],
            "index": "pypi",
            "version": "==2.2.0"
        },
        "filelock": {
            "hashes": [
                "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404",
                "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==3.7.1"
        },
        "identify": {
            "hashes": [
                "sha256:a3d4c096b384d50d5e6dc5bc8b9bc44f1f61cefebd750a7b3e9f939b53fb214d",
                "sha256:feaa9db2dc0ce333b453ce171c0cf1247bbfde2c55fc6bb785022d411a1b78b5"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==2.5.2"
        },
        "jsonfield": {
            "hashes": [
                "sha256:7e4e84597de21eeaeeaaa7cc5da08c61c48a9b64d0c446b2d71255d01812887a",
                "sha256:df857811587f252b97bafba42e02805e70a398a7a47870bc6358a0308dd689ed"
            ],
            "index": "pypi",
            "version": "==3.1.0"
        },
        "kombu": {
            "hashes": [
                "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610",
                "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==5.2.4"
        },
        "nodeenv": {
            "hashes": [
                "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e",
                "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
            "version": "==1.7.0"
        },
        "platformdirs": {
            "hashes": [
                "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788",
                "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==2.5.2"
        },
        "pre-commit": {
            "hashes": [
                "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7",
                "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"
            ],
            "index": "pypi",
            "version": "==2.20.0"
        },
        "prompt-toolkit": {
            "hashes": [
                "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0",
                "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"
            ],
            "markers": "python_full_version >= '3.6.2'",
            "version": "==3.0.30"
        },
        "psycopg2": {
            "hashes": [
                "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c",
                "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf",
                "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362",
                "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7",
                "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461",
                "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126",
                "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981",
                "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56",
                "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305",
                "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2",
                "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"
            ],
            "index": "pypi",
            "version": "==2.9.3"
        },
        "py-moneyed": {
            "hashes": [
                "sha256:1fafe552cfa3cba579d026924c27b070d71b4140e50ef4535e4083b3f4f2473f",
                "sha256:a56e1987deacb2e0eac5904552699a5d3fa251042e528bf2ff74a72359f5e5b3"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==2.0"
        },
        "python-dateutil": {
            "hashes": [
                "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
                "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
            ],
            "index": "pypi",
            "version": "==2.8.2"
        },
        "python-dotenv": {
            "hashes": [
                "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
                "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
            ],
            "index": "pypi",
            "version": "==0.20.0"
        },
        "pytz": {
            "hashes": [
                "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
                "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
            ],
            "version": "==2022.1"
        },
        "pyyaml": {
            "hashes": [
                "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
                "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
                "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
                "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
                "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
                "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
                "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
                "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
                "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
                "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
                "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
                "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
                "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
                "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
                "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
                "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
                "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
                "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
                "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
                "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
                "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
                "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
                "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
                "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
                "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
                "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
                "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
                "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
                "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
                "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
                "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
                "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
                "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==6.0"
        },
        "redis": {
            "hashes": [
                "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
                "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
            ],
            "index": "pypi",
            "version": "==3.5.3"
        },
        "sentry-sdk": {
            "hashes": [
                "sha256:60b13757d6344a94bf0ccb3c0a006c4de77daab09871b30fbbd05d5ec24e54fb",
                "sha256:f185c53496d79b280fe5d9d21e6572aee1ab802d3354eb12314d216cfbaa8d30"
            ],
            "index": "pypi",
            "version": "==1.9.0"
        },
        "setuptools": {
            "hashes": [
                "sha256:273b6847ae61f7829c1affcdd9a32f67aa65233be508f4fbaab866c5faa4e408",
                "sha256:d5340d16943a0f67057329db59b564e938bb3736c6e50ae16ea84d5e5d9ba6d0"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==63.3.0"
        },
        "six": {
            "hashes": [
                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==1.16.0"
        },
        "sqlparse": {
            "hashes": [
                "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
                "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
            ],
            "markers": "python_version >= '3.5'",
            "version": "==0.4.2"
        },
        "toml": {
            "hashes": [
                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
            ],
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==0.10.2"
        },
        "typing-extensions": {
            "hashes": [
                "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
                "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==4.3.0"
        },
        "urllib3": {
            "hashes": [
                "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc",
                "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
            "version": "==1.26.11"
        },
        "vine": {
            "hashes": [
                "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
                "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==5.0.0"
        },
        "virtualenv": {
            "hashes": [
                "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db",
                "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==20.16.2"
        },
        "wcwidth": {
            "hashes": [
                "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
                "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
            ],
            "version": "==0.2.5"
        }
    },
    "develop": {
        "astroid": {
            "hashes": [
                "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b",
                "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"
            ],
            "markers": "python_full_version >= '3.6.2'",
            "version": "==2.11.7"
        },
        "dill": {
            "hashes": [
                "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302",
                "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
            "version": "==0.3.5.1"
        },
        "isort": {
            "hashes": [
                "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
                "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
            ],
            "markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
            "version": "==5.10.1"
        },
        "lazy-object-proxy": {
            "hashes": [
                "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7",
                "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a",
                "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c",
                "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc",
                "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f",
                "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09",
                "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442",
                "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e",
                "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029",
                "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61",
                "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb",
                "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0",
                "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35",
                "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42",
                "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1",
                "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad",
                "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443",
                "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd",
                "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9",
                "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148",
                "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38",
                "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55",
                "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36",
                "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a",
                "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b",
                "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44",
                "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6",
                "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69",
                "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4",
                "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84",
                "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de",
                "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28",
                "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c",
                "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1",
                "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8",
                "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b",
                "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==1.7.1"
        },
        "mccabe": {
            "hashes": [
                "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
                "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==0.7.0"
        },
        "platformdirs": {
            "hashes": [
                "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788",
                "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==2.5.2"
        },
        "pylint": {
            "hashes": [
                "sha256:487ce2192eee48211269a0e976421f334cf94de1806ca9d0a99449adcdf0285e",
                "sha256:fabe30000de7d07636d2e82c9a518ad5ad7908590fe135ace169b44839c15f90"
            ],
            "index": "pypi",
            "version": "==2.14.5"
        },
        "setuptools": {
            "hashes": [
                "sha256:273b6847ae61f7829c1affcdd9a32f67aa65233be508f4fbaab866c5faa4e408",
                "sha256:d5340d16943a0f67057329db59b564e938bb3736c6e50ae16ea84d5e5d9ba6d0"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==63.3.0"
        },
        "tomli": {
            "hashes": [
                "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
                "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
            ],
            "markers": "python_version < '3.11'",
            "version": "==2.0.1"
        },
        "tomlkit": {
            "hashes": [
                "sha256:1c5bebdf19d5051e2e1de6cf70adfc5948d47221f097fcff7a3ffc91e953eaf5",
                "sha256:61901f81ff4017951119cd0d1ed9b7af31c821d6845c8c477587bbdcd5e5854e"
            ],
            "markers": "python_version >= '3.6' and python_version < '4.0'",
            "version": "==0.11.1"
        },
        "typing-extensions": {
            "hashes": [
                "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
                "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==4.3.0"
        },
        "wrapt": {
            "hashes": [
                "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
                "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
                "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
                "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
                "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
                "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
                "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
                "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
                "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
                "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
                "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
                "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
                "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
                "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
                "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
                "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
                "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
                "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
                "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
                "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
                "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
                "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
                "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
                "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
                "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
                "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
                "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
                "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
                "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
                "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
                "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
                "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
                "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
                "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
                "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
                "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
                "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
                "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
                "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
                "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
                "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
                "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
                "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
                "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
                "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
                "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
                "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
                "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
                "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
                "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
                "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
                "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
                "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
                "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
                "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
                "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
                "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
                "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
                "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
                "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
                "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
                "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
                "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
                "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==1.14.1"
        }
    }
}

Pipfile METASEP
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
django = "*"
djangorestframework = "*"
django-loginas = "*"
django-grappelli = "*"
django-model-utils = "*"
pre-commit = "*"
django-money = "*"
django-cors-headers = "*"
jsonfield = "*"
djangorestframework-api-key = "==2.*"
celery = ">=5.2.7,<5.3"
redis = "*"
psycopg2 = "*"
python-dateutil = "*"
django-q = "*"
python-dotenv = "*"
sentry-sdk = "*"

[dev-packages]
pylint = "*"

[requires]
python_version = "3.9"

Dockerfile METASEP
# using official Docker Python BaseImage
FROM python:3.9-bullseye


WORKDIR /usr/src/app


COPY Pipfile Pipfile.lock ./

# install pipenv in container
RUN pip install -U --no-cache-dir --disable-pip-version-check pipenv

# install the packages/dependencies required for the project
RUN pipenv install --system --deploy --ignore-pipfile
# copy all files from the <src> directory to the <dest> directory
COPY . .

EXPOSE 8000

# Run Server

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

AWSCLIV2.pkg METASEP

manage.py METASEP
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lotus.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

tenant/migrations/0001_initial.py METASEP

# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
non_informative
# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django_tenants.postgresql_backend.base
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            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"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="first name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="last name"
                    ),
                ),
                (
                    "email",
                    models.EmailField(
                        blank=True, max_length=254, verbose_name="email address"
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=200)),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "verbose_name": "user",
                "verbose_name_plural": "users",
                "abstract": False,
            },
            managers=[
                ("objects", django.contrib.auth.models.UserManager()),
            ],
        ),
        migrations.CreateModel(
            name="Tenant",
            fields=[
                (
                    "schema_name",
                    models.CharField(
                        db_index=True,
                        max_length=63,
                        unique=True,
                        validators=[
                            django_tenants.postgresql_backend.base._check_schema_name
                        ],
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=100)),
                (
                    "stripe_api_key",
                    models.CharField(blank=True, default="", max_length=100),
                ),
                (
                    "payment_plan",
                    models.CharField(
                        choices=[
                            ("self_hosted_free", "Self-Hosted Free"),
                            ("cloud", "Cloud"),
                            ("self_hosted_enterprise", "Self-Hosted Enterprise"),
                        ],
                        default="self_hosted_free",
                        max_length=40,
                    ),
                ),
                (
                    "id",
                    models.CharField(
                        default=uuid.uuid4,
                        max_length=40,
                        primary_key=True,
                        serialize=False,
                        unique=True,
                    ),
                ),
                ("created_on", models.DateField(auto_now_add=True)),
            ],
            options={
                "abstract": False,
            },
        ),
        migrations.CreateModel(
            name="Domain",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                (
                    "domain",
                    models.CharField(db_index=True, max_length=253, unique=True),
                ),
                ("is_primary", models.BooleanField(db_index=True, default=True)),
                (
                    "tenant",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        related_name="domains",
                        to="tenant.tenant",
                    ),
                ),
            ],
            options={
                "abstract": False,
            },
        ),
        migrations.CreateModel(
            name="APIToken",
            fields=[
                (
                    "id",
                    models.CharField(
                        editable=False,
                        max_length=150,
                        primary_key=True,
                        serialize=False,
                        unique=True,
                    ),
                ),
                ("prefix", models.CharField(editable=False, max_length=8, unique=True)),
                ("hashed_key", models.CharField(editable=False, max_length=150)),
                ("created", models.DateTimeField(auto_now_add=True, db_index=True)),
                (
                    "revoked",
                    models.BooleanField(
                        blank=True,
                        default=False,
                        help_text="If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)",
                    ),
                ),
                (
                    "expiry_date",
                    models.DateTimeField(
                        blank=True,
                        help_text="Once API key expires, clients cannot use it anymore.",
                        null=True,
                        verbose_name="Expires",
                    ),
                ),
                ("name", models.CharField(default="latest_token", max_length=200)),
                (
                    "user",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to=settings.AUTH_USER_MODEL,
                    ),
                ),
            ],
            options={
                "verbose_name": "API Token",
                "verbose_name_plural": "API Tokens",
            },
        ),
    ]
non_informative
non_informative
# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
non_informative
# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
non_informative
# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django_tenants.postgresql_backend.base
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            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"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="first name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="last name"
                    ),
                ),
                (
                    "email",
                    models.EmailField(
                        blank=True, max_length=254, verbose_name="email address"
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
random
# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django_tenants.postgresql_backend.base
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            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"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="first name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="last name"
                    ),
                ),
                (
                    "email",
                    models.EmailField(
                        blank=True, max_length=254, verbose_name="email address"
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=200)),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "verbose_name": "user",
                "verbose_name_plural": "users",
                "abstract": False,
            },
            managers=[
                ("objects", django.contrib.auth.models.UserManager()),
            ],
        ),
        migrations.CreateModel(
            name="Tenant",
            fields=[
                (
                    "schema_name",
                    models.CharField(
                        db_index=True,
                        max_length=63,
                        unique=True,
                        validators=[
                            django_tenants.postgresql_backend.base._check_schema_name
                        ],
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=100)),
                (
                    "stripe_api_key",
                    models.CharField(blank=True, default="", max_length=100),
                ),
                (
                    "payment_plan",
                    models.CharField(
                        choices=[
                            ("self_hosted_free", "Self-Hosted Free"),
                            ("cloud", "Cloud"),
                            ("self_hosted_enterprise", "Self-Hosted Enterprise"),
                        ],
                        default="self_hosted_free",
                        max_length=40,
                    ),
                ),
                (
                    "id",
                    models.CharField(
random
# Generated by Django 4.0.6 on 2022-08-03 19:05

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django_tenants.postgresql_backend.base
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            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"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="first name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="last name"
                    ),
                ),
                (
                    "email",
                    models.EmailField(
                        blank=True, max_length=254, verbose_name="email address"
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=200)),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "verbose_name": "user",
                "verbose_name_plural": "users",
                "abstract": False,
            },
            managers=[
                ("objects", django.contrib.auth.models.UserManager()),
            ],
        ),
        migrations.CreateModel(
            name="Tenant",
            fields=[
                (
                    "schema_name",
                    models.CharField(
                        db_index=True,
                        max_length=63,
                        unique=True,
                        validators=[
                            django_tenants.postgresql_backend.base._check_schema_name
                        ],
                    ),
                ),
                ("company_name", models.CharField(default=" ", max_length=100)),
                (
                    "stripe_api_key",
                    models.CharField(blank=True, default="", max_length=100),
                ),
                (
                    "payment_plan",
                    models.CharField(
                        choices=[
                            ("self_hosted_free", "Self-Hosted Free"),
                            ("cloud", "Cloud"),
                            ("self_hosted_enterprise", "Self-Hosted Enterprise"),
                        ],
                        default="self_hosted_free",
                        max_length=40,
                    ),
                ),
                (
                    "id",
                    models.CharField(
                        default=uuid.uuid4,
                        max_length=40,
                        primary_key=True,
                        serialize=False,
                        unique=True,
                    ),
                ),
                ("created_on", models.DateField(auto_now_add=True)),
            ],
            options={
                "abstract": False,
            },
        ),
        migrations.CreateModel(
            name="Domain",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                (
                    "domain",
                    models.CharField(db_index=True, max_length=253, unique=True),
                ),
                ("is_primary", models.BooleanField(db_index=True, default=True)),
                (
                    "tenant",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        related_name="domains",
                        to="tenant.tenant",
                    ),
                ),
            ],
            options={
                "abstract": False,
            },
        ),
        migrations.CreateModel(
            name="APIToken",
            fields=[
                (
                    "id",
                    models.CharField(
                        editable=False,
                        max_length=150,
                        primary_key=True,
                        serialize=False,
                        unique=True,
                    ),
                ),
                ("prefix", models.CharField(editable=False, max_length=8, unique=True)),
                ("hashed_key", models.CharField(editable=False, max_length=150)),
                ("created", models.DateTimeField(auto_now_add=True, db_index=True)),
                (
                    "revoked",
                    models.BooleanField(
                        blank=True,
                        default=False,
                        help_text="If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)",
                    ),
                ),
                (
                    "expiry_date",
                    models.DateTimeField(
                        blank=True,
                        help_text="Once API key expires, clients cannot use it anymore.",
                        null=True,
random
import django.contrib.auth.models # Generated by Django 4.0.6 on 2022-08-03 19:05 import django_tenants.postgresql_backend.base import django.utils.timezone                     ),                         default=uuid.uuid4,                         verbose_name="Expires",