Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added apps/custom_page/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions apps/custom_page/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CustomPageConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.custom_page"
8 changes: 8 additions & 0 deletions apps/custom_page/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.db import models


class PageTypes(models.TextChoices):
SIGN_IN = "sign_in"
SIGN_UP = "sign_up"
FORGET_PASSWORD = "forget_password" # nosec B105
CHANGE_PASSWORD = "change_password" # nosec B105
66 changes: 66 additions & 0 deletions apps/custom_page/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Generated by Django 5.0.6 on 2026-05-27 09:20

import django.db.models.deletion
import uuid
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
("organization", "0004_alter_organization_template"),
]

operations = [
migrations.CreateModel(
name="CustomPage",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"page_type",
models.CharField(
choices=[
("sign_in", "Sign In"),
("sign_up", "Sign Up"),
("forget_password", "Forget Password"),
("change_password", "Change Password"),
],
max_length=255,
),
),
("title", models.CharField(max_length=255)),
("subtitle", models.CharField(blank=True, max_length=255, null=True)),
("metadata", models.JSONField(blank=True, default=dict)),
("theme_colors", models.JSONField(blank=True, default=dict)),
(
"background_image",
models.CharField(blank=True, default="", max_length=500),
),
("show_logo", models.BooleanField(default=True)),
(
"organization",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="organization_custom_page",
to="organization.organization",
),
),
],
options={
"db_table": "custom_pages",
"ordering": ["-created_at"],
},
),
]
50 changes: 50 additions & 0 deletions apps/custom_page/migrations/0002_backfill_default_custom_pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from django.db import migrations

from apps.custom_page.service import get_default_pages


def backfill_default_custom_pages(apps, schema_editor):
CustomPage = apps.get_model("custom_page", "CustomPage")
Organization = apps.get_model("organization", "Organization")

pages_to_create = []
existing_pages = {}
for org_id, page_type in CustomPage.objects.values_list(
"organization_id", "page_type"
):
if org_id not in existing_pages:
existing_pages[org_id] = set()
existing_pages[org_id].add(page_type)

for organization in Organization.objects.all().iterator():
existing_page_types = existing_pages.get(organization.id, set())

for page in get_default_pages():
if page["page_type"] in existing_page_types:
continue

pages_to_create.append(
CustomPage(
organization_id=organization.id,
page_type=page["page_type"],
title=page["title"],
subtitle=page["subtitle"],
metadata=page["metadata"],
theme_colors=page["theme_colors"],
background_image=page["background_image"],
show_logo=page["show_logo"],
)
)

if pages_to_create:
CustomPage.objects.bulk_create(pages_to_create, batch_size=50)


class Migration(migrations.Migration):
dependencies = [
("custom_page", "0001_initial"),
]

operations = [
migrations.RunPython(backfill_default_custom_pages, migrations.RunPython.noop),
]
Empty file.
24 changes: 24 additions & 0 deletions apps/custom_page/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from common.models.base_model import BaseModel
from django.db import models

from apps.custom_page.constants import PageTypes
from apps.organization.models import Organization


class CustomPage(BaseModel):
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="organization_custom_page",
)
page_type = models.CharField(max_length=255, choices=PageTypes.choices)
title = models.CharField(max_length=255)
subtitle = models.CharField(max_length=255, null=True, blank=True)
metadata = models.JSONField(default=dict, blank=True)
theme_colors = models.JSONField(default=dict, blank=True)
background_image = models.CharField(max_length=500, blank=True, default="")
show_logo = models.BooleanField(default=True)

class Meta:
db_table = "custom_pages"
ordering = ["-created_at"]
Comment thread
ngovinh2k2 marked this conversation as resolved.
40 changes: 40 additions & 0 deletions apps/custom_page/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from common.apps.upload_file.service import get_presigned_url
from django.conf import settings
from rest_framework import serializers

from apps.custom_page.models import CustomPage


class CustomPageSerializer(serializers.ModelSerializer):
class Meta:
model = CustomPage
fields = [
"id",
"page_type",
"title",
"subtitle",
"metadata",
"theme_colors",
"background_image",
"show_logo",
"created_at",
"updated_at",
]
extra_kwargs = {
"id": {"read_only": True},
"background_image": {"write_only": True},
"created_at": {"read_only": True},
"updated_at": {"read_only": True},
}

