feat: add community contribution ranks

This commit is contained in:
2026-09-29 22:18:57 +03:30
parent 0f6220f72c
commit 77567178ad
46 changed files with 2206 additions and 23 deletions

View File

@@ -77,6 +77,74 @@ def test_profile_and_authorization_roles(client, user_factory, auth_headers):
assert client.get("/api/auth/check-mobile", {"mobile": member.mobile}).json()["exists"]
def test_public_profile_hides_personal_fields_until_the_user_opts_in(client, user_factory, auth_headers, event_factory):
from apps.events.models import Event, Registration
member = user_factory(
username="public_member",
first_name="Public",
last_name="Member",
email="public.member@example.org",
mobile="09195550123",
student_id="402123456",
year_of_study=1402,
bio="A public introduction",
)
event = event_factory(slug="public-profile-event")
Registration.objects.create(event=event, user=member, status=Registration.StatusChoices.CONFIRMED)
draft_event = event_factory(slug="private-profile-event", status=Event.StatusChoices.DRAFT)
Registration.objects.create(event=draft_event, user=member, status=Registration.StatusChoices.ATTENDED)
public_path = f"/api/auth/public-profile/{member.username}"
hidden_response = client.get(public_path)
assert hidden_response.status_code == 200
hidden = hidden_response.json()
assert hidden["full_name"] == "Public Member"
assert hidden["details_visible"] is False
assert hidden["mobile"] is None
assert hidden["student_id"] is None
assert hidden["year_of_study"] is None
assert "date_joined" not in hidden
assert "blog_activity" not in hidden
assert [row["slug"] for row in hidden["events"]] == [event.slug]
assert hidden.get("email") is None
updated = client.put(
"/api/auth/profile",
data=json.dumps({"show_public_profile_details": True}),
content_type="application/json",
**auth_headers(member),
)
assert updated.status_code == 200, updated.content
visible = client.get(public_path).json()
assert visible["details_visible"] is True
assert visible["mobile"] == member.mobile
assert visible["student_id"] == member.student_id
assert visible["year_of_study"] == member.year_of_study
assert visible["email"] == member.email
assert "date_joined" not in visible
assert "blog_activity" not in visible
def test_public_profile_does_not_expose_placeholder_email(client, user_factory):
member = user_factory(
username="public_placeholder_email",
email="pending-public-placeholder@noemail.local",
show_public_profile_details=True,
)
response = client.get(f"/api/auth/public-profile/{member.username}")
assert response.status_code == 200
assert response.json().get("email") is None
def test_public_profile_does_not_expose_inactive_users(client, user_factory):
member = user_factory(username="inactive_public_member", is_active=False)
response = client.get(f"/api/auth/public-profile/{member.username}")
assert response.status_code == 404
def test_profile_picture_upload_validation_and_delete(client, user_factory, auth_headers, settings, tmp_path):
settings.MEDIA_ROOT = tmp_path
user = user_factory()
@@ -150,6 +218,8 @@ def test_profile_rejects_duplicate_email_and_resolves_education_codes(client, us
changed = client.put(path, data=json.dumps({"major": major.code, "university": university.code}),
content_type="application/json", **headers)
assert changed.status_code == 200, changed.content
assert changed.json()["major_code"] == major.code
assert changed.json()["university_code"] == university.code
user.refresh_from_db()
assert user.major == major and user.university == university
assert client.put(path, data=json.dumps({"major": None, "university": None}),
@@ -158,6 +228,92 @@ def test_profile_rejects_duplicate_email_and_resolves_education_codes(client, us
assert user.major is None and user.university is None
def test_profile_update_allows_username_changes_and_limits_plain_text_bio(client, user_factory, auth_headers):
member = user_factory(username="before_edit")
another = user_factory(username="already_taken")
headers = auth_headers(member)
path = "/api/auth/profile"
changed = client.put(
path,
data=json.dumps({"username": "after_edit", "bio": "A short plain text introduction"}),
content_type="application/json",
**headers,
)
assert changed.status_code == 200, changed.content
assert changed.json()["username"] == "after_edit"
duplicate = client.put(
path,
data=json.dumps({"username": another.username}),
content_type="application/json",
**headers,
)
assert duplicate.status_code == 400
too_long = client.put(
path,
data=json.dumps({"bio": "a" * 501}),
content_type="application/json",
**headers,
)
assert too_long.status_code == 422
at_limit = client.put(
path,
data=json.dumps({"bio": "a" * 500}),
content_type="application/json",
**headers,
)
assert at_limit.status_code == 200, at_limit.content
def test_profile_username_changes_follow_public_username_rules(client, user_factory, auth_headers):
member = user_factory(username="amir")
user_factory(username="already_taken")
headers = auth_headers(member)
path = "/api/auth/profile"
unchanged_legacy_name = client.put(
path,
data=json.dumps({"username": "amir"}),
content_type="application/json",
**headers,
)
too_short = client.put(
path,
data=json.dumps({"username": "abc1"}),
content_type="application/json",
**headers,
)
invalid_characters = client.put(
path,
data=json.dumps({"username": "valid-name"}),
content_type="application/json",
**headers,
)
duplicate = client.put(
path,
data=json.dumps({"username": "already_taken"}),
content_type="application/json",
**headers,
)
available = client.put(
path,
data=json.dumps({"username": "valid_name42"}),
content_type="application/json",
**headers,
)
case_insensitive_availability = client.get("/api/auth/check-username?username=ALREADY_TAKEN")
assert unchanged_legacy_name.status_code == 200
assert too_short.status_code == 400
assert invalid_characters.status_code == 400
assert duplicate.status_code == 400
assert available.status_code == 200, available.content
assert available.json()["username"] == "valid_name42"
assert case_insensitive_availability.status_code == 200
assert case_insensitive_availability.json()["exists"] is True
def test_authorization_rejects_unknown_group_and_refresh_bad_token(client, user_factory, auth_headers):
admin = user_factory(is_superuser=True, is_staff=True)
member = user_factory()