mirror of https://github.com/zulip/zulip.git
billing/sponsorship: Allow blank organization URLs.
We ran into a bug in production caused by two issues: - Some users came from orgs that didn't have a website and since the URL field was required, they submitted invalid URLs. - We didn't properly respond to invalid form submissions, which led to UnboundLocalError exceptions in another part of the code. This commit solves this by doing the following: - We now allow blank URLs and have a convenient placeholder text label that tells users that they may leave the URL field blank. - This commit refactors the code such that invalid form submissions result in an informative error message about what exactly went wrong.
This commit is contained in:
parent
6117c3824a
commit
a808f02a22
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.5 on 2021-08-06 19:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("corporate", "0012_zulipsponsorshiprequest"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="zulipsponsorshiprequest",
|
||||||
|
name="org_website",
|
||||||
|
field=models.URLField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -204,6 +204,6 @@ class ZulipSponsorshipRequest(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
MAX_ORG_URL_LENGTH: int = 200
|
MAX_ORG_URL_LENGTH: int = 200
|
||||||
org_website: str = models.URLField(max_length=MAX_ORG_URL_LENGTH)
|
org_website: str = models.URLField(max_length=MAX_ORG_URL_LENGTH, blank=True, null=True)
|
||||||
|
|
||||||
org_description: str = models.TextField(default="")
|
org_description: str = models.TextField(default="")
|
||||||
|
|
|
@ -1461,6 +1461,32 @@ class StripeTest(StripeTestCase):
|
||||||
orjson.loads(response.content)["error_description"], "uncaught exception during upgrade"
|
orjson.loads(response.content)["error_description"], "uncaught exception during upgrade"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_request_sponsorship_form_with_invalid_url(self) -> None:
|
||||||
|
user = self.example_user("hamlet")
|
||||||
|
self.login_user(user)
|
||||||
|
data = {
|
||||||
|
"organization-type": Realm.ORG_TYPES["opensource"]["id"],
|
||||||
|
"website": "invalid-url",
|
||||||
|
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client_post("/json/billing/sponsorship", data)
|
||||||
|
|
||||||
|
self.assert_json_error(response, "Enter a valid URL.")
|
||||||
|
|
||||||
|
def test_request_sponsorship_form_with_blank_url(self) -> None:
|
||||||
|
user = self.example_user("hamlet")
|
||||||
|
self.login_user(user)
|
||||||
|
data = {
|
||||||
|
"organization-type": Realm.ORG_TYPES["opensource"]["id"],
|
||||||
|
"website": "",
|
||||||
|
"description": "Infinispan is a distributed in-memory key/value data store with optional schema.",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client_post("/json/billing/sponsorship", data)
|
||||||
|
|
||||||
|
self.assert_json_success(response)
|
||||||
|
|
||||||
def test_request_sponsorship(self) -> None:
|
def test_request_sponsorship(self) -> None:
|
||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
self.assertIsNone(get_customer_by_realm(user.realm))
|
self.assertIsNone(get_customer_by_realm(user.realm))
|
||||||
|
|
|
@ -203,7 +203,7 @@ def initial_upgrade(
|
||||||
|
|
||||||
|
|
||||||
class SponsorshipRequestForm(forms.Form):
|
class SponsorshipRequestForm(forms.Form):
|
||||||
website = forms.URLField(max_length=ZulipSponsorshipRequest.MAX_ORG_URL_LENGTH)
|
website = forms.URLField(max_length=ZulipSponsorshipRequest.MAX_ORG_URL_LENGTH, required=False)
|
||||||
organization_type = forms.IntegerField()
|
organization_type = forms.IntegerField()
|
||||||
description = forms.CharField(widget=forms.Textarea)
|
description = forms.CharField(widget=forms.Textarea)
|
||||||
|
|
||||||
|
@ -235,8 +235,8 @@ def sponsorship(
|
||||||
post_data.update(organization_type=organization_type)
|
post_data.update(organization_type=organization_type)
|
||||||
form = SponsorshipRequestForm(post_data)
|
form = SponsorshipRequestForm(post_data)
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
with transaction.atomic():
|
||||||
sponsorship_request = ZulipSponsorshipRequest(
|
sponsorship_request = ZulipSponsorshipRequest(
|
||||||
realm=realm,
|
realm=realm,
|
||||||
requested_by=user,
|
requested_by=user,
|
||||||
|
@ -275,3 +275,10 @@ def sponsorship(
|
||||||
)
|
)
|
||||||
|
|
||||||
return json_success()
|
return json_success()
|
||||||
|
else:
|
||||||
|
messages = []
|
||||||
|
for error_list in form.errors.get_json_data().values():
|
||||||
|
for error in error_list:
|
||||||
|
messages.append(error["message"])
|
||||||
|
message = " ".join(messages)
|
||||||
|
raise BillingError("Form validation error", message=message)
|
||||||
|
|
|
@ -238,7 +238,7 @@
|
||||||
<label>
|
<label>
|
||||||
<h4>Organization website</h4>
|
<h4>Organization website</h4>
|
||||||
</label>
|
</label>
|
||||||
<input name="website" style="width: 100%;" type="text" class="input-large" required/>
|
<input name="website" style="width: 100%;" type="text" class="input-large" placeholder="{{ _('Leave blank if your organization does not have a website.') }}"/>
|
||||||
<label>
|
<label>
|
||||||
<h4>Describe your organization briefly</h4>
|
<h4>Describe your organization briefly</h4>
|
||||||
</label>
|
</label>
|
||||||
|
|
Loading…
Reference in New Issue