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
28 changes: 28 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: 2
updates:
# =============================================================================
# npm ecosystems
# =============================================================================
# Note: We only monitor directories with actual dependencies declared.
# /enterprise/web-frontend and /premium/web-frontend are excluded because
# they currently have no dependencies in their package.json files.
# /integrations/zapier and /plugin-boilerplate are excluded intentionally.

- package-ecosystem: "npm"
directory: "/web-frontend"
schedule:
interval: "monthly"

# =============================================================================
# uv (Python) ecosystem
# =============================================================================
# Note: We only monitor /backend because it manages the uv workspace centrally.
# The workspace is defined in backend/pyproject.toml with members including
# ../premium/backend and ../enterprise/backend, and backend/uv.lock contains
# all resolved dependencies. Separate entries for enterprise/premium backends
# would be redundant and cause duplicate update runs.

- package-ecosystem: "uv"
directory: "/backend"
schedule:
interval: "monthly"
4 changes: 2 additions & 2 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ dev = [
"coverage==7.13.1",
"pytest-split==0.10.0",
"pytest-unordered==0.7.0",
"debugpy==1.8.19",
"debugpy==1.8.20",
"backports.cached-property==1.0.2",
"httpretty==1.1.4",
"graphviz==0.21",
"pytest-cov==7.0.0",
"django-stubs==5.2.8",
"django-stubs-ext==5.2.8",
"django-stubs-ext==5.2.9",
"mypy==1.19.1",
"mypy-extensions==1.1.0",
"ipython",
Expand Down
4 changes: 1 addition & 3 deletions backend/src/baserow/config/db_routers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import random

from django.conf import settings
from django.db import transaction
from django.db import DEFAULT_DB_ALIAS, transaction

from asgiref.local import Local

DEFAULT_DB_ALIAS = "default"

_db_state = Local()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def get_page_id(self, instance):

@extend_schema_field(OpenApiTypes.FLOAT)
def get_order(self, instance):
return self.context["data_source"].order
return str(self.context["data_source"].order)

@extend_schema_field(OpenApiTypes.OBJECT)
def get_schema(self, instance):
Expand Down
3 changes: 2 additions & 1 deletion backend/src/baserow/contrib/builder/data_sources/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,11 @@ def delete_data_source(self, user: AbstractUser, data_source: DataSourceForUpdat
context=data_source,
)

data_source_id = data_source.id
self.handler.delete_data_source(data_source)

data_source_deleted.send(
self, data_source_id=data_source.id, page=page, user=user
self, data_source_id=data_source_id, page=page, user=user
)

def dispatch_data_sources(
Expand Down
97 changes: 97 additions & 0 deletions backend/src/baserow/contrib/builder/ws/data_sources/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from django.contrib.auth.models import AbstractUser
from django.db import transaction
from django.dispatch import receiver

from baserow.contrib.builder.api.data_sources.serializers import DataSourceSerializer
from baserow.contrib.builder.data_sources import signals as data_source_signals
from baserow.contrib.builder.data_sources.models import DataSource
from baserow.contrib.builder.data_sources.object_scopes import (
BuilderDataSourceObjectScopeType,
)
from baserow.contrib.builder.data_sources.operations import (
ListDataSourcesPageOperationType,
ReadDataSourceOperationType,
)
from baserow.contrib.builder.pages.models import Page
from baserow.contrib.builder.pages.object_scopes import BuilderPageObjectScopeType
from baserow.core.services.registries import service_type_registry
from baserow.ws.tasks import broadcast_to_permitted_users


@receiver(data_source_signals.data_source_created)
def data_source_created(
sender, data_source: DataSource, user: AbstractUser, before_id=None, **kwargs
):
if data_source.service:
serializer = service_type_registry.get_serializer(
data_source.service,
DataSourceSerializer,
context={"data_source": data_source},
)
else:
serializer = DataSourceSerializer(
data_source, context={"data_source": data_source}
)

transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
data_source.page.builder.workspace_id,
ReadDataSourceOperationType.type,
BuilderDataSourceObjectScopeType.type,
data_source.id,
{
"type": "data_source_created",
"data_source": serializer.data,
"before_id": before_id,
},
getattr(user, "web_socket_id", None),
)
)