def validate_background_image(self, value):
return value or ""

def to_representation(self, instance):
data = super().to_representation(instance)
if instance.background_image:
data["url_background_image"] = get_presigned_url(
settings.AWS_S3.get("AWS_STORAGE_BUCKET_NAME"),
f"uploads/{instance.background_image}",
)
return data
65 changes: 65 additions & 0 deletions apps/custom_page/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from apps.custom_page.constants import PageTypes
from apps.custom_page.models import CustomPage


def get_default_pages():
return [
{
"page_type": PageTypes.SIGN_IN,
"title": "Sign in to SpaceDF",
"subtitle": "Sign in to manage your IoT devices",
"metadata": {},
"theme_colors": {},
"background_image": "",
"show_logo": True,
},
{
"page_type": PageTypes.SIGN_UP,
"title": "Sign up to SpaceDF",
"subtitle": "Sign up to manage your IoT devices",
"metadata": {},
"theme_colors": {},
"background_image": "",
"show_logo": True,
},
{
"page_type": PageTypes.FORGET_PASSWORD,
"title": "Forgot Your Password?",
"subtitle": "Enter your email address and we will send you instructions to reset your password.",
"metadata": {},
"theme_colors": {
"background_color": "#FFFFFF",
},
"background_image": "",
"show_logo": True,
},
{
"page_type": PageTypes.CHANGE_PASSWORD,
"title": "Create New Password",
"subtitle": "Create your new password. If you forget it, then you have to do forget password",
"metadata": {},
"theme_colors": {
"background_color": "#FFFFFF",
},
"background_image": "",
"show_logo": True,
},
]


def create_default_pages(organization):
default_pages = get_default_pages()
list_data = [
CustomPage(
organization=organization,
page_type=page.get("page_type"),
title=page.get("title"),
subtitle=page.get("subtitle"),
metadata=page.get("metadata"),
theme_colors=page.get("theme_colors"),
background_image=page.get("background_image"),
show_logo=page.get("show_logo"),
)
for page in default_pages
]
CustomPage.objects.bulk_create(list_data)
9 changes: 9 additions & 0 deletions apps/custom_page/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path

from apps.custom_page.views import ListCustomPageView

app_name = "custom_page"

urlpatterns = [
path("custom-pages", ListCustomPageView.as_view(), name="custom-pages-list"),
]
16 changes: 16 additions & 0 deletions apps/custom_page/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from common.pagination.base_pagination import BasePagination
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter

from apps.custom_page.models import CustomPage
from apps.custom_page.serializers import CustomPageSerializer
from utils.views import OrganizationListAPIView


class ListCustomPageView(OrganizationListAPIView):
serializer_class = CustomPageSerializer
queryset = CustomPage.objects.select_related("organization").all()
organization_field = "organization"
pagination_class = BasePagination
filter_backends = [DjangoFilterBackend, OrderingFilter]
ordering = ["-created_at"]
2 changes: 2 additions & 0 deletions bootstrap_service/management/commands/init_organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from django.utils import timezone

from apps.authentication.models import RootUser
from apps.custom_page.service import create_default_pages
from apps.organization.models import Organization
from apps.organization_roles.constants import OrganizationRoleType
from apps.organization_roles.models import OrganizationRoleUser
Expand Down Expand Up @@ -117,6 +118,7 @@ def _create_organization_with_roles(self, org_id, org_name, org_slug, result):
self.stdout.write(self.style.SUCCESS(f"Created organization: {org_name}"))

create_default_policies(organization)
create_default_pages(organization)
self.stdout.write(self.style.SUCCESS("Created default policies"))

role_mappings = [
Expand Down
1 change: 1 addition & 0 deletions bootstrap_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"common.apps.refresh_tokens",
"bootstrap_service",
"apps.organization",
"apps.custom_page",
"apps.organization_roles",
"apps.authentication",
]
Expand Down
1 change: 1 addition & 0 deletions bootstrap_service/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ def health_check(_):
path("bootstrap/admin/", admin.site.urls),
# apis
path("api/bootstrap/", include("apps.authentication.urls")),
path("api/", include("apps.custom_page.urls")),
path("api/", include("apps.organization.urls")),
]
Loading