Skip to content

feat: Add social media username validation#1043

Open
nourzakhama2003 wants to merge 5 commits intoalphaonelabs:mainfrom
nourzakhama2003:feat/add-social-media-validation
Open

feat: Add social media username validation#1043
nourzakhama2003 wants to merge 5 commits intoalphaonelabs:mainfrom
nourzakhama2003:feat/add-social-media-validation

Conversation

@nourzakhama2003
Copy link

@nourzakhama2003 nourzakhama2003 commented Mar 22, 2026

Add Social Media Username Validation

Problem

The Alpha One Labs platform currently allows users to enter any text in social media username fields (discord_username, slack_username, github_username) without validation. This leads to:

  • Invalid usernames being saved to the database
  • Poor user experience (no feedback on format requirements)
  • Data integrity issues
  • No enforceable constraints on profile data quality

Solution

Implemented platform-specific validators for each social media platform following their official username requirements:

New Features

  1. web/validators.py - Three custom validators:

    • validate_discord_username(): 2-32 characters, alphanumeric + special chars (-, _, .)
    • validate_slack_username(): 1-21 characters, lowercase only, must start with letter
    • validate_github_username(): 1-39 characters, alphanumeric + hyphens (no leading/trailing hyphens)
  2. Model-Level Validation - Added validators to Profile model fields to ensure data integrity at database layer

  3. Form-Level Validation - Enhanced ProfileUpdateForm with clean_*_username() methods for user-friendly error messages

Changes

  • web/validators.py (new): 95 lines - Custom Django validators
  • web/models.py: Applied validators to Profile model fields
  • web/forms.py: Added cleaner methods and validator imports
  • web/tests/test_social_media_validators.py (new): 350+ lines - Comprehensive test suite with 30+ test cases

Testing

Test Coverage:

  • Valid usernames for all platforms
  • Edge cases (minimum/maximum length, special characters)
  • Invalid formats and characters
  • Empty values handling
  • Form validation integration
  • Model validation integration

Benefits

  • Prevents invalid data from being saved
  • Improves user experience with real-time validation feedback
  • Ensures data quality and consistency
  • Follows platform-specific requirements (not generic validation)
  • Zero breaking changes - existing valid usernames remain unaffected

Type

  • Bug fix (prevents invalid data)
  • New feature (validators)
  • Breaking change
  • Documentation update needed? (No - self-documenting code)

Checklist

  • Code follows project style guidelines
  • Tests added for new functionality
  • No breaking changes
  • Documentation in code (docstrings, comments)
  • Validators follow Django best practices

Purpose

Add platform-specific validation for social media username fields (discord_username, slack_username, github_username) on the Profile model and surface user-facing validation errors in forms to prevent invalid or platform-incompatible values from being saved.

