mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
realm_playgrounds: Add url_template field.
As an intermediate step before we fully support url_template for realm playgrounds, we populate url_template in the backend ensuring that all the new entries will be validated. With a later backfilling migration, we prepare the database such that all the records will have a valid URL template. Signed-off-by: Zixuan James Li <p359101898@gmail.com>
This commit is contained in:
committed by
Tim Abbott
parent
131729a06c
commit
641f60305d
@@ -31,7 +31,11 @@ def do_add_realm_playground(
|
||||
url_prefix: str,
|
||||
) -> int:
|
||||
realm_playground = RealmPlayground(
|
||||
realm=realm, name=name, pygments_language=pygments_language, url_prefix=url_prefix
|
||||
realm=realm,
|
||||
name=name,
|
||||
pygments_language=pygments_language,
|
||||
url_prefix=url_prefix,
|
||||
url_template=url_prefix + "{code}",
|
||||
)
|
||||
# We expect full_clean to always pass since a thorough input validation
|
||||
# is performed in the view (using check_url, check_pygments_language, etc)
|
||||
@@ -68,6 +72,7 @@ def do_remove_realm_playground(
|
||||
"name": realm_playground.name,
|
||||
"pygments_language": realm_playground.pygments_language,
|
||||
"url_prefix": realm_playground.url_prefix,
|
||||
"url_template": realm_playground.url_template,
|
||||
}
|
||||
|
||||
realm_playground.delete()
|
||||
|
||||
19
zerver/migrations/0462_realmplayground_url_template.py
Normal file
19
zerver/migrations/0462_realmplayground_url_template.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-27 00:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
from zerver.models import url_template_validator
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0461_alter_realm_default_code_block_language"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="realmplayground",
|
||||
name="url_template",
|
||||
field=models.TextField(null=True, validators=[url_template_validator]),
|
||||
),
|
||||
]
|
||||
@@ -1366,6 +1366,7 @@ class RealmPlayground(models.Model):
|
||||
|
||||
realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
||||
url_prefix = models.TextField(validators=[URLValidator()])
|
||||
url_template = models.TextField(validators=[url_template_validator], null=True)
|
||||
|
||||
# User-visible display name used when configuring playgrounds in the settings page and
|
||||
# when displaying them in the playground links popover.
|
||||
@@ -1390,6 +1391,38 @@ class RealmPlayground(models.Model):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.realm.string_id}: {self.pygments_language} {self.name}"
|
||||
|
||||
def clean(self) -> None:
|
||||
"""Validate whether the URL template is valid for the playground,
|
||||
ensuring that "code" is the sole variable present in it.
|
||||
|
||||
Django's `full_clean` calls `clean_fields` followed by `clean` method
|
||||
and stores all ValidationErrors from all stages to return as JSON.
|
||||
"""
|
||||
|
||||
# Prior to the completion of this migration, we make url_template nullable,
|
||||
# while ensuring that no code path will create a RealmPlayground without populating it.
|
||||
assert self.url_template is not None
|
||||
|
||||
# Do not continue the check if the url template is invalid to begin with.
|
||||
# The ValidationError for invalid template will only be raised by the validator
|
||||
# set on the url_template field instead of here to avoid duplicates.
|
||||
if not uri_template.validate(self.url_template):
|
||||
return
|
||||
|
||||
# Extract variables used in the URL template.
|
||||
template_variables = set(uri_template.URITemplate(self.url_template).variable_names)
|
||||
|
||||
if (
|
||||
"code" not in template_variables
|
||||
): # nocoverage: prior to the completion of the migration, it is impossible to generate a URL template without the "code" variable
|
||||
raise ValidationError(_('Missing the required variable "code" in the URL template'))
|
||||
|
||||
# The URL template should only contain a single variable, which is "code".
|
||||
if len(template_variables) != 1:
|
||||
raise ValidationError(
|
||||
_('"code" should be the only variable present in the URL template'),
|
||||
)
|
||||
|
||||
|
||||
def get_realm_playgrounds(realm: Realm) -> List[RealmPlaygroundDict]:
|
||||
playgrounds: List[RealmPlaygroundDict] = []
|
||||
|
||||
@@ -900,6 +900,7 @@ class TestRealmAuditLog(ZulipTestCase):
|
||||
"name": "Python playground",
|
||||
"pygments_language": "Python",
|
||||
"url_prefix": "https://python.example.com",
|
||||
"url_template": "https://python.example.com{code}",
|
||||
}
|
||||
expected_extra_data = {
|
||||
"realm_playgrounds": initial_playgrounds,
|
||||
|
||||
@@ -65,6 +65,24 @@ class RealmPlaygroundTests(ZulipTestCase):
|
||||
resp = self.api_post(iago, "/api/v1/realm/playgrounds", payload)
|
||||
self.assert_json_error(resp, "Invalid characters in pygments language")
|
||||
|
||||
payload = {
|
||||
"name": "Template with an unexpected variable",
|
||||
"pygments_language": "Python",
|
||||
"url_prefix": "https://template.com?test={test}",
|
||||
}
|
||||
resp = self.api_post(iago, "/api/v1/realm/playgrounds", payload)
|
||||
self.assert_json_error(
|
||||
resp, '"code" should be the only variable present in the URL template'
|
||||
)
|
||||
|
||||
payload = {
|
||||
"name": "Invalid URL template",
|
||||
"pygments_language": "Python",
|
||||
"url_prefix": "https://template.com?test={test",
|
||||
}
|
||||
resp = self.api_post(iago, "/api/v1/realm/playgrounds", payload)
|
||||
self.assert_json_error(resp, "Invalid URL template.")
|
||||
|
||||
def test_create_already_existing_playground(self) -> None:
|
||||
iago = self.example_user("iago")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user