mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-04 13:15:24 +00:00
Merge branch 'add-application-builder-theme-templates' into 'develop'
Added Baserow, Eclipse, and Ivory theme templates See merge request baserow/baserow!3270
This commit is contained in:
commit
c870707460
15 changed files with 14957 additions and 34 deletions
backend
src/baserow
templates
ab_baserow_theme.jsonab_baserow_theme.zipab_eclipse_theme.jsonab_eclipse_theme.zipab_ivory_theme.jsonab_ivory_theme.zip
tests
web-frontend/modules
|
@ -28,6 +28,7 @@ class TemplateSerializer(serializers.ModelSerializer):
|
|||
"keywords",
|
||||
"workspace_id",
|
||||
"is_default",
|
||||
"open_application",
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
|
|
|
@ -1946,6 +1946,7 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
# hash mismatch, which means the workspace has already been deleted, we can
|
||||
# create a new workspace and import the exported applications into that
|
||||
# workspace.
|
||||
imported_id_mapping = None
|
||||
if not installed_template or installed_template.export_hash != export_hash:
|
||||
# It is optionally possible for a template to have additional files.
|
||||
# They are stored in a ZIP file and are generated when the template
|
||||
|
@ -1959,13 +1960,14 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
files_buffer = BytesIO()
|
||||
|
||||
workspace = Workspace.objects.create(name=parsed_json["name"])
|
||||
self.import_applications_to_workspace(
|
||||
_, id_mapping = self.import_applications_to_workspace(
|
||||
workspace,
|
||||
parsed_json["export"],
|
||||
files_buffer=files_buffer,
|
||||
import_export_config=config,
|
||||
storage=storage,
|
||||
)
|
||||
imported_id_mapping = id_mapping
|
||||
|
||||
if files_buffer:
|
||||
files_buffer.close()
|
||||
|
@ -1982,6 +1984,13 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
"workspace": workspace,
|
||||
}
|
||||
|
||||
# If the template was imported, then we'll map the desired open_application
|
||||
# id to the actually imported application id.
|
||||
if "open_application" in parsed_json and imported_id_mapping is not None:
|
||||
kwargs["open_application"] = imported_id_mapping["applications"].get(
|
||||
parsed_json["open_application"], None
|
||||
)
|
||||
|
||||
if not installed_template:
|
||||
installed_template = Template.objects.create(slug=slug, **kwargs)
|
||||
else:
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 5.0.9 on 2025-03-18 16:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0095_alter_userfile_image_height_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="template",
|
||||
name="open_application",
|
||||
field=models.IntegerField(
|
||||
help_text="The application ID that must be opened when the template is previewed. If null, then the first will automatically be chosen.",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -472,6 +472,11 @@ class Template(models.Model):
|
|||
blank=True,
|
||||
help_text="Keywords related to the template that can be used for search.",
|
||||
)
|
||||
open_application = models.IntegerField(
|
||||
null=True,
|
||||
help_text="The application ID that must be opened when the template is "
|
||||
"previewed. If null, then the first will automatically be chosen.",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("name",)
|
||||
|
|
4929
backend/templates/ab_baserow_theme.json
Normal file
4929
backend/templates/ab_baserow_theme.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
backend/templates/ab_baserow_theme.zip
Normal file
BIN
backend/templates/ab_baserow_theme.zip
Normal file
Binary file not shown.
4930
backend/templates/ab_eclipse_theme.json
Normal file
4930
backend/templates/ab_eclipse_theme.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
backend/templates/ab_eclipse_theme.zip
Normal file
BIN
backend/templates/ab_eclipse_theme.zip
Normal file
Binary file not shown.
4929
backend/templates/ab_ivory_theme.json
Normal file
4929
backend/templates/ab_ivory_theme.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
backend/templates/ab_ivory_theme.zip
Normal file
BIN
backend/templates/ab_ivory_theme.zip
Normal file
Binary file not shown.
|
@ -33,11 +33,13 @@ def test_list_templates(api_client, data_fixture):
|
|||
category=category_1,
|
||||
keywords="test1,test2",
|
||||
slug="project-tracker",
|
||||
open_application=None,
|
||||
)
|
||||
template_2 = data_fixture.create_template(
|
||||
name="Template 2",
|
||||
icon="document",
|
||||
category=category_2,
|
||||
open_application=1,
|
||||
)
|
||||
template_3 = data_fixture.create_template(
|
||||
name="Template 3", icon="document", categories=[category_2, category_3]
|
||||
|
@ -46,25 +48,67 @@ def test_list_templates(api_client, data_fixture):
|
|||
response = api_client.get(reverse("api:templates:list"))
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 3
|
||||
assert response_json[0]["id"] == category_1.id
|
||||
assert response_json[0]["name"] == "Cat 1"
|
||||
assert len(response_json[0]["templates"]) == 1
|
||||
assert response_json[0]["templates"][0]["id"] == template_1.id
|
||||
assert response_json[0]["templates"][0]["name"] == template_1.name
|
||||
assert response_json[0]["templates"][0]["slug"] == template_1.slug
|
||||
assert response_json[0]["templates"][0]["icon"] == template_1.icon
|
||||
assert response_json[0]["templates"][0]["keywords"] == "test1,test2"
|
||||
assert response_json[0]["templates"][0]["workspace_id"] == template_1.workspace_id
|
||||
assert response_json[0]["templates"][0]["is_default"] is True
|
||||
assert len(response_json[1]["templates"]) == 2
|
||||
assert response_json[1]["templates"][0]["id"] == template_2.id
|
||||
assert response_json[1]["templates"][0]["is_default"] is False
|
||||
assert response_json[1]["templates"][1]["id"] == template_3.id
|
||||
assert response_json[1]["templates"][1]["is_default"] is False
|
||||
assert len(response_json[2]["templates"]) == 1
|
||||
assert response_json[2]["templates"][0]["id"] == template_3.id
|
||||
assert response_json[2]["templates"][0]["is_default"] is False
|
||||
|
||||
assert response_json == [
|
||||
{
|
||||
"id": category_1.id,
|
||||
"name": "Cat 1",
|
||||
"templates": [
|
||||
{
|
||||
"id": template_1.id,
|
||||
"name": "Template 1",
|
||||
"slug": template_1.slug,
|
||||
"icon": "document",
|
||||
"keywords": "test1,test2",
|
||||
"workspace_id": template_1.workspace_id,
|
||||
"is_default": True,
|
||||
"open_application": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": category_2.id,
|
||||
"name": "Cat 2",
|
||||
"templates": [
|
||||
{
|
||||
"id": template_2.id,
|
||||
"name": "Template 2",
|
||||
"slug": template_2.slug,
|
||||
"icon": "document",
|
||||
"keywords": "",
|
||||
"workspace_id": template_2.workspace_id,
|
||||
"is_default": False,
|
||||
"open_application": 1,
|
||||
},
|
||||
{
|
||||
"id": template_3.id,
|
||||
"name": "Template 3",
|
||||
"slug": template_3.slug,
|
||||
"icon": "document",
|
||||
"keywords": "",
|
||||
"workspace_id": template_3.workspace_id,
|
||||
"is_default": False,
|
||||
"open_application": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": category_3.id,
|
||||
"name": "Cat 3",
|
||||
"templates": [
|
||||
{
|
||||
"id": template_3.id,
|
||||
"name": "Template 3",
|
||||
"slug": template_3.slug,
|
||||
"icon": "document",
|
||||
"keywords": "",
|
||||
"workspace_id": template_3.workspace_id,
|
||||
"is_default": False,
|
||||
"open_application": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -1330,6 +1330,34 @@ def test_sync_templates(data_fixture, tmpdir):
|
|||
settings.APPLICATION_TEMPLATES_DIR = old_templates
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sync_templates_mapped_open_application_id(data_fixture, tmpdir):
|
||||
old_templates = settings.APPLICATION_TEMPLATES_DIR
|
||||
settings.APPLICATION_TEMPLATES_DIR = os.path.join(
|
||||
settings.BASE_DIR, "../../../tests/templates"
|
||||
)
|
||||
|
||||
storage = FileSystemStorage(location=str(tmpdir), base_url="http://localhost")
|
||||
|
||||
handler = CoreHandler()
|
||||
handler.sync_templates(storage=storage)
|
||||
|
||||
template_1 = Template.objects.get(slug="example-template")
|
||||
template_2 = Template.objects.get(slug="example-template-2")
|
||||
|
||||
application = template_2.workspace.application_set.all().first()
|
||||
|
||||
assert template_1.open_application is None
|
||||
assert template_2.open_application == application.id
|
||||
|
||||
handler.sync_templates(storage=storage)
|
||||
|
||||
assert template_1.open_application is None
|
||||
assert template_2.open_application == application.id
|
||||
|
||||
settings.APPLICATION_TEMPLATES_DIR = old_templates
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("baserow.core.signals.application_created.send")
|
||||
def test_install_template(send_mock, tmpdir, data_fixture):
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"icon": "file",
|
||||
"keywords": ["Example", "Template", "For", "Search"],
|
||||
"categories": ["Test category 1"],
|
||||
"open_application": 2,
|
||||
"export": [
|
||||
{
|
||||
"id": 2,
|
||||
|
|
|
@ -60,6 +60,17 @@ export class BuilderApplicationType extends ApplicationType {
|
|||
return PageTemplate
|
||||
}
|
||||
|
||||
getTemplatePage(application) {
|
||||
const notSharedPages = application.pages.filter((p) => p.shared === false)
|
||||
if (notSharedPages.length === 0) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
builder: application,
|
||||
page: notSharedPages[0],
|
||||
}
|
||||
}
|
||||
|
||||
populate(application) {
|
||||
const values = super.populate(application)
|
||||
values.pages = values.pages.map(populatePage)
|
||||
|
|
|
@ -102,20 +102,22 @@ export default {
|
|||
populateApplication(application, this.$registry)
|
||||
)
|
||||
|
||||
// Check if there is an application that can give us an initial page. The
|
||||
// database application type would for example return the first table as page.
|
||||
for (let i = 0; i < this.applications.length; i++) {
|
||||
const application = this.applications[i]
|
||||
const pageValue = this.$registry
|
||||
.get('application', application.type)
|
||||
.getTemplatePage(application)
|
||||
if (pageValue !== null) {
|
||||
application._.selected = true
|
||||
this.selectPage({
|
||||
application: application.type,
|
||||
value: pageValue,
|
||||
})
|
||||
break
|
||||
// A template can optionally have a preferred application that must be opened
|
||||
// first in the preview. Try to select that one, and if that's not possible,
|
||||
// then try to select the first application of the template.
|
||||
const openApplication = this.applications.find(
|
||||
(a) => a.id === this.template.open_application
|
||||
)
|
||||
if (openApplication) {
|
||||
this.selectApplication(openApplication)
|
||||
} else {
|
||||
// Check if there is an application that can give us an initial page. The
|
||||
// database application type would for example return the first table as page.
|
||||
for (let i = 0; i < this.applications.length; i++) {
|
||||
const application = this.applications[i]
|
||||
if (this.selectApplication(application)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -125,6 +127,20 @@ export default {
|
|||
this.loading = false
|
||||
}
|
||||
},
|
||||
selectApplication(application) {
|
||||
const pageValue = this.$registry
|
||||
.get('application', application.type)
|
||||
.getTemplatePage(application)
|
||||
if (pageValue !== null) {
|
||||
application._.selected = true
|
||||
this.selectPage({
|
||||
application: application.type,
|
||||
value: pageValue,
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
selectPage({ application, value }) {
|
||||
this.page = {
|
||||
application,
|
||||
|
|
Loading…
Add table
Reference in a new issue