Skip to content

Commit

Permalink
Clean and standardise instagram entries
Browse files Browse the repository at this point in the history
- Audit all person identifiers and their validation
- Check the domain and the username for valid entries
  • Loading branch information
VirginiaDooley committed Jul 17, 2024
1 parent 6067222 commit 3352275
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 3 deletions.
22 changes: 22 additions & 0 deletions ynr/apps/people/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,25 @@ standing in.
Thing like their political party should be stored on the _candidacy_.
This is currently represented as a `Membership` in the `popolo` app.

# Person Identifiers

`PersonIdentifiers` are used to store contact information and online presence for a candidate. Users can add/edit these identifiers in the person update form. The following fields are available in the `PersonIdentifier` model with the corresponding form labels and accepted formats:
```
email = "Email": Accepts a valid email address; raises an exception if the email is submitted with an invalid format. The email address must be unique.
facebook_page_url = "Facebook Page": Accepts a valid URL but does not currently validate the domain.
facebook_personal_url = "Facebook Personal": Same as above.
homepage_url = "Homepage": Accepts a valid URL
blog_url = "Blog": Accepts a valid URL
linkedin_url = "Linkedin": Accepts a valid URL; does not currently validate the domain or the format of the username.
party_ppc_page_url = "Party Candidate Page": Accepts a valid URL
twitter_username = "Twitter": Accepts a Twitter username and returns the full Twitter URL.No longer validates the username actually exists.
mastodon_username = "Mastodon": Accepts and returns a valid Mastodon URL and validates the domain and username.
wikipedia_url = "Wikipedia": Accepts a valid URL; does not currently validate the domain.
wikidata_id = "Wikidata": Accepts a valid Wikidata ID
youtube_profile = "YouTube Profile": Accepts a valid URL; does not currently validate the domain.
instagram_url = "Instagram Profile": Accepts and returns a valid Instagram URL and validates the domain and format of the username.
blue_sky_url = "Bluesky URL": Accepts and returns a valid URL
threads_url = "Threads URL": Accepts and returns a valid URL
tiktok_url = "TikTok URL": Accepts and returns a valid URL
other_url = "Other URL": Accepts and returns a valid URL
```
12 changes: 12 additions & 0 deletions ynr/apps/people/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
StrippedCharField,
)
from people.helpers import (
clean_instagram_url,
clean_mastodon_username,
clean_twitter_username,
clean_wikidata_id,
Expand Down Expand Up @@ -116,6 +117,17 @@ def clean(self):
self.add_error(None, e)
return self.cleaned_data

def clean_instagram_url(self, username):
if self.instance.value != username:
self.instance.internal_identifier = None
if self.instance.internal_identifier:
return username
try:
return clean_instagram_url(username)
except ValueError as e:
raise ValidationError(e)
return username

def clean_twitter_username(self, username):
if self.instance.value != username:
self.instance.internal_identifier = None
Expand Down
24 changes: 24 additions & 0 deletions ynr/apps/people/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,30 @@ def clean_twitter_username(username):
return username


def clean_instagram_url(url):
parsed_username = urlparse(url)
if parsed_username.netloc not in [
"instagram.com",
"www.instagram.com",
"instagr.am",
"www.instagr.am",
]:
raise ValueError(
"The Instagram URL must be from a valid Instagram domain."
)
username = parsed_username.path.strip("/")
if not re.search(
r"^(?!.*[_.]{2})(?!.*[_.]$)[a-zA-Z0-9._]{1,30}$",
username,
):
raise ValueError(
"This is not a valid Instagram username. Please try again."
)
return "https://{domain}/{username}".format(
domain=parsed_username.netloc, username=username
)


def clean_wikidata_id(identifier):
identifier = identifier.strip().lower()
m = re.search(r"^.*wikidata.org/(wiki|entity)/(\w+)", identifier)
Expand Down
2 changes: 2 additions & 0 deletions ynr/apps/people/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ def get_value_html(self):

if self.value_type == "twitter_username":
url = format_html("https://twitter.com/{}", self.value)
if self.value_type == "instagram_url":
url = self.value

if self.value.startswith("http"):
url = format_html("{}", self.value)
Expand Down
61 changes: 59 additions & 2 deletions ynr/apps/people/tests/test_person_form_identifier_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ def _submit_values(self, value, value_type="twitter_username"):
form["source"] = "They changed their username"
form["tmp_person_identifiers-0-value_type"] = value_type
form["tmp_person_identifiers-0-value"] = value
form.submit()
return form.submit()

def _submit_mastodon_values(self, value, value_type="mastodon_username"):
Expand All @@ -163,7 +162,30 @@ def _submit_mastodon_values(self, value, value_type="mastodon_username"):
form["source"] = "They changed their username"
form["tmp_person_identifiers-0-value_type"] = value_type
form["tmp_person_identifiers-0-value"] = value
form.submit()
return form.submit()

def _submit_mastodon_values(self, value, value_type="mastodon_username"):
resp = self.app.get(
reverse("person-update", kwargs={"person_id": self.person.pk}),
user=self.user,
)

form = resp.forms[1]
form["source"] = "They changed their username"
form["tmp_person_identifiers-0-value_type"] = value_type
form["tmp_person_identifiers-0-value"] = value
return form.submit()

def _submit_instagram_values(self, value, value_type="instagram_url"):
resp = self.app.get(
reverse("person-update", kwargs={"person_id": self.person.pk}),
user=self.user,
)

form = resp.forms[1]
form["source"] = "They changed their username"
form["tmp_person_identifiers-0-value_type"] = value_type
form["tmp_person_identifiers-0-value"] = value
return form.submit()

def test_twitter_bad_url(self):
Expand All @@ -185,6 +207,41 @@ def test_twitter_full_url(self):
PersonIdentifier.objects.get().value, "madeuptwitteraccount"
)