Key Modifications

  • web/validators.py

    • New validators: validate_discord_username, validate_slack_username, validate_github_username.
    • Rules:
      • Discord: optional discriminator (#1234); username part 2–32 chars; allowed chars: alphanumeric, hyphen, underscore, period; discriminator (if present) must be exactly 4 digits.
      • Slack: 1–21 chars; lowercase only; must start with a letter; allowed chars include lowercase letters, digits, periods, hyphens, underscores.
      • GitHub: 1–39 chars; alphanumeric and hyphens; disallows leading/trailing hyphens and disallowed hyphen sequences.
    • Validators allow empty/falsy values, use gettext_lazy messages, and raise ValidationError with specific codes.
  • web/models.py

    • Attached the new validators to Profile.discord_username, Profile.slack_username, and Profile.github_username.
    • Adjusted max_length values to platform-aligned limits: discord_username=37, slack_username=21, github_username=39.
    • Profile.save() now calls self.full_clean() before persisting so model-level validation runs on save.
  • web/forms.py

    • ProfileUpdateForm: added helper _clean_social_media_username(field_name, validator) and field cleaners clean_discord_username, clean_slack_username, clean_github_username that call the validators and convert ValidationError into form errors for user-facing feedback.
  • web/tests/test_social_media_validators.py

    • New comprehensive test suite (~350+ lines, 30+ test cases) covering validator behavior, boundary lengths, invalid formats, empty values, and integration with Profile.full_clean() and ProfileUpdateForm.
  • Commits

    • Includes a data migration and an updated GitHub username error message in a follow-up commit.

Impact / Notes

  • UX: Forms now present platform-specific validation messages, improving user feedback and preventing common formatting mistakes.
  • Data integrity: Model-level validators plus full_clean() on save enforce validation across save paths, reducing invalid persisted data.
  • Reviewer attention: discord_username max_length set to 37 while the validator enforces a username-part max of 32 (plus optional discriminator). Reviewers should confirm the intent of the 37-character max to ensure consistency between DB field limits and validator expectations.

@github-actions
Copy link
Contributor

👀 Peer Review Required

Hi @nourzakhama2003! This pull request does not yet have a peer review.

Before this PR can be merged, please request a review from one of your peers:

  • Go to the PR page and click "Reviewers" on the right sidebar.
  • Select a team member or contributor to review your changes.
  • Once they approve, this reminder will be automatically removed.

Thank you for contributing! 🎉

@github-actions github-actions bot added the files-changed: 4 PR changes 4 files label Mar 22, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

Warning

Rate limit exceeded

@nourzakhama2003 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5cde8633-dd5d-4ff9-ab5c-b4df9c7e9e5c

📥 Commits

Reviewing files that changed from the base of the PR and between eb34ef4 and b06a618.

📒 Files selected for processing (1)
  • web/validators.py

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Adds new platform validators for Discord/Slack/GitHub, applies them to Profile fields (reducing max_length), runs model validation on save, extends ProfileUpdateForm with a shared social username cleaner and per-field cleaners, and adds comprehensive tests for validators, model and form validation.

Changes

Cohort / File(s) Summary
Validators
web/validators.py
New module: validate_discord_username, validate_slack_username, validate_github_username with platform-specific length/format rules, ValidationError messages and code values; validators are no-ops for empty values.
Model changes
web/models.py
Profile social fields updated: discord_username max_length 50→37 (validators=[validate_discord_username]), slack_username 50→21 (validators=[validate_slack_username]), github_username 50→39 (validators=[validate_github_username]); Profile.save() now calls self.full_clean() before persisting.
Form integration
web/forms.py
ProfileUpdateForm imports platform validators and adds _clean_social_media_username(field_name, validator) plus clean_discord_username, clean_slack_username, clean_github_username that delegate to validators and convert model ValidationError to forms.ValidationError.
Tests
web/tests/test_social_media_validators.py
New test module with unit tests for each validator (valid/invalid cases, empty accepted), model full_clean() tests verifying field-specific message_dict errors, and ProfileUpdateForm tests for valid, invalid, and empty social fields.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding validation for social media usernames across Discord, Slack, and GitHub platforms.
Docstring Coverage ✅ Passed Docstring coverage is 86.05% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/forms.py`:
- Around line 750-775: Replace the duplicated logic in clean_discord_username,
clean_slack_username, and clean_github_username with a single helper (e.g.,
_clean_username(field_key, validator)) that fetches username =
self.cleaned_data.get(field_key, ""), runs the passed validator
(validate_discord_username / validate_slack_username / validate_github_username)
inside a try/except, and on ValidationError re-raises forms.ValidationError
using the original error message; then have each clean_* method simply call this
helper with its field name and validator. Ensure the helper returns the username
and that the signatures of clean_discord_username, clean_slack_username, and
clean_github_username remain present but delegate to _clean_username to preserve
Django form behavior.

In `@web/models.py`:
- Around line 66-83: The CharField max_length values for discord_username,
slack_username, and github_username must be reduced to match their validators to
prevent DB-level persistence of overlong values: change discord_username
max_length from 50 to 32, slack_username max_length from 50 to 21, and
github_username max_length from 50 to 39 (update the fields discord_username,
slack_username, github_username accordingly) and create/apply a migration so the
schema and validators remain consistent.

In `@web/tests/test_social_media_validators.py`:
- Around line 41-50: The tests for validate_discord_username currently assert
translated message substrings; instead update them to assert the
ValidationError.code (a stable error code) equals a specific code (e.g.,
"invalid_discord_username") after raising the error from
validate_discord_username; replace self.assertIn("2 and 32", str(cm.exception))
with an assertion like self.assertEqual(cm.exception.code,
"invalid_discord_username") and apply the same change to the other similar
assertions referenced (the other validate_discord_username tests around the
noted ranges).
- Around line 189-229: The tests (e.g., test_profile_with_valid_usernames,
test_profile_with_invalid_discord_username,
test_profile_with_invalid_slack_username,
test_profile_with_invalid_github_username) currently persist invalid Profile
instances using Profile.objects.create(...) before calling full_clean(); change
each test to instantiate unsaved profiles with Profile(... ) (no
.objects.create), then call profile.full_clean() (assert no exception for the
valid case, and use with self.assertRaises(ValidationError) for the invalid
cases) so invalid data is not written to the DB and the tests remain isolated
and intention-revealing.

In `@web/validators.py`:
- Around line 26-33: The current logic in validators.py trims everything after
the first "#" into username_part which allows invalid discriminators like
"User#", "User#abcd", or "User#1234#x"; update the validation to first ensure
the value contains at most one "#" (or none), and if a "#" is present validate
the suffix is exactly 4 numeric digits (the Discord discriminator) before
accepting the username_part; keep raising ValidationError (use the same message
or add a second message for invalid discriminator) when the username length or
discriminator format is invalid and reference the existing variables/value and
function that raises ValidationError to locate where to apply the checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: af54cc89-7101-40ab-93a5-786e0c792f79

📥 Commits

Reviewing files that changed from the base of the PR and between c94caf8 and 1ed5d31.

📒 Files selected for processing (4)
  • web/forms.py
  • web/models.py
  • web/tests/test_social_media_validators.py
  • web/validators.py

@nourzakhama2003
Copy link
Author

Hi team,

This PR is ready for review. Could someone please take a look?

@ayesha1145 @omsherikar (or any other active contributor you see)

Thank you!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/forms.py`:
- Around line 750-765: The _clean_social_media_username helper is catching a
ValidationError and converting it to a plain string which drops error codes and
metadata; instead, re-raise a forms.ValidationError preserving the original
ValidationError details (e.g., use the caught exception's error_list /
message_dict / error_list structure or rethrow the original ValidationError) so
error codes from your validators in web/validators.py (like
invalid_discord_length, invalid_slack_chars) remain intact; update
_clean_social_media_username to catch ValidationError as e and raise
forms.ValidationError(e.error_list or e) (or otherwise pass along e.messages and
e.code metadata) rather than raising a plain string.

In `@web/models.py`:
- Around line 66-83: The Profile.save method currently doesn't run model
validation so field validators like validate_discord_username,
validate_slack_username, and validate_github_username can be bypassed; update
Profile.save to call self.full_clean() before super().save(...) (while
preserving the existing referral_code generation logic that checks/sets
self.referral_code via generate_referral_code()) so all validators run on every
save.

In `@web/tests/test_social_media_validators.py`:
- Around line 198-248: The tests create new Profile(user=self.user, ...) which
conflicts with the existing Profile created by User.objects.create_user via the
post_save signal; instead reuse the existing profile via self.user.profile: in
test_profile_with_valid_social_usernames,
test_profile_with_invalid_discord_username,
test_profile_with_invalid_slack_username and
test_profile_with_invalid_github_username get the profile object with profile =
self.user.profile, set the discord_username/slack_username/github_username
fields on that profile, then call profile.full_clean() (and profile.save() for
the valid case) so you don't hit the OneToOne uniqueness error or unrelated user
validation errors.

In `@web/validators.py`:
- Around line 107-113: The current re.match allows consecutive hyphens; update
the pattern used in the validation (the re.match call that checks the variable
value) to reject consecutive hyphens by adding a negative lookahead (e.g. start
the regex with (?!.*--) ) so usernames like "octo--cat" fail; keep raising the
same ValidationError with code "invalid_github_chars" and the existing message
so behavior and error surface stay consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ac9c5cfc-20e6-4325-b124-ee77f111e380

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed5d31 and e00d4a1.

📒 Files selected for processing (4)
  • web/forms.py
  • web/models.py
  • web/tests/test_social_media_validators.py
  • web/validators.py

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR has 3 unresolved review conversations. Please resolve them before this PR can be merged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/forms.py (1)

709-723: ⚠️ Potential issue | 🟡 Minor

Align the form max_length values with the backend limits.

These fields still advertise 50 characters even though Profile and the validators now enforce 37/21/39. Matching the form limits will give users the right browser-side constraint and avoid generic 50-character errors masking the platform-specific feedback this PR adds.

🔧 Suggested adjustment
     discord_username = forms.CharField(
-        max_length=50,
+        max_length=37,
         required=False,
         widget=TailwindInput(),
         help_text="Discord username (visible if profile is public)",
     )
     slack_username = forms.CharField(
-        max_length=50, required=False, widget=TailwindInput(), help_text="Slack username (visible if profile is public)"
+        max_length=21,
+        required=False,
+        widget=TailwindInput(),
+        help_text="Slack username (visible if profile is public)",
     )
     github_username = forms.CharField(
-        max_length=50,
+        max_length=39,
         required=False,
         widget=TailwindInput(),
         help_text="GitHub username (visible if profile is public)",
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/forms.py` around lines 709 - 723, The form fields discord_username,
slack_username, and github_username in web/forms.py still use max_length=50 but
the backend/Profile validators enforce 37, 21, and 39 respectively; update those
CharField max_length values to 37 for discord_username, 21 for slack_username,
and 39 for github_username so the browser-side constraints match the backend
validation and surface platform-specific errors correctly.
♻️ Duplicate comments (1)
web/tests/test_social_media_validators.py (1)

203-205: 🧹 Nitpick | 🔵 Trivial

Reuse self.user.profile and assert save() in these invalid model tests.

create_user() already creates a Profile, so constructing a second Profile(user=self.user, ...) pulls in an unrelated user uniqueness error. Reusing self.user.profile keeps the assertions isolated and lets these tests cover the new Profile.save() guarantee instead of only full_clean().

🔧 Suggested pattern
-        profile = Profile(
-            user=self.user,
-            discord_username="A",  # Too short
-            slack_username="valid",
-            github_username="valid",
-        )
-        with self.assertRaises(ValidationError) as cm:
-            profile.full_clean()
+        profile = self.user.profile
+        profile.discord_username = "A"  # Too short
+        profile.slack_username = "valid"
+        profile.github_username = "valid"
+        with self.assertRaises(ValidationError) as cm:
+            profile.save()
         self.assertIn("discord_username", cm.exception.message_dict)

As per coding guidelines **/tests/**: Ensure tests are descriptive, cover edge cases, and follow the project's existing test patterns. Verify test isolation and meaningful assertions.

Also applies to: 217-251

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/tests/test_social_media_validators.py` around lines 203 - 205, Tests
create a second Profile manually causing unique-user conflicts; update the tests
that currently construct Profile(user=self.user, ...) to reuse the existing
profile from setUp via self.user.profile, mutate the fields to the invalid
values being tested, then call and assert on profile.save() (e.g., expect a
ValidationError) so the tests exercise Profile.save() behavior rather than only
full_clean(); update the test cases mentioned (around the setUp block and the
tests covering lines ~217-251) to follow this pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/models.py`:
- Around line 66-83: Existing rows may violate the new max_length/validators and
Profile.save() now calls full_clean(), so create and run a data migration before
deploying the model change that (1) scans profiles for invalid/over-limit values
for discord_username, slack_username, github_username (and any other
social-handle fields being shrunk), (2) either trims values to the new
max_length or moves originals to a temp backup column/log and sets them
blank/null depending on business rules, and (3) logs/report affected user IDs so
you can verify; deploy that migration first (or temporarily keep the old
max_length/remove full_clean() from Profile.save() until backfill completes)
then release the model/validator changes.

In `@web/validators.py`:
- Around line 107-113: Update the ValidationError message raised where the regex
check on value occurs so the user-facing text matches the rule enforced by the
regex: state that GitHub usernames may contain alphanumerics and hyphens, cannot
start or end with a hyphen, and may not contain consecutive hyphens (e.g.,
"octo--cat" is invalid). Modify the string inside the existing _("...") passed
to ValidationError (keeping the same code="invalid_github_chars" and use of _())
to reflect the consecutive-hyphen restriction.

---

Outside diff comments:
In `@web/forms.py`:
- Around line 709-723: The form fields discord_username, slack_username, and
github_username in web/forms.py still use max_length=50 but the backend/Profile
validators enforce 37, 21, and 39 respectively; update those CharField
max_length values to 37 for discord_username, 21 for slack_username, and 39 for
github_username so the browser-side constraints match the backend validation and
surface platform-specific errors correctly.

---

Duplicate comments:
In `@web/tests/test_social_media_validators.py`:
- Around line 203-205: Tests create a second Profile manually causing
unique-user conflicts; update the tests that currently construct
Profile(user=self.user, ...) to reuse the existing profile from setUp via
self.user.profile, mutate the fields to the invalid values being tested, then
call and assert on profile.save() (e.g., expect a ValidationError) so the tests
exercise Profile.save() behavior rather than only full_clean(); update the test
cases mentioned (around the setUp block and the tests covering lines ~217-251)
to follow this pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 12ea4fb2-ebc9-4d14-8df7-3df3f07253c2

📥 Commits

Reviewing files that changed from the base of the PR and between e00d4a1 and 7f346a6.

📒 Files selected for processing (4)
  • web/forms.py
  • web/models.py
  • web/tests/test_social_media_validators.py
  • web/validators.py

@github-actions github-actions bot added files-changed: 6 PR changes 6 files and removed files-changed: 4 PR changes 4 files labels Mar 22, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/validators.py`:
- Line 13: Add explicit type hints to the public validator function signatures:
change validate_discord_username, validate_* at the symbol names found at lines
~61 and ~89 to accept value: str | None (or Optional[str]) and return -> None;
update each function signature (e.g., def validate_discord_username(value: str |
None) -> None:) so static type checkers and linters understand the input/output
types while leaving the internal logic unchanged.
- Line 73: Remove the redundant lower-bound length checks that follow earlier
falsy-value returns: replace the condition "if len(value) < 1 or len(value) >
21:" (and the analogous check at the second occurrence) with a single
upper-bound check "if len(value) > 21:" so the validation only enforces the max
length; keep the existing early returns for falsy values intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 857ceb01-8559-46a4-a33a-1fd46677ea5e

📥 Commits

Reviewing files that changed from the base of the PR and between 7f346a6 and eb34ef4.

⛔ Files ignored due to path filters (2)
  • web/migrations/0064_cleanup_social_media_usernames.py is excluded by !**/migrations/**
  • web/migrations/0065_alter_profile_social_username_fields.py is excluded by !**/migrations/**
📒 Files selected for processing (1)
  • web/validators.py

- Add explicit type hints to all validator function signatures: Optional[str] -> None
- Remove redundant len(value) < 1 checks after early returns for empty values
- Improves code clarity and enables better static analysis
@github-actions github-actions bot dismissed their stale review March 22, 2026 21:44

All review conversations have been resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

files-changed: 6 PR changes 6 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant