feat: add instance view

wip-payments
Aravinth Manivannan 2022-06-17 23:28:51 +05:30
parent 3705c64616
commit d15eb7ae3d
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
10 changed files with 356 additions and 24 deletions

View File

@ -1,5 +1,6 @@
from django.contrib import admin
from .models import InstanceConfiguration
from .models import InstanceConfiguration, Instance
admin.site.register(InstanceConfiguration)
admin.site.register(Instance)

View File

@ -0,0 +1,45 @@
# Generated by Django 4.0.3 on 2022-06-17 15:47
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("dash", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Instance",
fields=[
("ID", models.AutoField(primary_key=True, serialize=False)),
(
"name",
models.CharField(
max_length=32,
unique=True,
verbose_name="Name of this Instance. Also Serves as subdomain",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"configuration_id",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="dash.instanceconfiguration",
),
),
(
"owned_by",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.0.3 on 2022-06-17 17:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("dash", "0002_instance"),
]
operations = [
migrations.AlterField(
model_name="instance",
name="name",
field=models.CharField(
max_length=200,
unique=True,
verbose_name="Name of this Instance. Also Serves as subdomain",
),
),
]

View File

@ -50,3 +50,25 @@ class InstanceConfiguration(models.Model):
fields=["ram", "cpu", "storage"], name="no_repeat_config"
)
]
class Instance(models.Model):
"""
Hostea instances
"""
owned_by = models.OneToOneField(User, on_delete=models.CASCADE)
ID = models.AutoField(primary_key=True)
name = models.CharField(
"Name of this Instance. Also Serves as subdomain",
unique=True,
max_length=200,
)
configuration_id = models.ForeignKey(
InstanceConfiguration, on_delete=models.PROTECT
)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return f"{self.owned_by}'s instance '{self.name}'"

View File

@ -4,7 +4,7 @@
<p class="secondary-nav__option-link">Hello, {{ username }}!</p>
</div>
<div class="secondary-nav__options">
<a href="/foo" class="secondary-nav__option-link">Instances</a>
<a href="{% url 'dash.instances.new' %}" class="secondary-nav__option-link">Instances</a>
<details class="secondary-nav__options-group" {{ open_support }}>
<summary><a href="{% url 'support.home' %}">Support</a></summary>
<div class="secondary-nav__options-group-options">

View File

@ -0,0 +1,53 @@
{% extends 'dash/common/base.html' %} {% block dash %}
<h2>{{ title }}</h2>
<form
action="{% url 'dash.instances.new' %}"
method="POST"
class="dash__form"
accept-charset="utf-8"
>
{% include "common/components/error.html" %} {% csrf_token %}
<label class="dash__form-label" for="login">
Instance name
<input
class="form__input"
name="name"
autofocus
required
id="name"
type="text"
{% if name %}
value="{{ name }}"
{% endif %}
/>
</label>
<fieldset class="configuration">
<legend>Instance Configuration</legend>
{% for config in configurations %}
<label for="{{ config.name }}">
<div class="configuration__container">
<h3 class="configuration__name">{{ config.name }}</h3>
<ul>
<li class="configuration__spec">{{ config.cpu }} vCPU</li>
<li class="configuration__spec">{{ config.ram }} GB RAM</li>
<li class="configuration__spec">{{ config.storage }} GB Storage</li>
</ul>
<input
required
type="radio"
id="{{ config.name }}"
name="configuration"
value="{{ config.name }}"
/>
</div>
</label>
{% endfor %}
</fieldset>
<div class="form__action-container">
<button class="form__submit" type="submit">Create Instance</button>
</div>
</form>
{% endblock %}

View File

@ -19,7 +19,45 @@ from django.test import TestCase, Client, override_settings
from django.conf import settings
from django.db.utils import IntegrityError
from .models import InstanceConfiguration
from .models import InstanceConfiguration, Instance
def register_util(t: TestCase, username: str):
t.password = "password121231"
t.username = username
t.email = f"{t.username}@example.org"
t.user = get_user_model().objects.create(
username=t.username,
email=t.email,
)
t.user.set_password(t.password)
t.user.save()
def create_configurations(t: TestCase):
t.instance_config = [
InstanceConfiguration(name="Personal", ram=0.5, cpu=1, storage=25),
InstanceConfiguration(name="Enthusiast", ram=2, cpu=2, storage=50),
InstanceConfiguration(name="Small Business", ram=8, cpu=4, storage=64),
InstanceConfiguration(name="Enterprise", ram=64, cpu=24, storage=1024),
]
for instance in t.instance_config:
instance.save()
print(f"[*][init] Instance {instance.name} is saved")
t.assertEqual(
InstanceConfiguration.objects.filter(name=instance.name).exists(), True
)
def login_util(t: TestCase, c: Client, redirect_to: str):
payload = {
"login": t.username,
"password": t.password,
}
resp = c.post(reverse("accounts.login"), payload)
t.assertEqual(resp.status_code, 302)
t.assertEqual(resp.headers["location"], reverse(redirect_to))
class DashHome(TestCase):
@ -28,15 +66,7 @@ class DashHome(TestCase):
"""
def setUp(self):
self.password = "password121231"
self.username = "dashboard_home_user"
self.email = f"{self.username}@example.org"
self.user = get_user_model().objects.create(
username=self.username,
email=self.email,
)
self.user.set_password(self.password)
self.user.save()
register_util(t=self, username="dashboard_home_user")
def test_dash_is_protected(self):
"""
@ -60,13 +90,7 @@ class DashHome(TestCase):
c = Client()
# username login works
payload = {
"login": self.username,
"password": self.password,
}
resp = c.post(reverse("accounts.login"), payload)
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.headers["location"], reverse("accounts.home"))
login_util(t=self, c=c, redirect_to="accounts.home")
# email login works
resp = c.get(reverse("dash.home"))
@ -98,3 +122,54 @@ class InstancesConfig(TestCase):
name="test config 3", ram=0.5, cpu=1, storage=0.5
)
config3.save()
class CreateInstance(TestCase):
def setUp(self):
register_util(t=self, username="createinstance_user")
create_configurations(t=self)
def test_create_instance_renders(self):
c = Client()
login_util(self, c, "accounts.home")
urls = [(reverse("dash.instances.new"), "Instance Configuration")]
for (url, test) in urls:
print(f"[*] Testing URI: {url}")
resp = c.get(url)
self.assertEqual(resp.status_code, 200)
self.assertEqual(b"Billing" in resp.content, True)
self.assertEqual(b"Support" in resp.content, True)
self.assertEqual(b"Logout" in resp.content, True)
self.assertEqual(str.encode(test) in resp.content, True)
payload = {
"name": "test_create_instance_renders",
"configuration": self.instance_config[0].name,
}
self.assertEqual(Instance.objects.filter(name=payload["name"]).exists(), False)
resp = c.post(reverse("dash.instances.new"), payload)
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.headers["location"], reverse("dash.home"))
self.assertEqual(
Instance.objects.filter(
name=payload["name"],
owned_by=self.user,
configuration_id=self.instance_config[0],
).exists(),
True,
)
resp = c.post(reverse("dash.instances.new"), payload)
self.assertEqual(resp.status_code, 400)
self.assertEqual(b"Instance name exists" in resp.content, True)
payload = {
"name": f"2{payload['name']}",
"configuration": f"2{payload['name']}",
}
resp = c.post(reverse("dash.instances.new"), payload)
self.assertEqual(resp.status_code, 400)
self.assertEqual(b"Configuration doesn" in resp.content, True)