def test_clean_instagram_url(self):
resp = self._submit_instagram_values(
"https://www.instagr.am/disco_dude"
)
self.assertEqual(resp.status_code, 302)
instagram_url_qs = PersonIdentifier.objects.filter(
value_type="instagram_url"
)
self.assertEqual(instagram_url_qs.count(), 1)
self.assertEqual(
instagram_url_qs[0].value,
"https://www.instagr.am/disco_dude",
)

def test_bad_instagram_domain(self):
resp = self._submit_instagram_values("www.instagl.am/blah")
form = resp.context["identifiers_formset"]
self.assertFalse(form.is_valid())
self.assertEqual(
form[0].non_field_errors(),
["The Instagram URL must be from a valid Instagram domain."],
)

def test_bad_instagram_username(self):
resp = self._submit_instagram_values(
"https://www.instagr.am/________blah"
)
self.assertEqual(resp.status_code, 200)
form = resp.context["identifiers_formset"]
self.assertFalse(form.is_valid())
self.assertEqual(
form[0].non_field_errors(),
["This is not a valid Instagram username. Please try again."],
)

def test_mastodon_bad_url(self):
# submit a username missing the `@` symbol
resp = self._submit_mastodon_values("https://mastodon.social/joe")
Expand Down
17 changes: 17 additions & 0 deletions ynr/apps/people/tests/test_person_identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ def test_get_value_html_twitter(self):
# Test the value type HTML
self.assertEqual(pi.get_value_type_html, "Twitter")

def test_get_value_html_instagram(self):
pi = PersonIdentifier.objects.create(
person=self.person,
value="https://www.instagram.com/democlub",
value_type="instagram_url",
internal_identifier="2324",
)

# Test the value HTML
self.assertEqual(
pi.get_value_html,
"""<a href="https://www.instagram.com/democlub" rel="nofollow">https://www.instagram.com/democlub</a>""",
)

# Test the value type HTML
self.assertEqual(pi.get_value_type_html, "Instagram")

def test_get_value_html_mastodon(self):
pi = PersonIdentifier.objects.create(
person=self.person,
Expand Down
2 changes: 1 addition & 1 deletion ynr/apps/ynr_refactoring/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PersonIdentifierFields(Enum):
wikipedia_url = "Wikipedia"
wikidata_id = "Wikidata"
youtube_profile = "YouTube Profile"
instagram_url = "Instagram Profile"
instagram_url = "Instagram URL"
blue_sky_url = "Bluesky URL"
threads_url = "Threads URL"
tiktok_url = "TikTok URL"
Expand Down

0 comments on commit 3352275

Please sign in to comment.