mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 00:59:06 +00:00
Resolve "Allow to create sub domains"
This commit is contained in:
parent
645eb09ed4
commit
55533d021e
41 changed files with 865 additions and 175 deletions
backend
src/baserow
tests/baserow/contrib/builder
docs/installation
web-frontend/modules
builder
core
|
@ -1184,6 +1184,11 @@ if POSTHOG_ENABLED:
|
|||
else:
|
||||
posthog.disabled = True
|
||||
|
||||
BASEROW_BUILDER_DOMAINS = os.getenv("BASEROW_BUILDER_DOMAINS", None)
|
||||
BASEROW_BUILDER_DOMAINS = (
|
||||
BASEROW_BUILDER_DOMAINS.split(",") if BASEROW_BUILDER_DOMAINS is not None else []
|
||||
)
|
||||
|
||||
# Indicates whether we are running the tests or not. Set to True in the test.py settings
|
||||
# file used by pytest.ini
|
||||
TESTS = False
|
||||
|
|
|
@ -11,3 +11,16 @@ ERROR_DOMAIN_NOT_IN_BUILDER = (
|
|||
HTTP_400_BAD_REQUEST,
|
||||
"The domain id {e.domain_id} does not belong to the builder.",
|
||||
)
|
||||
|
||||
ERROR_DOMAIN_NAME_NOT_UNIQUE = (
|
||||
"ERROR_DOMAIN_NAME_NOT_UNIQUE",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The domain name {e.domain_name} already exists.",
|
||||
)
|
||||
|
||||
ERROR_SUB_DOMAIN_HAS_INVALID_DOMAIN_NAME = (
|
||||
"ERROR_SUB_DOMAIN_HAS_INVALID_DOMAIN_NAME",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The subdomain {e.domain_name} has an invalid domain name, "
|
||||
"you can only use {e.available_domain_names}",
|
||||
)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from typing import List
|
||||
|
||||
from django.utils.functional import lazy
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
@ -8,6 +10,7 @@ from baserow.api.services.serializers import PublicServiceSerializer
|
|||
from baserow.contrib.builder.api.pages.serializers import PathParamSerializer
|
||||
from baserow.contrib.builder.data_sources.models import DataSource
|
||||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.registries import domain_type_registry
|
||||
from baserow.contrib.builder.elements.models import Element
|
||||
from baserow.contrib.builder.elements.registries import element_type_registry
|
||||
from baserow.contrib.builder.models import Builder
|
||||
|
@ -16,9 +19,15 @@ from baserow.core.services.registries import service_type_registry
|
|||
|
||||
|
||||
class DomainSerializer(serializers.ModelSerializer):
|
||||
type = serializers.SerializerMethodField(help_text="The type of the domain.")
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_type(self, instance):
|
||||
return domain_type_registry.get_by_model(instance.specific_class).type
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ("id", "domain_name", "order", "builder_id", "last_published")
|
||||
fields = ("id", "type", "domain_name", "order", "builder_id", "last_published")
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
"builder_id": {"read_only": True},
|
||||
|
@ -27,9 +36,37 @@ class DomainSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class CreateDomainSerializer(serializers.ModelSerializer):
|
||||
type = serializers.ChoiceField(
|
||||
choices=lazy(domain_type_registry.get_types, list)(),
|
||||
required=True,
|
||||
help_text="The type of the domain.",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ("domain_name",)
|
||||
fields = (
|
||||
"type",
|
||||
"domain_name",
|
||||
)
|
||||
|
||||
|
||||
class UpdateDomainSerializer(serializers.ModelSerializer):
|
||||
type = serializers.ChoiceField(
|
||||
choices=lazy(domain_type_registry.get_types, list)(),
|
||||
required=False,
|
||||
help_text="The type of the domain.",
|
||||
)
|
||||
|
||||
domain_name = serializers.CharField(
|
||||
required=False, help_text=Domain._meta.get_field("domain_name").help_text
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = (
|
||||
"type",
|
||||
"domain_name",
|
||||
)
|
||||
|
||||
|
||||
class OrderDomainsSerializer(serializers.Serializer):
|
||||
|
|
|
@ -10,29 +10,43 @@ from rest_framework.status import HTTP_202_ACCEPTED
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from baserow.api.applications.errors import ERROR_APPLICATION_DOES_NOT_EXIST
|
||||
from baserow.api.decorators import map_exceptions, validate_body
|
||||
from baserow.api.decorators import (
|
||||
map_exceptions,
|
||||
validate_body,
|
||||
validate_body_custom_fields,
|
||||
)
|
||||
from baserow.api.jobs.serializers import JobSerializer
|
||||
from baserow.api.schemas import CLIENT_SESSION_ID_SCHEMA_PARAMETER, get_error_schema
|
||||
from baserow.api.utils import DiscriminatorCustomFieldsMappingSerializer
|
||||
from baserow.api.utils import (
|
||||
DiscriminatorCustomFieldsMappingSerializer,
|
||||
type_from_data_or_registry,
|
||||
validate_data_custom_fields,
|
||||
)
|
||||
from baserow.contrib.builder.api.data_sources.serializers import DataSourceSerializer
|
||||
from baserow.contrib.builder.api.domains.errors import (
|
||||
ERROR_DOMAIN_DOES_NOT_EXIST,
|
||||
ERROR_DOMAIN_NAME_NOT_UNIQUE,
|
||||
ERROR_DOMAIN_NOT_IN_BUILDER,
|
||||
ERROR_SUB_DOMAIN_HAS_INVALID_DOMAIN_NAME,
|
||||
)
|
||||
from baserow.contrib.builder.api.domains.serializers import (
|
||||
CreateDomainSerializer,
|
||||
DomainSerializer,
|
||||
OrderDomainsSerializer,
|
||||
PublicBuilderSerializer,
|
||||
UpdateDomainSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.api.pages.errors import ERROR_PAGE_DOES_NOT_EXIST
|
||||
from baserow.contrib.builder.data_sources.service import DataSourceService
|
||||
from baserow.contrib.builder.domains.exceptions import (
|
||||
DomainDoesNotExist,
|
||||
DomainNameNotUniqueError,
|
||||
DomainNotInBuilder,
|
||||
SubDomainHasInvalidDomainName,
|
||||
)
|
||||
from baserow.contrib.builder.domains.handler import DomainHandler
|
||||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.registries import domain_type_registry
|
||||
from baserow.contrib.builder.domains.service import DomainService
|
||||
from baserow.contrib.builder.elements.registries import element_type_registry
|
||||
from baserow.contrib.builder.elements.service import ElementService
|
||||
|
@ -66,9 +80,13 @@ class DomainsView(APIView):
|
|||
tags=["Builder domains"],
|
||||
operation_id="create_builder_domain",
|
||||
description="Creates a new domain for an application builder",
|
||||
request=CreateDomainSerializer,
|
||||
request=DiscriminatorCustomFieldsMappingSerializer(
|
||||
domain_type_registry, CreateDomainSerializer, request=True
|
||||
),
|
||||
responses={
|
||||
200: DomainSerializer,
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
domain_type_registry, DomainSerializer
|
||||
),
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_NOT_IN_GROUP",
|
||||
|
@ -82,17 +100,22 @@ class DomainsView(APIView):
|
|||
@map_exceptions(
|
||||
{
|
||||
ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST,
|
||||
SubDomainHasInvalidDomainName: ERROR_SUB_DOMAIN_HAS_INVALID_DOMAIN_NAME,
|
||||
}
|
||||
)
|
||||
@validate_body(CreateDomainSerializer)
|
||||
@validate_body_custom_fields(
|
||||
domain_type_registry, base_serializer_class=CreateDomainSerializer
|
||||
)
|
||||
def post(self, request, data: Dict, builder_id: int):
|
||||
builder = BuilderHandler().get_builder(builder_id)
|
||||
type_name = data.pop("type")
|
||||
|
||||
domain_type = domain_type_registry.get(type_name)
|
||||
domain = DomainService().create_domain(
|
||||
request.user, builder, data["domain_name"]
|
||||
request.user, domain_type, builder, **data
|
||||
)
|
||||
|
||||
serializer = DomainSerializer(domain)
|
||||
serializer = domain_type_registry.get_serializer(domain, DomainSerializer)
|
||||
return Response(serializer.data)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -109,7 +132,9 @@ class DomainsView(APIView):
|
|||
operation_id="get_builder_domains",
|
||||
description="Gets all the domains of a builder",
|
||||
responses={
|
||||
200: DomainSerializer(many=True),
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
domain_type_registry, DomainSerializer, many=True
|
||||
),
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_NOT_IN_GROUP",
|
||||
|
@ -132,9 +157,12 @@ class DomainsView(APIView):
|
|||
builder,
|
||||
)
|
||||
|
||||
serializer = DomainSerializer(domains, many=True)
|
||||
data = [
|
||||
domain_type_registry.get_serializer(domain, DomainSerializer).data
|
||||
for domain in domains
|
||||
]
|
||||
|
||||
return Response(serializer.data)
|
||||
return Response(data)
|
||||
|
||||
|
||||
class DomainView(APIView):
|
||||
|
@ -151,9 +179,11 @@ class DomainView(APIView):
|
|||
tags=["Builder domains"],
|
||||
operation_id="update_builder_domain",
|
||||
description="Updates an existing domain of an application builder",
|
||||
request=CreateDomainSerializer,
|
||||
request=UpdateDomainSerializer,
|
||||
responses={
|
||||
200: DomainSerializer,
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
domain_type_registry, DomainSerializer
|
||||
),
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_NOT_IN_GROUP",
|
||||
|
@ -167,19 +197,35 @@ class DomainView(APIView):
|
|||
@map_exceptions(
|
||||
{
|
||||
DomainDoesNotExist: ERROR_DOMAIN_DOES_NOT_EXIST,
|
||||
DomainNameNotUniqueError: ERROR_DOMAIN_NAME_NOT_UNIQUE,
|
||||
SubDomainHasInvalidDomainName: ERROR_SUB_DOMAIN_HAS_INVALID_DOMAIN_NAME,
|
||||
}
|
||||
)
|
||||
@validate_body(CreateDomainSerializer)
|
||||
def patch(self, request, data: Dict, domain_id: int):
|
||||
base_queryset = Domain.objects.select_for_update(of=("self",))
|
||||
def patch(self, request, domain_id: int):
|
||||
base_queryset = Domain.objects
|
||||
|
||||
domain = DomainService().get_domain(
|
||||
request.user, domain_id, base_queryset=base_queryset
|
||||
domain = (
|
||||
DomainService()
|
||||
.get_domain(request.user, domain_id, base_queryset=base_queryset)
|
||||
.specific
|
||||
)
|
||||
domain_type = type_from_data_or_registry(
|
||||
request.data, domain_type_registry, domain
|
||||
)
|
||||
|
||||
data = validate_data_custom_fields(
|
||||
domain_type.type,
|
||||
domain_type_registry,
|
||||
request.data,
|
||||
base_serializer_class=UpdateDomainSerializer,
|
||||
partial=True,
|
||||
)
|
||||
|
||||
domain_updated = DomainService().update_domain(request.user, domain, **data)
|
||||
|
||||
serializer = DomainSerializer(domain_updated)
|
||||
serializer = domain_type_registry.get_serializer(
|
||||
domain_updated, DomainSerializer
|
||||
)
|
||||
return Response(serializer.data)
|
||||
|
||||
@extend_schema(
|
||||
|
|
|
@ -151,6 +151,12 @@ class BuilderConfig(AppConfig):
|
|||
element_type_registry.register(InputTextElementType())
|
||||
element_type_registry.register(ColumnElementType())
|
||||
|
||||
from .domains.domain_types import CustomDomainType, SubDomainType
|
||||
from .domains.registries import domain_type_registry
|
||||
|
||||
domain_type_registry.register(CustomDomainType())
|
||||
domain_type_registry.register(SubDomainType())
|
||||
|
||||
from .domains.trash_types import DomainTrashableItemType
|
||||
|
||||
trash_item_type_registry.register(DomainTrashableItemType())
|
||||
|
|
41
backend/src/baserow/contrib/builder/domains/domain_types.py
Normal file
41
backend/src/baserow/contrib/builder/domains/domain_types.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from typing import Dict
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from baserow.contrib.builder.domains.exceptions import SubDomainHasInvalidDomainName
|
||||
from baserow.contrib.builder.domains.models import CustomDomain, SubDomain
|
||||
from baserow.contrib.builder.domains.registries import DomainType
|
||||
|
||||
|
||||
class CustomDomainType(DomainType):
|
||||
type = "custom"
|
||||
model_class = CustomDomain
|
||||
|
||||
|
||||
class SubDomainType(DomainType):
|
||||
type = "sub_domain"
|
||||
model_class = SubDomain
|
||||
|
||||
def prepare_values(self, values: Dict) -> Dict:
|
||||
domain_name = values.get("domain_name", None)
|
||||
|
||||
if domain_name is not None:
|
||||
self._validate_domain_name(domain_name)
|
||||
|
||||
return values
|
||||
|
||||
def _validate_domain_name(self, domain_name: str):
|
||||
"""
|
||||
Checks if the subdomain uses a domain name that is among the available domain
|
||||
names defined in settings.
|
||||
|
||||
:param domain_name: The name that is being proposed
|
||||
:raises SubDomainHasInvalidDomainName: If the domain name is not registered
|
||||
"""
|
||||
|
||||
for domain in settings.BASEROW_BUILDER_DOMAINS:
|
||||
if domain_name.endswith(f".{domain}"):
|
||||
# The domain suffix is valid
|
||||
return
|
||||
|
||||
raise SubDomainHasInvalidDomainName(domain_name)
|
|
@ -1,3 +1,6 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
class DomainDoesNotExist(Exception):
|
||||
"""Raised when trying to get a domain that doesn't exist."""
|
||||
|
||||
|
@ -12,3 +15,27 @@ class DomainNotInBuilder(Exception):
|
|||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class DomainNameNotUniqueError(Exception):
|
||||
"""Raised when trying to set a domain name that already exists"""
|
||||
|
||||
def __init__(self, domain_name, *args, **kwargs):
|
||||
self.domain_name = domain_name
|
||||
super().__init__(
|
||||
f"The domain name {domain_name} already exists", *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class SubDomainHasInvalidDomainName(Exception):
|
||||
"""Raised when a subdomain is using an invalid domain name"""
|
||||
|
||||
def __init__(self, domain_name, *args, **kwargs):
|
||||
self.domain_name = domain_name
|
||||
self.available_domain_names = settings.BASEROW_BUILDER_DOMAINS
|
||||
super().__init__(
|
||||
f"The subdomain {domain_name} has an invalid domain name, you can only use "
|
||||
f"{self.available_domain_names}",
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
from typing import List
|
||||
from typing import Iterable, List, cast
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db.models import QuerySet
|
||||
from django.db.utils import IntegrityError
|
||||
from django.utils import timezone
|
||||
|
||||
from baserow.contrib.builder.domains.exceptions import (
|
||||
DomainDoesNotExist,
|
||||
DomainNameNotUniqueError,
|
||||
DomainNotInBuilder,
|
||||
)
|
||||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.registries import DomainType
|
||||
from baserow.contrib.builder.exceptions import BuilderDoesNotExist
|
||||
from baserow.contrib.builder.models import Builder
|
||||
from baserow.core.db import specific_iterator
|
||||
from baserow.core.exceptions import IdDoesNotExist
|
||||
from baserow.core.registries import ImportExportConfig, application_type_registry
|
||||
from baserow.core.trash.handler import TrashHandler
|
||||
from baserow.core.utils import Progress
|
||||
from baserow.core.utils import Progress, extract_allowed
|
||||
|
||||
|
||||
class DomainHandler:
|
||||
allowed_fields_create = ["domain_name"]
|
||||
allowed_fields_update = ["domain_name", "last_published"]
|
||||
|
||||
def get_domain(self, domain_id: int, base_queryset: QuerySet = None) -> Domain:
|
||||
"""
|
||||
Gets a domain by ID
|
||||
|
@ -39,20 +46,20 @@ class DomainHandler:
|
|||
|
||||
def get_domains(
|
||||
self, builder: Builder, base_queryset: QuerySet = None
|
||||
) -> QuerySet[Domain]:
|
||||
) -> Iterable[Domain]:
|
||||
"""
|
||||
Gets all the domains of a builder.
|
||||
|
||||
:param builder: The builder we are trying to get all domains for
|
||||
:param base_queryset: Can be provided to already filter or apply performance
|
||||
improvements to the queryset when it's being executed
|
||||
:return: A queryset with all the domains
|
||||
:return: An iterable of all the specific domains
|
||||
"""
|
||||
|
||||
if base_queryset is None:
|
||||
base_queryset = Domain.objects
|
||||
|
||||
return base_queryset.filter(builder=builder)
|
||||
return specific_iterator(base_queryset.filter(builder=builder))
|
||||
|
||||
def get_public_builder_by_domain_name(self, domain_name: str) -> Builder:
|
||||
"""
|
||||
|
@ -78,20 +85,31 @@ class DomainHandler:
|
|||
|
||||
return domain.published_to
|
||||
|
||||
def create_domain(self, builder: Builder, domain_name: str) -> Domain:
|
||||
def create_domain(
|
||||
self, domain_type: DomainType, builder: Builder, **kwargs
|
||||
) -> Domain:
|
||||
"""
|
||||
Creates a new domain
|
||||
|
||||
:param domain_type: The type of domain that's being created
|
||||
:param builder: The builder the domain belongs to
|
||||
:param domain_name: The name of the domain
|
||||
:param kwargs: Additional attributes of the domain
|
||||
:return: The newly created domain instance
|
||||
"""
|
||||
|
||||
last_order = Domain.get_last_order(builder)
|
||||
domain = Domain.objects.create(
|
||||
builder=builder, domain_name=domain_name, order=last_order
|
||||
|
||||
model_class = cast(Domain, domain_type.model_class)
|
||||
|
||||
allowed_values = extract_allowed(
|
||||
kwargs, self.allowed_fields_create + domain_type.allowed_fields
|
||||
)
|
||||
|
||||
prepared_values = domain_type.prepare_values(allowed_values)
|
||||
|
||||
domain = model_class(builder=builder, order=last_order, **prepared_values)
|
||||
domain.save()
|
||||
|
||||
return domain
|
||||
|
||||
def delete_domain(self, domain: Domain):
|
||||
|
@ -112,10 +130,23 @@ class DomainHandler:
|
|||
:return: The updated domain
|
||||
"""
|
||||
|
||||
for key, value in kwargs.items():
|
||||
domain_type = domain.get_type()
|
||||
|
||||
allowed_values = extract_allowed(
|
||||
kwargs, self.allowed_fields_update + domain_type.allowed_fields
|
||||
)
|
||||
|
||||
prepared_values = domain_type.prepare_values(allowed_values)
|
||||
|
||||
for key, value in prepared_values.items():
|
||||
setattr(domain, key, value)
|
||||
|
||||
domain.save()
|
||||
try:
|
||||
domain.save()
|
||||
except IntegrityError as error:
|
||||
if "unique" in str(error) and "domain_name" in prepared_values:
|
||||
raise DomainNameNotUniqueError(prepared_values["domain_name"])
|
||||
raise error
|
||||
|
||||
return domain
|
||||
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db.models import CASCADE, SET_NULL
|
||||
|
||||
import validators
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from baserow.contrib.builder.domains.registries import domain_type_registry
|
||||
from baserow.core.jobs.mixins import JobWithUserIpAddress
|
||||
from baserow.core.jobs.models import Job
|
||||
from baserow.core.mixins import (
|
||||
HierarchicalModelMixin,
|
||||
OrderableMixin,
|
||||
PolymorphicContentTypeMixin,
|
||||
TrashableModelMixin,
|
||||
WithRegistry,
|
||||
)
|
||||
from baserow.core.registry import ModelRegistryMixin
|
||||
|
||||
|
||||
def validate_domain(value: str):
|
||||
|
@ -29,7 +34,24 @@ def validate_domain(value: str):
|
|||
raise ValidationError("Invalid domain syntax")
|
||||
|
||||
|
||||
class Domain(HierarchicalModelMixin, TrashableModelMixin, OrderableMixin, models.Model):
|
||||
def get_default_domain_content_type():
|
||||
return ContentType.objects.get_for_model(CustomDomain)
|
||||
|
||||
|
||||
class Domain(
|
||||
HierarchicalModelMixin,
|
||||
TrashableModelMixin,
|
||||
OrderableMixin,
|
||||
WithRegistry,
|
||||
PolymorphicContentTypeMixin,
|
||||
models.Model,
|
||||
):
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
verbose_name="content type",
|
||||
related_name="builder_domains",
|
||||
on_delete=models.SET(get_default_domain_content_type),
|
||||
)
|
||||
builder = models.ForeignKey(
|
||||
"builder.Builder",
|
||||
on_delete=CASCADE,
|
||||
|
@ -63,6 +85,18 @@ class Domain(HierarchicalModelMixin, TrashableModelMixin, OrderableMixin, models
|
|||
queryset = Domain.objects.filter(builder=builder)
|
||||
return cls.get_highest_order_of_queryset(queryset) + 1
|
||||
|
||||
@staticmethod
|
||||
def get_type_registry() -> ModelRegistryMixin:
|
||||
return domain_type_registry
|
||||
|
||||
|
||||
class CustomDomain(Domain):
|
||||
pass
|
||||
|
||||
|
||||
class SubDomain(Domain):
|
||||
pass
|
||||
|
||||
|
||||
class PublishDomainJob(JobWithUserIpAddress, Job):
|
||||
domain: Domain = models.ForeignKey(Domain, null=True, on_delete=models.SET_NULL)
|
||||
|
|
34
backend/src/baserow/contrib/builder/domains/registries.py
Normal file
34
backend/src/baserow/contrib/builder/domains/registries.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from abc import ABC
|
||||
from typing import Dict
|
||||
|
||||
from baserow.core.registry import (
|
||||
CustomFieldsInstanceMixin,
|
||||
CustomFieldsRegistryMixin,
|
||||
Instance,
|
||||
ModelInstanceMixin,
|
||||
ModelRegistryMixin,
|
||||
Registry,
|
||||
)
|
||||
|
||||
|
||||
class DomainType(Instance, ModelInstanceMixin, CustomFieldsInstanceMixin, ABC):
|
||||
def prepare_values(self, values: Dict) -> Dict:
|
||||
"""
|
||||
Called before a domain is saved/updates to validate the data or transform the
|
||||
data before it is being saved.
|
||||
:param values: The values that are about to be saved
|
||||
:return: The values after validation/transformation
|
||||
"""
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class DomainTypeRegistry(Registry, ModelRegistryMixin, CustomFieldsRegistryMixin):
|
||||
"""
|
||||
Contains all the registered domain types.
|
||||
"""
|
||||
|
||||
name = "domain_type"
|
||||
|
||||
|
||||
domain_type_registry = DomainTypeRegistry()
|
|
@ -29,6 +29,7 @@ from baserow.core.utils import Progress, extract_allowed
|
|||
|
||||
from .job_types import PublishDomainJobType
|
||||
from .operations import PublishDomainOperationType
|
||||
from .registries import DomainType
|
||||
|
||||
|
||||
class DomainService:
|
||||
|
@ -121,14 +122,19 @@ class DomainService:
|
|||
return builder
|
||||
|
||||
def create_domain(
|
||||
self, user: AbstractUser, builder: Builder, domain_name: str
|
||||
self,
|
||||
user: AbstractUser,
|
||||
domain_type: DomainType,
|
||||
builder: Builder,
|
||||
**kwargs,
|
||||
) -> Domain:
|
||||
"""
|
||||
Creates a new domain
|
||||
|
||||
:param user: The user trying to create the domain
|
||||
:param domain_type: The type of domain that's being created
|
||||
:param builder: The builder the domain belongs to
|
||||
:param domain_name: The name of the domain
|
||||
:param kwargs: Additional attributes of the domain
|
||||
:return: The newly created domain instance
|
||||
"""
|
||||
|
||||
|
@ -139,7 +145,7 @@ class DomainService:
|
|||
context=builder,
|
||||
)
|
||||
|
||||
domain = self.handler.create_domain(builder, domain_name)
|
||||
domain = self.handler.create_domain(domain_type, builder, **kwargs)
|
||||
|
||||
domain_created.send(self, domain=domain, user=user)
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# Generated by Django 3.2.21 on 2023-09-14 13:34
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import baserow.contrib.builder.domains.models
|
||||
|
||||
|
||||
def get_default_domain_content_type_id():
|
||||
return baserow.contrib.builder.domains.models.get_default_domain_content_type().id
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("builder", "0017_mainthemeconfigblock"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CustomDomain",
|
||||
fields=[
|
||||
(
|
||||
"domain_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="builder.domain",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("builder.domain",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SubDomain",
|
||||
fields=[
|
||||
(
|
||||
"domain_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="builder.domain",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("builder.domain",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="domain",
|
||||
name="content_type",
|
||||
field=models.ForeignKey(
|
||||
default=get_default_domain_content_type_id,
|
||||
on_delete=models.SET(
|
||||
baserow.contrib.builder.domains.models.get_default_domain_content_type
|
||||
),
|
||||
related_name="builder_domains",
|
||||
to="contenttypes.contenttype",
|
||||
verbose_name="content type",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,34 @@
|
|||
from django.db import migrations, transaction
|
||||
|
||||
from baserow.contrib.builder.domains.models import CustomDomain, Domain
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
"""
|
||||
This migration introduces polymorphism, so we need to decide which type of
|
||||
domain all the existing domains now become. In this case we are turning
|
||||
all the existing domains into custom domains.
|
||||
"""
|
||||
|
||||
with transaction.atomic():
|
||||
domains = Domain.objects.all()
|
||||
|
||||
for domain in domains:
|
||||
domain.delete()
|
||||
CustomDomain.objects.create(
|
||||
domain_name=domain.domain_name,
|
||||
order=domain.order,
|
||||
builder=domain.builder,
|
||||
)
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
...
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("builder", "0018_sub_domains")]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse),
|
||||
]
|
|
@ -4,7 +4,7 @@ from django.db import transaction
|
|||
from faker import Faker
|
||||
|
||||
from baserow.contrib.builder.data_sources.handler import DataSourceHandler
|
||||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.models import CustomDomain
|
||||
from baserow.contrib.builder.elements.handler import ElementHandler
|
||||
from baserow.contrib.builder.elements.registries import element_type_registry
|
||||
from baserow.contrib.builder.models import Builder
|
||||
|
@ -38,12 +38,18 @@ def load_test_data():
|
|||
user, workspace, "builder", name="Back to local website"
|
||||
)
|
||||
|
||||
Domain.objects.filter(domain_name="test1.getbaserow.io").delete()
|
||||
Domain.objects.filter(domain_name="test2.getbaserow.io").delete()
|
||||
Domain.objects.filter(domain_name="test3.getbaserow.io").delete()
|
||||
Domain.objects.create(builder=builder, domain_name="test1.getbaserow.io", order=1)
|
||||
Domain.objects.create(builder=builder, domain_name="test2.getbaserow.io", order=2)
|
||||
Domain.objects.create(builder=builder, domain_name="test3.getbaserow.io", order=3)
|
||||
CustomDomain.objects.filter(domain_name="test1.getbaserow.io").delete()
|
||||
CustomDomain.objects.filter(domain_name="test2.getbaserow.io").delete()
|
||||
CustomDomain.objects.filter(domain_name="test3.getbaserow.io").delete()
|
||||
CustomDomain.objects.create(
|
||||
builder=builder, domain_name="test1.getbaserow.io", order=1
|
||||
)
|
||||
CustomDomain.objects.create(
|
||||
builder=builder, domain_name="test2.getbaserow.io", order=2
|
||||
)
|
||||
CustomDomain.objects.create(
|
||||
builder=builder, domain_name="test3.getbaserow.io", order=3
|
||||
)
|
||||
|
||||
integration_type = integration_type_registry.get("local_baserow")
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.models import CustomDomain, SubDomain
|
||||
|
||||
|
||||
class DomainFixtures:
|
||||
def create_builder_domain(self, user=None, **kwargs):
|
||||
def create_builder_custom_domain(self, user=None, **kwargs):
|
||||
return self.create_builder_domain(CustomDomain, user, **kwargs)
|
||||
|
||||
def create_builder_sub_domain(self, user=None, **kwargs):
|
||||
return self.create_builder_domain(SubDomain, user, **kwargs)
|
||||
|
||||
def create_builder_domain(self, model_class, user=None, **kwargs):
|
||||
if user is None:
|
||||
user = self.create_user()
|
||||
|
||||
|
@ -15,6 +21,6 @@ class DomainFixtures:
|
|||
if "order" not in kwargs:
|
||||
kwargs["order"] = 0
|
||||
|
||||
domain = Domain.objects.create(**kwargs)
|
||||
domain = model_class.objects.create(**kwargs)
|
||||
|
||||
return domain
|
||||
|
|
|
@ -18,7 +18,7 @@ def test_get_public_builder_by_domain_name(api_client, data_fixture):
|
|||
page = data_fixture.create_builder_page(user=user, builder=builder_to)
|
||||
page2 = data_fixture.create_builder_page(user=user, builder=builder_to)
|
||||
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
domain_name="test.getbaserow.io", published_to=builder_to
|
||||
)
|
||||
|
||||
|
@ -56,7 +56,7 @@ def test_get_builder_missing_domain_name(api_client, data_fixture):
|
|||
page = data_fixture.create_builder_page(user=user)
|
||||
page2 = data_fixture.create_builder_page(builder=page.builder, user=user)
|
||||
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
domain_name="test.getbaserow.io", published_to=page.builder
|
||||
)
|
||||
|
||||
|
@ -78,7 +78,7 @@ def test_get_non_public_builder(api_client, data_fixture):
|
|||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
page2 = data_fixture.create_builder_page(builder=page.builder, user=user)
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
domain_name="test.getbaserow.io", builder=page.builder
|
||||
)
|
||||
|
||||
|
@ -148,7 +148,7 @@ def test_publish_builder(mock_run_async_job, api_client, data_fixture):
|
|||
page = data_fixture.create_builder_page(builder=builder_from, user=user)
|
||||
page2 = data_fixture.create_builder_page(builder=builder_from, user=user)
|
||||
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
domain_name="test.getbaserow.io", builder=builder_from
|
||||
)
|
||||
|
||||
|
@ -182,7 +182,7 @@ def test_get_elements_of_public_builder(api_client, data_fixture):
|
|||
element2 = data_fixture.create_builder_heading_element(page=page)
|
||||
element3 = data_fixture.create_builder_paragraph_element(page=page)
|
||||
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
domain_name="test.getbaserow.io",
|
||||
published_to=page.builder,
|
||||
builder=builder_from,
|
||||
|
@ -238,7 +238,7 @@ def test_get_data_source_of_public_builder(api_client, data_fixture):
|
|||
page=page
|
||||
)
|
||||
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
domain_name="test.getbaserow.io",
|
||||
published_to=page.builder,
|
||||
builder=builder_from,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
import pytest
|
||||
|
@ -9,13 +10,15 @@ from rest_framework.status import (
|
|||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
from baserow.contrib.builder.domains.domain_types import CustomDomainType, SubDomainType
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_domains(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:builder_id:domains:list", kwargs={"builder_id": builder.id}
|
||||
|
@ -62,7 +65,7 @@ def test_create_domain(api_client, data_fixture):
|
|||
)
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"domain_name": domain_name},
|
||||
{"type": CustomDomainType.type, "domain_name": domain_name},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
@ -82,7 +85,7 @@ def test_create_domain_user_not_in_workspace(api_client, data_fixture):
|
|||
)
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"domain_name": "test.com"},
|
||||
{"type": CustomDomainType.type, "domain_name": "test.com"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
@ -98,7 +101,7 @@ def test_create_domain_application_does_not_exist(api_client, data_fixture):
|
|||
url = reverse("api:builder:builder_id:domains:list", kwargs={"builder_id": 9999})
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"domain_name": "test.com"},
|
||||
{"type": CustomDomainType.type, "domain_name": "test.com"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
@ -114,14 +117,14 @@ def test_create_domain_domain_already_exists(api_client, data_fixture):
|
|||
|
||||
domain_name = "test.com"
|
||||
|
||||
data_fixture.create_builder_domain(builder=builder, domain_name=domain_name)
|
||||
data_fixture.create_builder_custom_domain(builder=builder, domain_name=domain_name)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:builder_id:domains:list", kwargs={"builder_id": builder.id}
|
||||
)
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"domain_name": domain_name},
|
||||
{"type": CustomDomainType.type, "domain_name": domain_name},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
@ -142,7 +145,7 @@ def test_create_domain_invalid_domain_name(api_client, data_fixture):
|
|||
)
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"domain_name": domain_name},
|
||||
{"type": CustomDomainType.type, "domain_name": domain_name},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
@ -151,11 +154,36 @@ def test_create_domain_invalid_domain_name(api_client, data_fixture):
|
|||
assert response.json()["error"] == "ERROR_REQUEST_BODY_VALIDATION"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(BASEROW_BUILDER_DOMAINS=["test.com"])
|
||||
def test_create_invalid_sub_domain(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
|
||||
domain_name = "hello.nottest.com"
|
||||
|
||||
url = reverse(
|
||||
"api:builder:builder_id:domains:list", kwargs={"builder_id": builder.id}
|
||||
)
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"type": SubDomainType.type, "domain_name": domain_name},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_SUB_DOMAIN_HAS_INVALID_DOMAIN_NAME"
|
||||
assert "test.com" in response_json["detail"]
|
||||
assert "nottest.com" in response_json["detail"]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_domain(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain = data_fixture.create_builder_domain(
|
||||
domain = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, domain_name="something.com"
|
||||
)
|
||||
|
||||
|
@ -187,12 +215,45 @@ def test_update_domain_domain_does_not_exist(api_client, data_fixture):
|
|||
assert response.json()["error"] == "ERROR_DOMAIN_DOES_NOT_EXIST"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_domain_with_same_name(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
domain = data_fixture.create_builder_custom_domain(user=user)
|
||||
|
||||
url = reverse("api:builder:domains:item", kwargs={"domain_id": domain.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"domain_name": domain.domain_name},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_200_OK
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_domain_name_uniqueness(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
domain = data_fixture.create_builder_custom_domain(user=user)
|
||||
domain_2 = data_fixture.create_builder_custom_domain(user=user)
|
||||
|
||||
url = reverse("api:builder:domains:item", kwargs={"domain_id": domain.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"domain_name": domain_2.domain_name},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST, response.json()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_domains(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:builder_id:domains:order", kwargs={"builder_id": builder.id}
|
||||
|
@ -211,8 +272,8 @@ def test_order_domains(api_client, data_fixture):
|
|||
def test_order_domains_user_not_in_workspace(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:builder_id:domains:order", kwargs={"builder_id": builder.id}
|
||||
|
@ -232,8 +293,8 @@ def test_order_domains_user_not_in_workspace(api_client, data_fixture):
|
|||
def test_order_domains_domain_not_in_builder(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(order=2)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:builder_id:domains:order", kwargs={"builder_id": builder.id}
|
||||
|
@ -253,8 +314,8 @@ def test_order_domains_domain_not_in_builder(api_client, data_fixture):
|
|||
def test_order_domains_application_does_not_exist(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
url = reverse("api:builder:builder_id:domains:order", kwargs={"builder_id": 99999})
|
||||
response = api_client.post(
|
||||
|
@ -272,7 +333,7 @@ def test_order_domains_application_does_not_exist(api_client, data_fixture):
|
|||
def test_delete_domain(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
|
||||
url = reverse("api:builder:domains:item", kwargs={"domain_id": domain.id})
|
||||
response = api_client.delete(
|
||||
|
@ -288,7 +349,7 @@ def test_delete_domain(api_client, data_fixture):
|
|||
def test_delete_domain_user_not_in_workspace(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
|
||||
url = reverse("api:builder:domains:item", kwargs={"domain_id": domain.id})
|
||||
response = api_client.delete(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from baserow.contrib.builder.domains.domain_types import CustomDomainType
|
||||
from baserow.contrib.builder.domains.exceptions import (
|
||||
DomainDoesNotExist,
|
||||
DomainNotInBuilder,
|
||||
|
@ -13,7 +14,7 @@ from baserow.core.utils import Progress
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_get_domain(data_fixture):
|
||||
domain = data_fixture.create_builder_domain()
|
||||
domain = data_fixture.create_builder_custom_domain()
|
||||
assert DomainHandler().get_domain(domain.id).id == domain.id
|
||||
|
||||
|
||||
|
@ -25,7 +26,7 @@ def test_get_domain_domain_does_not_exist(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_get_domain_base_queryset(data_fixture, django_assert_num_queries):
|
||||
domain = data_fixture.create_builder_domain()
|
||||
domain = data_fixture.create_builder_custom_domain()
|
||||
|
||||
# Without selecting related
|
||||
domain = DomainHandler().get_domain(domain.id)
|
||||
|
@ -42,8 +43,8 @@ def test_get_domain_base_queryset(data_fixture, django_assert_num_queries):
|
|||
@pytest.mark.django_db
|
||||
def test_get_domains(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
domains = list(DomainHandler().get_domains(builder))
|
||||
domain_ids = [domain.id for domain in domains]
|
||||
|
@ -58,7 +59,9 @@ def test_create_domain(data_fixture):
|
|||
builder = data_fixture.create_builder_application()
|
||||
expected_order = Domain.get_last_order(builder)
|
||||
|
||||
domain = DomainHandler().create_domain(builder, "test.com")
|
||||
domain = DomainHandler().create_domain(
|
||||
CustomDomainType(), builder, domain_name="test.com"
|
||||
)
|
||||
|
||||
assert domain.order == expected_order
|
||||
assert domain.domain_name == "test.com"
|
||||
|
@ -66,7 +69,7 @@ def test_create_domain(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_domain(data_fixture):
|
||||
domain = data_fixture.create_builder_domain()
|
||||
domain = data_fixture.create_builder_custom_domain()
|
||||
|
||||
DomainHandler().delete_domain(domain)
|
||||
|
||||
|
@ -75,7 +78,7 @@ def test_delete_domain(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_update_domain(data_fixture):
|
||||
domain = data_fixture.create_builder_domain(domain_name="test.com")
|
||||
domain = data_fixture.create_builder_custom_domain(domain_name="test.com")
|
||||
|
||||
DomainHandler().update_domain(domain, domain_name="new.com")
|
||||
|
||||
|
@ -87,8 +90,8 @@ def test_update_domain(data_fixture):
|
|||
@pytest.mark.django_db
|
||||
def test_order_domains(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
assert DomainHandler().order_domains(builder, [domain_two.id, domain_one.id]) == [
|
||||
domain_two.id,
|
||||
|
@ -105,8 +108,8 @@ def test_order_domains(data_fixture):
|
|||
@pytest.mark.django_db
|
||||
def test_order_domains_domain_not_in_builder(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
base_qs = Domain.objects.filter(id=domain_two.id)
|
||||
|
||||
|
@ -120,7 +123,7 @@ def test_order_domains_domain_not_in_builder(data_fixture):
|
|||
def test_get_public_builder_by_name(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to
|
||||
)
|
||||
|
||||
|
@ -132,7 +135,7 @@ def test_get_public_builder_by_name(data_fixture):
|
|||
@pytest.mark.django_db
|
||||
def test_get_published_builder_by_missing_domain_name(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain1 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain1 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
with pytest.raises(BuilderDoesNotExist):
|
||||
DomainHandler().get_public_builder_by_domain_name(domain1.domain_name)
|
||||
|
@ -142,7 +145,7 @@ def test_get_published_builder_by_missing_domain_name(data_fixture):
|
|||
def test_get_published_builder_for_trashed_builder(data_fixture):
|
||||
builder = data_fixture.create_builder_application(trashed=True)
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to
|
||||
)
|
||||
|
||||
|
@ -151,7 +154,7 @@ def test_get_published_builder_for_trashed_builder(data_fixture):
|
|||
|
||||
builder = data_fixture.create_builder_application()
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to, trashed=True
|
||||
)
|
||||
|
||||
|
@ -163,7 +166,7 @@ def test_get_published_builder_for_trashed_builder(data_fixture):
|
|||
def test_domain_publishing(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
|
||||
domain1 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain1 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
page1 = data_fixture.create_builder_page(builder=builder)
|
||||
page2 = data_fixture.create_builder_page(builder=builder)
|
||||
|
|
|
@ -22,10 +22,10 @@ def test_allow_public_builder_manager_type(data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to
|
||||
)
|
||||
domain2 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain2 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
public_page = data_fixture.create_builder_page(builder=builder_to)
|
||||
non_public_page = data_fixture.create_builder_page(builder=builder)
|
||||
|
|
|
@ -2,6 +2,7 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from baserow.contrib.builder.domains.domain_types import CustomDomainType
|
||||
from baserow.contrib.builder.domains.exceptions import DomainNotInBuilder
|
||||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.service import DomainService
|
||||
|
@ -15,7 +16,9 @@ def test_domain_created_signal_sent(domain_created_mock, data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
|
||||
domain = DomainService().create_domain(user, builder, "test")
|
||||
domain = DomainService().create_domain(
|
||||
user, CustomDomainType(), builder, domain_name="test"
|
||||
)
|
||||
|
||||
assert domain_created_mock.called_with(domain=domain, user=user)
|
||||
|
||||
|
@ -26,7 +29,9 @@ def test_create_domain_user_not_in_workspace(data_fixture):
|
|||
builder = data_fixture.create_builder_application()
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
DomainService().create_domain(user, builder, "test")
|
||||
DomainService().create_domain(
|
||||
user, CustomDomainType, builder, domain_name="test"
|
||||
)
|
||||
|
||||
|
||||
@patch("baserow.contrib.builder.domains.service.domain_deleted")
|
||||
|
@ -34,7 +39,7 @@ def test_create_domain_user_not_in_workspace(data_fixture):
|
|||
def test_domain_deleted_signal_sent(domain_deleted_mock, data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain = data_fixture.create_builder_domain(builder=builder)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
DomainService().delete_domain(user, domain)
|
||||
|
||||
|
@ -49,7 +54,9 @@ def test_delete_domain_user_not_in_workspace(data_fixture):
|
|||
user_unrelated = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
|
||||
domain = DomainService().create_domain(user, builder, "test")
|
||||
domain = DomainService().create_domain(
|
||||
user, CustomDomainType(), builder, domain_name="test"
|
||||
)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
DomainService().delete_domain(user_unrelated, domain)
|
||||
|
@ -61,7 +68,7 @@ def test_delete_domain_user_not_in_workspace(data_fixture):
|
|||
def test_get_domain_user_not_in_workspace(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain = data_fixture.create_builder_domain(builder=builder)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
DomainService().get_domain(user, domain.id)
|
||||
|
@ -71,7 +78,7 @@ def test_get_domain_user_not_in_workspace(data_fixture):
|
|||
def test_get_domains_user_not_in_workspace(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain = data_fixture.create_builder_domain(builder=builder)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
DomainService().get_domains(user, builder)
|
||||
|
@ -81,8 +88,8 @@ def test_get_domains_user_not_in_workspace(data_fixture):
|
|||
def test_get_domains_partial_permissions(data_fixture, stub_check_permissions):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_with_access = data_fixture.create_builder_domain(builder=builder)
|
||||
domain_without_access = data_fixture.create_builder_domain(builder=builder)
|
||||
domain_with_access = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
domain_without_access = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
def exclude_domain_without_access(
|
||||
actor,
|
||||
|
@ -107,7 +114,7 @@ def test_get_domains_partial_permissions(data_fixture, stub_check_permissions):
|
|||
def test_domain_updated_signal_sent(domain_updated_mock, data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain = data_fixture.create_builder_domain(builder=builder)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
DomainService().update_domain(user, domain, domain_name="new.com")
|
||||
|
||||
|
@ -118,7 +125,7 @@ def test_domain_updated_signal_sent(domain_updated_mock, data_fixture):
|
|||
def test_update_domain_user_not_in_workspace(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain = data_fixture.create_builder_domain(builder=builder)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
DomainService().update_domain(user, domain, domain_name="test.com")
|
||||
|
@ -128,7 +135,7 @@ def test_update_domain_user_not_in_workspace(data_fixture):
|
|||
def test_update_domain_invalid_values(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain = data_fixture.create_builder_domain(builder=builder)
|
||||
domain = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
domain_updated = DomainService().update_domain(user, domain, nonsense="hello")
|
||||
|
||||
|
@ -140,8 +147,8 @@ def test_update_domain_invalid_values(data_fixture):
|
|||
def test_domains_reordered_signal_sent(domains_reordered_mock, data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
full_order = DomainService().order_domains(
|
||||
user, builder, [domain_two.id, domain_one.id]
|
||||
|
@ -156,8 +163,8 @@ def test_domains_reordered_signal_sent(domains_reordered_mock, data_fixture):
|
|||
def test_order_domains_user_not_in_workspace(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application()
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(builder=builder, order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(builder=builder, order=2)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
DomainService().order_domains(user, builder, [domain_two.id, domain_one.id])
|
||||
|
@ -167,8 +174,8 @@ def test_order_domains_user_not_in_workspace(data_fixture):
|
|||
def test_order_domains_domain_not_in_builder(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain_one = data_fixture.create_builder_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_domain(order=2)
|
||||
domain_one = data_fixture.create_builder_custom_domain(builder=builder, order=1)
|
||||
domain_two = data_fixture.create_builder_custom_domain(order=2)
|
||||
|
||||
with pytest.raises(DomainNotInBuilder):
|
||||
DomainService().order_domains(user, builder, [domain_two.id, domain_one.id])
|
||||
|
@ -179,7 +186,7 @@ def test_get_published_builder_by_domain_name(data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to
|
||||
)
|
||||
|
||||
|
@ -197,7 +204,7 @@ def test_get_published_builder_by_domain_name_unauthorized(
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to
|
||||
)
|
||||
|
||||
|
@ -213,7 +220,7 @@ def test_async_publish_domain(mock_run_async_job, data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
|
||||
domain1 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain1 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
job = DomainService().async_publish(user, domain1)
|
||||
|
||||
|
@ -225,7 +232,7 @@ def test_async_publish_domain(mock_run_async_job, data_fixture):
|
|||
@pytest.mark.django_db(transaction=True)
|
||||
def test_async_publish_domain_no_permission(data_fixture, stub_check_permissions):
|
||||
user = data_fixture.create_user()
|
||||
domain1 = data_fixture.create_builder_domain()
|
||||
domain1 = data_fixture.create_builder_custom_domain()
|
||||
|
||||
with stub_check_permissions(raise_permission_denied=True), pytest.raises(
|
||||
PermissionException
|
||||
|
@ -239,7 +246,7 @@ def test_publish_domain(domain_updated_mock, data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
|
||||
domain1 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain1 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
progress = Progress(100)
|
||||
domain = DomainService().publish(user, domain1, progress)
|
||||
|
@ -252,7 +259,7 @@ def test_publish_domain_unauthorized(data_fixture, stub_check_permissions):
|
|||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
|
||||
domain1 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain1 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
progress = Progress(100)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from baserow.core.jobs.handler import JobHandler
|
|||
def test_publish_domain_job_type(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
builder = data_fixture.create_builder_application(user=user)
|
||||
domain1 = data_fixture.create_builder_domain(builder=builder)
|
||||
domain1 = data_fixture.create_builder_custom_domain(builder=builder)
|
||||
|
||||
publish_domain_job = JobHandler().create_and_start_job(
|
||||
user,
|
||||
|
|
|
@ -410,7 +410,7 @@ def test_builder_application_import(data_fixture):
|
|||
def test_delete_builder_application_with_published_builder(data_fixture):
|
||||
builder = data_fixture.create_builder_application()
|
||||
builder_to = data_fixture.create_builder_application(workspace=None)
|
||||
domain1 = data_fixture.create_builder_domain(
|
||||
domain1 = data_fixture.create_builder_custom_domain(
|
||||
builder=builder, published_to=builder_to
|
||||
)
|
||||
|
||||
|
|
|
@ -139,6 +139,7 @@ x-backend-variables: &backend-variables
|
|||
BASEROW_DISABLE_LOCKED_MIGRATIONS:
|
||||
BASEROW_USE_PG_FULLTEXT_SEARCH:
|
||||
BASEROW_AUTO_VACUUM:
|
||||
BASEROW_BUILDER_DOMAINS:
|
||||
|
||||
services:
|
||||
# A caddy reverse proxy sitting in-front of all the services. Responsible for routing
|
||||
|
@ -206,6 +207,7 @@ services:
|
|||
BASEROW_USE_PG_FULLTEXT_SEARCH:
|
||||
BASEROW_UNIQUE_ROW_VALUES_SIZE_LIMIT:
|
||||
BASEROW_ROW_PAGE_SIZE_LIMIT:
|
||||
BASEROW_BUILDER_DOMAINS:
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
|
|
|
@ -158,6 +158,7 @@ x-backend-variables: &backend-variables
|
|||
BASEROW_DISABLE_LOCKED_MIGRATIONS:
|
||||
BASEROW_USE_PG_FULLTEXT_SEARCH:
|
||||
BASEROW_AUTO_VACUUM:
|
||||
BASEROW_BUILDER_DOMAINS:
|
||||
|
||||
services:
|
||||
backend:
|
||||
|
@ -201,6 +202,7 @@ services:
|
|||
BASEROW_USE_PG_FULLTEXT_SEARCH:
|
||||
BASEROW_UNIQUE_ROW_VALUES_SIZE_LIMIT:
|
||||
BASEROW_ROW_PAGE_SIZE_LIMIT:
|
||||
BASEROW_BUILDER_DOMAINS:
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
|
|
|
@ -157,6 +157,7 @@ x-backend-variables: &backend-variables
|
|||
BASEROW_DISABLE_LOCKED_MIGRATIONS:
|
||||
BASEROW_USE_PG_FULLTEXT_SEARCH:
|
||||
BASEROW_AUTO_VACUUM:
|
||||
BASEROW_BUILDER_DOMAINS:
|
||||
|
||||
services:
|
||||
# A caddy reverse proxy sitting in-front of all the services. Responsible for routing
|
||||
|
@ -220,6 +221,7 @@ services:
|
|||
POSTHOG_HOST:
|
||||
BASEROW_UNIQUE_ROW_VALUES_SIZE_LIMIT:
|
||||
BASEROW_ROW_PAGE_SIZE_LIMIT:
|
||||
BASEROW_BUILDER_DOMAINS:
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
|
|
|
@ -139,6 +139,12 @@ The installation methods referred to in the variable descriptions are:
|
|||
| BASEROW\_INITIAL\_CREATE\_SYNC\_TABLE\_DATA\_LIMIT | The maximum number of rows you can import in a synchronous way | 5000 |
|
||||
| BASEROW\_MAX\_ROW\_REPORT\_ERROR\_COUNT | The maximum row error count tolerated before a file import fails. Before this max error count the import will continue and the non failing rows will be imported and after it, no rows are imported at all. | 30 |
|
||||
|
||||
### Backend Application Builder Configuration
|
||||
| Name | Description | Defaults |
|
||||
|---------------------------|--------------------------------------------------------------------------------------------------------------------------|------------------------|
|
||||
| BASEROW\_BUILDER\_DOMAINS | A comma separated list of domain names that can be used as the domains to create sub domains in the application builder. | |
|
||||
|
||||
|
||||
### User file upload Configuration
|
||||
|
||||
Baserow needs somewhere to store the following types of files:
|
||||
|
@ -242,6 +248,8 @@ domain than your Baserow, you need to make sure CORS is configured correctly.
|
|||
| BASEROW_MAX_SNAPSHOTS_PER_GROUP | Controls how many application snapshots can be created per workspace. | -1 (unlimited) |
|
||||
| BASEROW\_USE\_PG\_FULLTEXT\_SEARCH | By default, Baserow will use Postgres full-text as its search backend. If the product is installed on a system with limited disk space, and less accurate results / degraded search performance is acceptable, then switch this setting off by setting it to false. | true |
|
||||
| BASEROW\_UNIQUE\_ROW\_VALUES\_SIZE\_LIMIT | Sets the limit for the automatic detection of multiselect options when converting a text field to a multiselect field. Increase the value to detect more options automatically, but consider performance implications. | 100 |
|
||||
| BASEROW\_BUILDER\_DOMAINS | A comma separated list of domain names that can be used as the domains to create sub domains in the application builder. | |
|
||||
|
||||
|
||||
### SSO Configuration
|
||||
| Name | Description | Defaults |
|
||||
|
|
|
@ -29,26 +29,25 @@
|
|||
</div>
|
||||
<template v-if="serverErrors.domain_name">
|
||||
<div v-if="serverErrors.domain_name.code === 'invalid'" class="error">
|
||||
{{ $t('customDomainForm.invalidDomain') }}
|
||||
{{ $t('domainForm.invalidDomain') }}
|
||||
</div>
|
||||
<div v-if="serverErrors.domain_name.code === 'unique'" class="error">
|
||||
{{ $t('customDomainForm.notUniqueDomain') }}
|
||||
</div></template
|
||||
>
|
||||
{{ $t('domainForm.notUniqueDomain') }}
|
||||
</div>
|
||||
</template>
|
||||
</FormElement>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
import { required, maxLength } from 'vuelidate/lib/validators'
|
||||
import domainForm from '@baserow/modules/builder/mixins/domainForm'
|
||||
|
||||
export default {
|
||||
name: 'DomainForm',
|
||||
mixins: [form],
|
||||
name: 'CustomDomainForm',
|
||||
mixins: [domainForm],
|
||||
data() {
|
||||
return {
|
||||
serverErrors: { domain_name: null },
|
||||
values: {
|
||||
domain_name: '',
|
||||
},
|
||||
|
@ -64,22 +63,5 @@ export default {
|
|||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleServerError(error) {
|
||||
if (error.handler.code !== 'ERROR_REQUEST_BODY_VALIDATION') return false
|
||||
|
||||
this.serverErrors = Object.fromEntries(
|
||||
Object.entries(error.handler.detail || {}).map(([key, value]) => [
|
||||
key,
|
||||
value[0],
|
||||
])
|
||||
)
|
||||
|
||||
return true
|
||||
},
|
||||
hasError() {
|
||||
return !this.isFormValid() || this.serverErrors.domain_name !== null
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
<Error :error="error"></Error>
|
||||
<FormElement class="control">
|
||||
<Dropdown
|
||||
v-model="selectedDomainType"
|
||||
v-model="selectedDomain"
|
||||
:show-search="false"
|
||||
class="domain-settings__domain-type"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="domainType in domainTypes"
|
||||
:key="domainType.getType()"
|
||||
:name="domainType.name"
|
||||
:value="domainType.getType()"
|
||||
v-for="option in options"
|
||||
:key="option.value.domain"
|
||||
:name="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</Dropdown>
|
||||
</FormElement>
|
||||
|
@ -20,7 +20,9 @@
|
|||
:is="currentDomainType.formComponent"
|
||||
ref="domainForm"
|
||||
:builder="builder"
|
||||
:domain="selectedDomain.domain"
|
||||
@submitted="createDomain($event)"
|
||||
@error="formHasError = $event"
|
||||
/>
|
||||
<div class="actions">
|
||||
<a @click="hideForm">
|
||||
|
@ -29,7 +31,7 @@
|
|||
</a>
|
||||
<button
|
||||
class="button button--large"
|
||||
:disabled="createLoading || formHasError()"
|
||||
:disabled="createLoading || formHasError"
|
||||
:class="{ 'button--loading': createLoading }"
|
||||
@click="onSubmit"
|
||||
>
|
||||
|
@ -58,17 +60,29 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
selectedDomainType: 'custom',
|
||||
selectedDomain: { type: 'custom', domain: 'custom' },
|
||||
createLoading: false,
|
||||
formHasError: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
domainTypes() {
|
||||
return this.$registry.getAll('domain')
|
||||
return Object.values(this.$registry.getAll('domain')) || []
|
||||
},
|
||||
selectedDomainType() {
|
||||
return this.selectedDomain.type
|
||||
},
|
||||
currentDomainType() {
|
||||
return this.$registry.get('domain', this.selectedDomainType)
|
||||
},
|
||||
options() {
|
||||
return this.domainTypes.map((domainType) => domainType.options).flat()
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedDomainType() {
|
||||
this.$refs?.domainForm?.reset()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
|
@ -92,13 +106,6 @@ export default {
|
|||
}
|
||||
this.createLoading = false
|
||||
},
|
||||
formHasError() {
|
||||
if (this.$refs.domainForm) {
|
||||
return this.$refs.domainForm.hasError()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleAnyError(error) {
|
||||
if (
|
||||
!this.$refs.domainForm ||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div>
|
||||
<p>
|
||||
{{ $t('subDomainDetails.text') }}
|
||||
</p>
|
||||
<div class="actions actions--right">
|
||||
<a
|
||||
class="button button--error"
|
||||
:class="{ 'button--loading': domain._.loading }"
|
||||
@click="$emit('delete')"
|
||||
>
|
||||
{{ $t('action.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SubDomainDetails',
|
||||
props: {
|
||||
domain: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<form @submit.prevent="submit">
|
||||
<FormElement :error="fieldHasErrors('domain_name')" class="control">
|
||||
<FormInput
|
||||
v-model="domainPrefix"
|
||||
:label="$t('subDomainForm.domainNameLabel')"
|
||||
:error="
|
||||
$v.values.domain_name.$dirty && !$v.values.domain_name.required
|
||||
? $t('error.requiredField')
|
||||
: $v.values.domain_name.$dirty && !$v.values.domain_name.maxLength
|
||||
? $t('error.maxLength', { max: 255 })
|
||||
: serverErrors.domain_name &&
|
||||
serverErrors.domain_name.code === 'invalid'
|
||||
? $t('domainForm.invalidDomain')
|
||||
: serverErrors.domain_name &&
|
||||
serverErrors.domain_name.code === 'unique'
|
||||
? $t('domainForm.notUniqueDomain')
|
||||
: ''
|
||||
"
|
||||
@input="serverErrors.domain_name = null"
|
||||
>
|
||||
<template #suffix> .{{ domain }} </template>
|
||||
</FormInput>
|
||||
</FormElement>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { maxLength, required } from 'vuelidate/lib/validators'
|
||||
import domainForm from '@baserow/modules/builder/mixins/domainForm'
|
||||
|
||||
export default {
|
||||
name: 'SubDomainForm',
|
||||
mixins: [domainForm],
|
||||
data() {
|
||||
return {
|
||||
domainPrefix: '',
|
||||
values: {
|
||||
domain_name: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
domainPrefix(value) {
|
||||
this.values.domain_name = `${value}.${this.domain}`
|
||||
},
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
values: {
|
||||
domain_name: {
|
||||
required,
|
||||
maxLength: maxLength(255),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,6 +1,8 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
import CustomDomainDetails from '@baserow/modules/builder/components/domain/CustomDomainDetails'
|
||||
import CustomDomainForm from '@baserow/modules/builder/components/domain/CustomDomainForm'
|
||||
import SubDomainForm from '@baserow/modules/builder/components/domain/SubDomainForm'
|
||||
import SubDomainDetails from '@baserow/modules/builder/components/domain/SubDomainDetails'
|
||||
|
||||
export class DomainType extends Registerable {
|
||||
get name() {
|
||||
|
@ -10,6 +12,14 @@ export class DomainType extends Registerable {
|
|||
get detailsComponent() {
|
||||
return null
|
||||
}
|
||||
|
||||
get formComponent() {
|
||||
return null
|
||||
}
|
||||
|
||||
get options() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomDomainType extends DomainType {
|
||||
|
@ -28,4 +38,44 @@ export class CustomDomainType extends DomainType {
|
|||
get formComponent() {
|
||||
return CustomDomainForm
|
||||
}
|
||||
|
||||
get options() {
|
||||
return [
|
||||
{
|
||||
name: this.app.i18n.t('domainTypes.customName'),
|
||||
value: {
|
||||
type: this.getType(),
|
||||
domain: 'custom',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export class SubDomainType extends DomainType {
|
||||
static getType() {
|
||||
return 'sub_domain'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.app.i18n.t('domainTypes.subDomainName')
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this.app.$config.BASEROW_BUILDER_DOMAINS.map((domain) => ({
|
||||
name: this.app.i18n.t('domainTypes.subDomain', { domain }),
|
||||
value: {
|
||||
type: this.getType(),
|
||||
domain,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
get detailsComponent() {
|
||||
return SubDomainDetails
|
||||
}
|
||||
|
||||
get formComponent() {
|
||||
return SubDomainForm
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,17 +155,27 @@
|
|||
"hostHeader": "Host",
|
||||
"valueHeader": "Value"
|
||||
},
|
||||
"domainForm": {
|
||||
"invalidDomain": "The provided domain name is invalid",
|
||||
"notUniqueDomain": "The provided domain is already used"
|
||||
},
|
||||
"customDomainForm": {
|
||||
"domainNameLabel": "Domain name",
|
||||
"invalidDomain": "The provided domain name is invalid",
|
||||
"notUniqueDomain": "The provided domain is already used"
|
||||
"domainNameLabel": "Domain name"
|
||||
},
|
||||
"subDomainForm": {
|
||||
"domainNameLabel": "Domain name"
|
||||
},
|
||||
"subDomainDetails": {
|
||||
"text": "The DNS settings of the domain have already been configured and checked. It works without making any additional changes."
|
||||
},
|
||||
"domainCard": {
|
||||
"refresh": "Refresh settings",
|
||||
"detailLabel": "Show details"
|
||||
},
|
||||
"domainTypes": {
|
||||
"customName": "Custom domain"
|
||||
"customName": "Custom domain",
|
||||
"subDomainName": "Subdomain",
|
||||
"subDomain": "Subdomain of {domain}"
|
||||
},
|
||||
"linkElement": {
|
||||
"noValue": "Unnamed..."
|
||||
|
|
46
web-frontend/modules/builder/mixins/domainForm.js
Normal file
46
web-frontend/modules/builder/mixins/domainForm.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import form from '@baserow/modules/core/mixins/form'
|
||||
|
||||
export default {
|
||||
mixins: [form],
|
||||
props: {
|
||||
domain: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
serverErrors: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasServerErrors() {
|
||||
return Object.values(this.serverErrors).some((value) => value !== null)
|
||||
},
|
||||
hasError() {
|
||||
return !this.isFormValid() || this.hasServerErrors
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
hasError: {
|
||||
handler(value) {
|
||||
this.$emit('error', value)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleServerError(error) {
|
||||
if (error.handler.code !== 'ERROR_REQUEST_BODY_VALIDATION') return false
|
||||
|
||||
this.serverErrors = Object.fromEntries(
|
||||
Object.entries(error.handler.detail || {}).map(([key, value]) => [
|
||||
key,
|
||||
value[0],
|
||||
])
|
||||
)
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
|
@ -49,7 +49,10 @@ import {
|
|||
VisibilityPageSidePanelType,
|
||||
StylePageSidePanelType,
|
||||
} from '@baserow/modules/builder/pageSidePanelTypes'
|
||||
import { CustomDomainType } from '@baserow/modules/builder/domainTypes'
|
||||
import {
|
||||
CustomDomainType,
|
||||
SubDomainType,
|
||||
} from '@baserow/modules/builder/domainTypes'
|
||||
import { PagePageSettingsType } from '@baserow/modules/builder/pageSettingsTypes'
|
||||
import {
|
||||
TextPathParamType,
|
||||
|
@ -158,6 +161,7 @@ export default (context) => {
|
|||
app.$registry.register('pageSidePanel', new EventsPageSidePanelType(context))
|
||||
|
||||
app.$registry.register('domain', new CustomDomainType(context))
|
||||
app.$registry.register('domain', new SubDomainType(context))
|
||||
|
||||
app.$registry.register('pageSettings', new PagePageSettingsType(context))
|
||||
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
export default (client) => {
|
||||
return {
|
||||
async fetchAll(builderId) {
|
||||
// TODO Manually add the domain type while the backend doesn't support it.
|
||||
const result = await client.get(`builder/${builderId}/domains/`)
|
||||
result.data = result.data.map((domain) => ({ type: 'custom', ...domain }))
|
||||
return result
|
||||
fetchAll(builderId) {
|
||||
return client.get(`builder/${builderId}/domains/`)
|
||||
},
|
||||
async create(builderId, { type, ...data }) {
|
||||
// TODO For now we manage the type manually.
|
||||
const result = await client.post(`builder/${builderId}/domains/`, data)
|
||||
result.data.type = 'custom'
|
||||
return result
|
||||
create(builderId, data) {
|
||||
return client.post(`builder/${builderId}/domains/`, data)
|
||||
},
|
||||
delete(domainId) {
|
||||
return client.delete(`builder/domains/${domainId}/`)
|
||||
|
|
|
@ -54,10 +54,10 @@ const actions = {
|
|||
|
||||
commit('SET_ITEMS', { domains })
|
||||
},
|
||||
async create({ commit }, { builderId, ...data }) {
|
||||
async create({ commit }, { builderId, type, ...data }) {
|
||||
const { data: domain } = await DomainService(this.$client).create(
|
||||
builderId,
|
||||
data
|
||||
{ type, ...data }
|
||||
)
|
||||
|
||||
commit('ADD_ITEM', { domain })
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
.domain-settings__domain-type {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
width: fit-content;
|
||||
|
||||
.dropdown__items {
|
||||
width: min-content;
|
||||
}
|
||||
}
|
||||
|
||||
.domains-settings__loading {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.form-input {
|
||||
display: block;
|
||||
display: flex;
|
||||
border: 1px solid $color-neutral-400;
|
||||
position: relative;
|
||||
@include rounded($rounded);
|
||||
|
@ -96,3 +96,14 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input__suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
border-left: 1px solid $color-neutral-400;
|
||||
appearance: none;
|
||||
padding: 0 12px;
|
||||
outline: none;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,11 @@
|
|||
class="form-input__icon fas"
|
||||
:class="[`fa-${icon}`]"
|
||||
/>
|
||||
<div class="form-input__suffix disabled">
|
||||
<div>
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasError" class="error">
|
||||
{{ error }}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from '@baserow/modules/core/utils/dom'
|
||||
|
||||
import dropdownHelpers from './dropdownHelpers'
|
||||
import _ from 'lodash'
|
||||
|
||||
export default {
|
||||
mixins: [dropdownHelpers],
|
||||
|
@ -325,7 +326,7 @@ export default {
|
|||
getSelectedProperty(value, property) {
|
||||
for (const i in this.getDropdownItemComponents()) {
|
||||
const item = this.getDropdownItemComponents()[i]
|
||||
if (item.value === value) {
|
||||
if (_.isEqual(item.value, value)) {
|
||||
return item[property]
|
||||
}
|
||||
}
|
||||
|
@ -338,7 +339,7 @@ export default {
|
|||
hasValue() {
|
||||
for (const i in this.getDropdownItemComponents()) {
|
||||
const item = this.getDropdownItemComponents()[i]
|
||||
if (item.value === this.value) {
|
||||
if (_.isEqual(item.value, this.value)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +367,9 @@ export default {
|
|||
)
|
||||
|
||||
const isArrowUp = event.key === 'ArrowUp'
|
||||
let index = children.findIndex((item) => item.value === this.hover)
|
||||
let index = children.findIndex((item) =>
|
||||
_.isEqual(item.value, this.hover)
|
||||
)
|
||||
index = isArrowUp ? index - 1 : index + 1
|
||||
|
||||
// Check if the new index is within the allowed range.
|
||||
|
|
|
@ -91,6 +91,9 @@ export default function CoreModule(options) {
|
|||
process.env.BASEROW_UNIQUE_ROW_VALUES_SIZE_LIMIT ?? 100,
|
||||
BASEROW_ROW_PAGE_SIZE_LIMIT:
|
||||
parseInt(process.env.BASEROW_ROW_PAGE_SIZE_LIMIT) ?? 200,
|
||||
BASEROW_BUILDER_DOMAINS: process.env.BASEROW_BUILDER_DOMAINS
|
||||
? process.env.BASEROW_BUILDER_DOMAINS.split(',')
|
||||
: [],
|
||||
}
|
||||
|
||||
const locales = [
|
||||
|
|
Loading…
Add table
Reference in a new issue