View File

@ -15,8 +15,9 @@
from django.contrib import admin
from django.urls import path, include
from .views import home
from .views import home, create_instance
urlpatterns = [
path("instance/new/", create_instance, name="dash.instances.new"),
path("", home, name="dash.home"),
]

View File

@ -21,6 +21,8 @@ from django.http import HttpResponse
from django.views.decorators.csrf import csrf_protect
from django.urls import reverse
from .models import Instance, InstanceConfiguration
def default_ctx(title: str, username: str):
"""
@ -38,7 +40,58 @@ def home(request):
Dashboard homepage view
"""
PAGE_TITLE = "Home"
username = request.user
ctx = default_ctx(title=PAGE_TITLE, username=username.username)
user = request.user
ctx = default_ctx(title=PAGE_TITLE, username=user.username)
instances = Instance.objects.filter(owned_by=user)
if instances:
ctx["instances"] = instances
return render(request, "dash/home/index.html", context=ctx)
@login_required
@csrf_protect
def create_instance(request):
"""
Create instance view
"""
PAGE_TITLE = "Create Instance"
user = request.user
def get_ctx():
ctx = default_ctx(title=PAGE_TITLE, username=user.username)
configurations = InstanceConfiguration.objects.all()
ctx["configurations"] = InstanceConfiguration.objects.all()
return ctx
if request.method == "GET":
ctx = get_ctx()
return render(request, "dash/instances/new/index.html", context=ctx)
name = request.POST["name"]
if Instance.objects.filter(name=name).exists():
ctx = get_ctx()
ctx["error"] = {
"title": "Can't create instance",
"reason": "Instance name exists, please try again with a different name",
}
print(ctx["error"]["reason"])
return render(request, "dash/instances/new/index.html", status=400, context=ctx)
configuration = request.POST["configuration"]
if not InstanceConfiguration.objects.filter(name=configuration).exists():
ctx = get_ctx()
ctx["error"] = {
"title": "Can't create instance",
"reason": "Configuration doesn't exist, please try again.",
}
print(ctx["error"]["reason"])
return render(request, "dash/instances/new/index.html", status=400, context=ctx)
configuration = get_object_or_404(InstanceConfiguration, name=configuration)
instance = Instance(
name=name, configuration_id=configuration, owned_by=request.user
)
instance.save()
return redirect(reverse("dash.home"))

View File

@ -522,7 +522,6 @@ footer {
content: " ►";
}
.secondary-nav__options-group[open] > summary::after {
margin-left: 30px;
content: " ▼";
@ -531,11 +530,72 @@ footer {
/* secondary nav ends */
.dash__main {
flex: 2;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: calc(-63px + 100vh);
flex:2;
}
/* dash forms starts */
.dash__form {
display: flex;
flex-direction: column;
width: 50%;
margin: auto;
background-color: ping;
padding: 0 10px;
}
fieldset {
border: none;
}
.configuration__container {
padding: 20px;
padding-left: 0;
margin: 10px 20px;
margin-left: 0;
border: 1px solid rgb(211, 211, 211);
display: inline-flex;
flex-direction: column;
}
.configuration__name {
margin-left: 20px;
}
.configuration__spec {
margin-left: 40px;
}
/*
.form__label {
margin: 5px 0;
}
.form__input {
display: block;
width: 100%;
margin: 10px 0;
padding: 5px 0;
height: 25px;
}
.form__submit {
width: 100%;
display: block;
margin: 10px 0;
color: #fff;
border: none;
font-weight: 400;
padding: 15px;
cursor: pointer;
background-color: rgb(0, 128, 0);
}
*/
/* dash forms ends */
/* dashbaord ends */