@receiver(data_source_signals.data_source_updated)
def data_source_updated(sender, data_source: DataSource, user: AbstractUser, **kwargs):
if data_source.service:
serializer = service_type_registry.get_serializer(
data_source.service,
DataSourceSerializer,
context={"data_source": data_source},
)
else:
serializer = DataSourceSerializer(
data_source, context={"data_source": data_source}
)

transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
data_source.page.builder.workspace_id,
ReadDataSourceOperationType.type,
BuilderDataSourceObjectScopeType.type,
data_source.id,
{
"type": "data_source_updated",
"data_source": serializer.data,
},
getattr(user, "web_socket_id", None),
)
)


@receiver(data_source_signals.data_source_deleted)
def data_source_deleted(
sender, data_source_id: int, page: Page, user: AbstractUser, **kwargs
):
transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
page.builder.workspace_id,
ListDataSourcesPageOperationType.type,
BuilderPageObjectScopeType.type,
page.id,
{
"type": "data_source_deleted",
"data_source_id": data_source_id,
"page_id": page.id,
},
getattr(user, "web_socket_id", None),
)
)
18 changes: 18 additions & 0 deletions backend/src/baserow/contrib/builder/ws/signals.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
from .data_sources.signals import (
data_source_created,
data_source_deleted,
data_source_updated,
)
from .element.signals import (
element_created,
element_deleted,
element_orders_recalculated,
element_updated,
)
from .page.signals import page_created, page_deleted, page_reordered, page_updated
from .theme.signals import theme_updated
from .workflow_actions.signals import (
workflow_action_created,
workflow_action_deleted,
workflow_action_updated,
)

__all__ = [
"data_source_created",
"data_source_updated",
"data_source_deleted",
"page_created",
"page_deleted",
"page_updated",
Expand All @@ -15,4 +29,8 @@
"element_deleted",
"element_updated",
"element_orders_recalculated",
"theme_updated",
"workflow_action_created",
"workflow_action_updated",
"workflow_action_deleted",
]
29 changes: 29 additions & 0 deletions backend/src/baserow/contrib/builder/ws/theme/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.contrib.auth.models import AbstractUser
from django.db import transaction
from django.dispatch import receiver

from baserow.contrib.builder.models import Builder
from baserow.contrib.builder.object_scopes import BuilderObjectScopeType
from baserow.contrib.builder.theme import signals as theme_signals
from baserow.contrib.builder.theme.operations import UpdateThemeOperationType
from baserow.ws.tasks import broadcast_to_permitted_users


@receiver(theme_signals.theme_updated)
def theme_updated(
sender, builder: Builder, user: AbstractUser, properties: dict, **kwargs
):
transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
builder.workspace_id,
UpdateThemeOperationType.type,
BuilderObjectScopeType.type,
builder.id,
{
"type": "theme_updated",
"builder_id": builder.id,
"properties": properties,
},
getattr(user, "web_socket_id", None),
)
)
98 changes: 98 additions & 0 deletions backend/src/baserow/contrib/builder/ws/workflow_actions/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from django.contrib.auth.models import AbstractUser
from django.db import transaction
from django.dispatch import receiver

from baserow.contrib.builder.api.workflow_actions.serializers import (
BuilderWorkflowActionSerializer,
)
from baserow.contrib.builder.pages.models import Page
from baserow.contrib.builder.pages.object_scopes import BuilderPageObjectScopeType
from baserow.contrib.builder.workflow_actions import signals as workflow_action_signals
from baserow.contrib.builder.workflow_actions.object_scopes import (
BuilderWorkflowActionScopeType,
)
from baserow.contrib.builder.workflow_actions.operations import (
ListBuilderWorkflowActionsPageOperationType,
ReadBuilderWorkflowActionOperationType,
)
from baserow.contrib.builder.workflow_actions.registries import (
builder_workflow_action_type_registry,
)
from baserow.core.workflow_actions.models import WorkflowAction
from baserow.ws.tasks import broadcast_to_permitted_users


