mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +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