Skip to content

feat: Add social media username validation#1043

Closed
nourzakhama2003 wants to merge 6 commits into
alphaonelabs:mainfrom
nourzakhama2003:feat/add-social-media-validation
Closed

feat: Add social media username validation#1043
nourzakhama2003 wants to merge 6 commits into
alphaonelabs:mainfrom
nourzakhama2003:feat/add-social-media-validation

Conversation

@nourzakhama2003
Copy link
Copy Markdown

@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: letters, digits, hyphen, underscore, period; discriminator (if present) must be exactly 4 digits.
      • Slack: 1–21 chars; must start with a lowercase letter; lowercase only; allowed chars include lowercase letters, digits, periods, hyphens, underscores.
      • GitHub: 1–39 chars; alphanumeric and hyphens; disallows leading/trailing hyphens and invalid hyphen sequences.
    • Validators accept empty/falsy values, use gettext_lazy messages, and raise ValidationError with specific codes.
  • web/models.py

    • Attached validators to Profile.discord_username, Profile.slack_username, and Profile.github_username.
    • Reduced max_length to platform-aligned limits: discord_username=37, slack_username=21, github_username=39.
    • Profile.save() now calls self.full_clean() prior to saving 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 invoke 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 valid inputs, boundary lengths, invalid formats, empty values, and integration with Profile.full_clean() and ProfileUpdateForm.
  • Commits

    • Includes a refactor commit adding type hints and small cleanup to validator functions; PR description indicates checklist items and tests were added.

Impact / Notes

  • UX: Forms now surface platform-specific validation errors, improving immediate 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 DB max_length is 37 while the validator enforces a username-part max of 32 (plus optional 4-digit discriminator and '#'); confirm the intended DB limit vs validator expectation to ensure consistency.

@github-actions
Copy link
Copy Markdown
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
Copy Markdown
Contributor

coderabbitai Bot commented Mar 22, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Pro

Run ID: b0440973-f76a-48d6-84a7-f9c822b47da4

📥 Commits

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

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

Walkthrough

Adds new Discord/Slack/GitHub username validators, applies them to Profile fields (reducing max_length), calls model validation on save, extends ProfileUpdateForm with a shared social-field 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. Enforce platform-specific length/format rules, raise ValidationError with codes, accept 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 saving.
Form integration
web/forms.py
ProfileUpdateForm imports 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 into 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.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Form as "ProfileUpdateForm"
    participant Validator as "validate_*_username"
    participant Model as "Profile (full_clean)"
    participant DB

    User->>Form: submit form data (discord/slack/github)
    Form->>Validator: call validator per field (via _clean_social_media_username)
    Validator-->>Form: raise/return ValidationError or pass
    Form->>Model: save instance (instance bound)
    Model->>Model: full_clean() -> run field validators
    Model-->>Form: raise ValidationError back to form (if any)
    Model->>DB: save (if validation passed)
    DB-->>User: persist confirmed
Loading

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
Copy Markdown
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

Comment thread web/forms.py Outdated
Comment thread web/tests/test_social_media_validators.py Outdated
Comment thread web/tests/test_social_media_validators.py Outdated
Comment thread web/validators.py
@nourzakhama2003
Copy link
Copy Markdown
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
Copy Markdown
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

Comment thread web/forms.py
Comment thread web/tests/test_social_media_validators.py
Comment thread web/validators.py Outdated
Copy link
Copy Markdown
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
Copy Markdown
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

Comment thread web/models.py
Comment thread 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
Copy Markdown
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

Comment thread web/validators.py Outdated
Comment thread web/validators.py Outdated
- 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.

@github-actions
Copy link
Copy Markdown
Contributor

🚚 This Repository Is Moving

Hi @nourzakhama2003, thank you for your contribution!

We are in the process of migrating most of the logic from this repository to our new repository: alphaonelabs/learn.

What this means for your PR

Please do not merge or continue work here. Instead:

  1. Review the alphaonelabs/learn repository and familiarize yourself with its tech stack.
  2. Adapt your changes to work with that codebase.
  3. Open a new Pull Request in alphaonelabs/learn.

This PR has been automatically closed. Once you have opened your PR in the new repository, feel free to reference it here.

Thank you for your understanding and continued support! 🙏

@github-actions github-actions Bot closed this Mar 26, 2026
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.

2 participants