@receiver(workflow_action_signals.workflow_action_created)
def workflow_action_created(
sender,
workflow_action: WorkflowAction,
user: AbstractUser,
before_id=None,
**kwargs,
):
transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
workflow_action.page.builder.workspace_id,
ReadBuilderWorkflowActionOperationType.type,
BuilderWorkflowActionScopeType.type,
workflow_action.id,
{
"type": "workflow_action_created",
"page_id": workflow_action.page_id,
"workflow_action": builder_workflow_action_type_registry.get_serializer(
workflow_action, BuilderWorkflowActionSerializer
).data,
"before_id": before_id,
},
getattr(user, "web_socket_id", None),
)
)


@receiver(workflow_action_signals.workflow_action_updated)
def workflow_action_updated(
sender,
workflow_action: WorkflowAction,
user: AbstractUser,
**kwargs,
):
transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
workflow_action.page.builder.workspace_id,
ReadBuilderWorkflowActionOperationType.type,
BuilderWorkflowActionScopeType.type,
workflow_action.id,
{
"type": "workflow_action_updated",
"page_id": workflow_action.page_id,
"workflow_action": builder_workflow_action_type_registry.get_serializer(
workflow_action, BuilderWorkflowActionSerializer
).data,
},
getattr(user, "web_socket_id", None),
)
)


@receiver(workflow_action_signals.workflow_action_deleted)
def workflow_action_deleted(
sender,
workflow_action_id: int,
page: Page,
user: AbstractUser,
**kwargs,
):
transaction.on_commit(
lambda: broadcast_to_permitted_users.delay(
page.builder.workspace_id,
ListBuilderWorkflowActionsPageOperationType.type,
BuilderPageObjectScopeType.type,
page.id,
{
"type": "workflow_action_deleted",
"workflow_action_id": workflow_action_id,
"page_id": page.id,
},
getattr(user, "web_socket_id", None),
)
)
9 changes: 7 additions & 2 deletions backend/src/baserow/contrib/database/export/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from django.conf import settings

from celery.exceptions import Ignore

from baserow.config.celery import app

EXPORT_SOFT_TIME_LIMIT = 60 * 60
Expand All @@ -23,8 +25,11 @@ def run_export_job(self, job_id):
from baserow.contrib.database.export.handler import ExportHandler
from baserow.contrib.database.export.models import ExportJob

job = ExportJob.objects.get(id=job_id)
ExportHandler.run_export_job(job)
try:
job = ExportJob.objects.get(id=job_id)
ExportHandler.run_export_job(job)
except ExportJob.DoesNotExist:
raise Ignore("Task obsolete")


# noinspection PyUnusedLocal
Expand Down
8 changes: 6 additions & 2 deletions backend/src/baserow/contrib/database/fields/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import connection
from django.db import DEFAULT_DB_ALIAS, connection
from django.db.models import Prefetch, QuerySet
from django.db.utils import DatabaseError, DataError, ProgrammingError

Expand Down Expand Up @@ -1179,7 +1179,11 @@ def update_field_select_options(self, user, field, select_options):
)

if to_delete:
SelectOption.objects.filter(field=field, id__in=to_delete).delete()
# before_field_options_update already set deleted options to NULL,
# so we can safely call _raw_delete without violating any constraints.
SelectOption.objects.filter(field=field, id__in=to_delete)._raw_delete(
using=DEFAULT_DB_ALIAS
)

instance_to_create = []
for order, select_option in enumerate(select_options):
Expand Down
Loading
Loading