-
Notifications
You must be signed in to change notification settings - Fork 1
SS-1176 Users are able to set a custom default start URL for their apps #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
4994f10
27b763b
b5e8313
636ff9c
1425fdb
90f2b98
06b74f8
42c1e9f
b5cf2ed
758939e
60002fc
b3860ab
ad3feae
dc37d17
7b0ec61
8d560bd
9ce5d5b
3527fd0
6c4b717
cde08ad
83f9954
47476ce
a9b049b
bbcb968
a329f08
7d84b18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,6 @@ | |||||||
| from apps.forms.base import AppBaseForm | ||||||||
| from apps.forms.field.common import SRVCommonDivField | ||||||||
| from apps.models import CustomAppInstance, VolumeInstance | ||||||||
| from apps.types_.url_additional_path_validation import UrlAdditionalPathValidation | ||||||||
| from projects.models import Flavor | ||||||||
|
|
||||||||
| __all__ = ["CustomAppForm"] | ||||||||
|
|
@@ -19,20 +18,20 @@ class CustomAppForm(AppBaseForm): | |||||||
| image = forms.CharField(max_length=255, required=True) | ||||||||
| path = forms.CharField(max_length=255, required=False) | ||||||||
|
|
||||||||
| custom_default_url = forms.CharField(max_length=255, required=False, label="Custom default start URL") | ||||||||
| default_url_subpath = forms.CharField(max_length=255, required=False, label="Custom default url subpath") | ||||||||
|
|
||||||||
| def _setup_form_fields(self): | ||||||||
| # Handle Volume field | ||||||||
| super()._setup_form_fields() | ||||||||
| self.fields["volume"].initial = None | ||||||||
|
|
||||||||
| self.fields["custom_default_url"].widget.attrs.update({"class": "textinput form-control"}) | ||||||||
| self.fields["custom_default_url"].help_text = "Specify a non-default start URL if your app requires that." | ||||||||
| self.fields["custom_default_url"].bottom_help_text = mark_safe( | ||||||||
| self.fields["default_url_subpath"].widget.attrs.update({"class": "textinput form-control"}) | ||||||||
| self.fields["default_url_subpath"].help_text = "Specify a non-default start URL if your app requires that." | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Modified it. |
||||||||
| self.fields["default_url_subpath"].bottom_help_text = mark_safe( | ||||||||
| ( | ||||||||
| "We will display this URL for your app in our app catalogue." | ||||||||
| " Keep in mind that when your app does not have anything on the root URL" | ||||||||
| " (<span id='id_custom_default_url_form_help_text'></span>), if a user manually" | ||||||||
| " (<span id='id_custom_default_url_subpath_form_help_text'></span>), if a user manually" | ||||||||
| " navigates to the root URL, they will see an empty page there." | ||||||||
| ) | ||||||||
| ) | ||||||||
|
|
@@ -61,7 +60,7 @@ def _setup_form_helper(self): | |||||||
| AccordionGroup( | ||||||||
| "Advanced settings", | ||||||||
| PrependedText( | ||||||||
| "custom_default_url", | ||||||||
| "default_url_subpath", | ||||||||
| "Subdomain/", | ||||||||
| template="apps/partials/srv_prepend_input_group_custom_app.html", | ||||||||
| ), | ||||||||
|
|
@@ -72,17 +71,6 @@ def _setup_form_helper(self): | |||||||
| ) | ||||||||
| self.helper.layout = Layout(body, self.footer) | ||||||||
|
|
||||||||
| def clean_custom_default_url(self): | ||||||||
| cleaned_data = super().clean() | ||||||||
|
|
||||||||
| custom_url = cleaned_data.get("custom_default_url", None) | ||||||||
| custom_url_candidate = UrlAdditionalPathValidation(custom_url) | ||||||||
| try: | ||||||||
| custom_url_candidate.validate_candidate() | ||||||||
| return custom_url | ||||||||
| except ValidationError as e: | ||||||||
| self.add_error("custom_default_url", e) | ||||||||
|
|
||||||||
| def clean_path(self): | ||||||||
| cleaned_data = super().clean() | ||||||||
|
|
||||||||
|
|
@@ -120,7 +108,7 @@ class Meta: | |||||||
| "port", | ||||||||
| "image", | ||||||||
| "tags", | ||||||||
| "custom_default_url", | ||||||||
| "default_url_subpath", | ||||||||
| ] | ||||||||
| labels = { | ||||||||
| "note_on_linkonly_privacy": "Reason for choosing the link only option", | ||||||||
|
|
||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Generated by Django 5.1.1 on 2024-11-19 13:27 | ||
|
|
||
| import apps.models.app_types.custom.custom | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("apps", "0017_alter_streamlitinstance_port"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="customappinstance", | ||
| name="default_url_subpath", | ||
| field=models.CharField( | ||
| blank=True, | ||
| default="", | ||
| max_length=255, | ||
| validators=[apps.models.app_types.custom.custom.validate_default_url_subpath], | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,14 +1,40 @@ | ||||||||||||
| import regex as re | ||||||||||||
| from django.core.exceptions import ValidationError | ||||||||||||
|
Comment on lines
+1
to
+2
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This should be caught by isort I think
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you. Addressed it. |
||||||||||||
| from django.db import models | ||||||||||||
|
|
||||||||||||
| from apps.models import AppInstanceManager, BaseAppInstance | ||||||||||||
|
|
||||||||||||
| from .base import AbstractCustomAppInstance | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def validate_default_url_subpath(candidate): | ||||||||||||
| """ | ||||||||||||
| Validates a custom default url path addition. | ||||||||||||
| The RegexValidator will raise a ValidationError if the input does not match the regular expression. | ||||||||||||
| It is up to the caller to handle the raised exception if desired. | ||||||||||||
| """ | ||||||||||||
| error_message = ( | ||||||||||||
| "Your custom default URL is not valid, please correct it. " | ||||||||||||
| "It must be 1-53 characters long." | ||||||||||||
| " It can contain only Unicode letters, digits, hyphens" | ||||||||||||
| " ( - ), forward slashes ( / ), and underscores ( _ )." | ||||||||||||
| " It cannot start or end with a hyphen ( - ) and " | ||||||||||||
| "cannot start with a forward slash ( / )." | ||||||||||||
| " It cannot contain consecutive forward slashes ( // )." | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| pattern = r"^(?!-)(?!/)(?!.*//)[\p{Letter}\p{Mark}0-9-/_]{1,53}(?<!-)$|^$" | ||||||||||||
|
|
||||||||||||
| if not re.match(pattern, candidate): | ||||||||||||
| raise ValidationError(error_message) | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class CustomAppInstanceManager(AppInstanceManager): | ||||||||||||
| model_type = "customappinstance" | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class CustomAppInstance(AbstractCustomAppInstance, BaseAppInstance): | ||||||||||||
| custom_default_url = models.CharField(max_length=255, default="", blank=True) | ||||||||||||
| default_url_subpath = models.CharField( | ||||||||||||
| validators=[validate_default_url_subpath], max_length=255, default="", blank=True | ||||||||||||
| ) | ||||||||||||
| objects = CustomAppInstanceManager() | ||||||||||||
|
This file was deleted.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,46 @@ | ||
| {% include "apps/partials/div_label_with_help_toggle.html" with help_message=field.help_text %} | ||
| {% load get_setting %} | ||
| <div> | ||
| <div class="input-group"> | ||
| {% if crispy_prepended_text %} | ||
| <span class="input-group-text" id="id_original_url_custom_default_url_form"></span> | ||
| {% endif %} | ||
| {{ field }} | ||
| </div> | ||
| {% if field.field.bottom_help_text %} | ||
| <small class="form-text text-muted">{{ field.field.bottom_help_text }}</small> | ||
| {% endif %} | ||
|
|
||
| {% if field.errors %} | ||
| {% for error in field.errors %} | ||
| <div class="client-validation-feedback client-validation-invalid"> | ||
| {{ error }} | ||
| </div> | ||
| {% endfor %} | ||
| {% endif %} | ||
| </div> | ||
|
|
||
| <script> | ||
|
|
||
| const subdomainInput = document.getElementById("id_subdomain"); | ||
| const customDefaultUrlFormModifiedUrlText = document.getElementById("id_original_url_custom_default_url_form"); | ||
| const customDefaultUrlFormModifiedHelpTextUrl = document.getElementById("id_custom_default_url_form_help_text"); | ||
|
|
||
| function updateUrlWithSubdomain() { | ||
| const subdomainValue = subdomainInput.value.trim(); | ||
| const displayValue = subdomainValue || "subdomain_name"; | ||
|
|
||
| customDefaultUrlFormModifiedUrlText.innerHTML = `https://${displayValue}.{% get_setting 'DOMAIN' %}/`; | ||
| customDefaultUrlFormModifiedHelpTextUrl.innerHTML = `https://${displayValue}.{% get_setting 'DOMAIN' %}/</b>`; | ||
| } | ||
|
|
||
| updateUrlWithSubdomain(); | ||
|
|
||
| subdomainInput.addEventListener("blur", updateUrlWithSubdomain); | ||
| <head> | ||
| <script> | ||
| document.addEventListener("DOMContentLoaded", function () { | ||
| const subdomainInput = document.getElementById("id_subdomain"); | ||
| const customDefaultUrlFormModifiedUrlText = document.getElementById("id_original_default_url_subpath_form"); | ||
| const customDefaultUrlFormModifiedHelpTextUrl = document.getElementById("id_custom_default_url_subpath_form_help_text"); | ||
|
|
||
| function updateUrlWithSubdomain() { | ||
| const subdomainValue = subdomainInput.value.trim(); | ||
| const displayValue = subdomainValue || "subdomain_name"; | ||
|
|
||
| customDefaultUrlFormModifiedUrlText.innerHTML = `${displayValue}.{% get_setting 'DOMAIN' %}/`; | ||
| customDefaultUrlFormModifiedHelpTextUrl.innerHTML = `${displayValue}.{% get_setting 'DOMAIN' %}/</b>`; | ||
| } | ||
|
|
||
| updateUrlWithSubdomain(); | ||
|
|
||
| subdomainInput.addEventListener("blur", updateUrlWithSubdomain); | ||
| }); | ||
| </script> | ||
| </head> | ||
|
|
||
| <body> | ||
| <div> | ||
| <div class="input-group"> | ||
| {% if crispy_prepended_text %} | ||
| <span class="input-group-text" id="id_original_default_url_subpath_form"></span> | ||
| {% endif %} | ||
| {{ field }} | ||
| </div> | ||
| {% if field.field.bottom_help_text %} | ||
| <small class="form-text text-muted">{{ field.field.bottom_help_text }}</small> | ||
| {% endif %} | ||
|
|
||
| </script> | ||
| {% if field.errors %} | ||
| {% for error in field.errors %} | ||
| <div class="client-validation-feedback client-validation-invalid"> | ||
| {{ error }} | ||
| </div> | ||
| {% endfor %} | ||
| {% endif %} | ||
| </div> | ||
| </body> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Changed it.