Add profile info

This commit is contained in:
cuom1999 2024-04-26 22:51:16 -05:00
parent 55a85689e9
commit 8d0045ec82
10 changed files with 353 additions and 157 deletions

View file

@ -6,7 +6,7 @@ from reversion.admin import VersionAdmin
from django.contrib.auth.admin import UserAdmin as OldUserAdmin
from django_ace import AceWidget
from judge.models import Profile
from judge.models import Profile, ProfileInfo
from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
@ -54,6 +54,13 @@ class TimezoneFilter(admin.SimpleListFilter):
return queryset.filter(timezone=self.value())
class ProfileInfoInline(admin.StackedInline):
model = ProfileInfo
can_delete = False
verbose_name_plural = "profile info"
fk_name = "profile"
class ProfileAdmin(VersionAdmin):
fields = (
"user",
@ -67,7 +74,6 @@ class ProfileAdmin(VersionAdmin):
"ip",
"mute",
"is_unlisted",
"is_banned_problem_voting",
"notes",
"is_totp_enabled",
"current_contest",
@ -90,6 +96,7 @@ class ProfileAdmin(VersionAdmin):
actions_on_top = True
actions_on_bottom = True
form = ProfileForm
inlines = (ProfileInfoInline,)
def get_queryset(self, request):
return super(ProfileAdmin, self).get_queryset(request).select_related("user")

View file

@ -39,6 +39,7 @@ from judge.models import (
BlogPost,
ContestProblem,
TestFormatterModel,
ProfileInfo,
)
from judge.widgets import (
@ -51,6 +52,7 @@ from judge.widgets import (
Select2MultipleWidget,
DateTimePickerWidget,
ImageWidget,
DatePickerWidget,
)
@ -69,6 +71,17 @@ class UserForm(ModelForm):
]
class ProfileInfoForm(ModelForm):
class Meta:
model = ProfileInfo
fields = ["tshirt_size", "date_of_birth", "address"]
widgets = {
"tshirt_size": Select2Widget(attrs={"style": "width:100%"}),
"date_of_birth": DatePickerWidget,
"address": forms.TextInput(attrs={"style": "width:100%"}),
}
class ProfileForm(ModelForm):
class Meta:
model = Profile

View file

@ -0,0 +1,69 @@
# Generated by Django 3.2.18 on 2024-04-27 03:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("judge", "0186_change_about_fields_max_len"),
]
operations = [
migrations.RemoveField(
model_name="profile",
name="is_banned_problem_voting",
),
migrations.CreateModel(
name="ProfileInfo",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"tshirt_size",
models.CharField(
blank=True,
choices=[
("S", "Small (S)"),
("M", "Medium (M)"),
("L", "Large (L)"),
("XL", "Extra Large (XL)"),
("XXL", "2 Extra Large (XXL)"),
],
max_length=5,
null=True,
verbose_name="t-shirt size",
),
),
(
"date_of_birth",
models.DateField(
blank=True, null=True, verbose_name="date of birth"
),
),
(
"address",
models.CharField(
blank=True, max_length=255, null=True, verbose_name="address"
),
),
(
"profile",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="info",
to="judge.profile",
verbose_name="profile associated",
),
),
],
),
]

View file

@ -43,6 +43,7 @@ from judge.models.profile import (
Profile,
Friend,
OrganizationProfile,
ProfileInfo,
)
from judge.models.runtime import Judge, Language, RuntimeVersion
from judge.models.submission import (

View file

@ -26,6 +26,15 @@ from judge.caching import cache_wrapper
__all__ = ["Organization", "Profile", "OrganizationRequest", "Friend"]
TSHIRT_SIZES = (
("S", "Small (S)"),
("M", "Medium (M)"),
("L", "Large (L)"),
("XL", "Extra Large (XL)"),
("XXL", "2 Extra Large (XXL)"),
)
class EncryptedNullCharField(EncryptedCharField):
def get_prep_value(self, value):
if not value:
@ -213,11 +222,6 @@ class Profile(models.Model):
help_text=_("User will not be ranked."),
default=False,
)
is_banned_problem_voting = models.BooleanField(
verbose_name=_("banned from voting"),
help_text=_("User will not be able to vote on problems' point values."),
default=False,
)
rating = models.IntegerField(null=True, default=None, db_index=True)
current_contest = models.OneToOneField(
"ContestParticipation",
@ -422,6 +426,36 @@ class Profile(models.Model):
verbose_name_plural = _("user profiles")
class ProfileInfo(models.Model):
profile = models.OneToOneField(
Profile,
verbose_name=_("profile associated"),
on_delete=models.CASCADE,
related_name="info",
)
tshirt_size = models.CharField(
max_length=5,
choices=TSHIRT_SIZES,
verbose_name=_("t-shirt size"),
null=True,
blank=True,
)
date_of_birth = models.DateField(
verbose_name=_("date of birth"),
null=True,
blank=True,
)
address = models.CharField(
max_length=255,
verbose_name=_("address"),
null=True,
blank=True,
)
def __str__(self):
return f"{self.profile.user.username}'s Info"
class OrganizationRequest(models.Model):
user = models.ForeignKey(
Profile,

View file

@ -35,8 +35,8 @@ from django.views.generic import DetailView, ListView, TemplateView
from django.template.loader import render_to_string
from reversion import revisions
from judge.forms import UserForm, ProfileForm
from judge.models import Profile, Rating, Submission, Friend
from judge.forms import UserForm, ProfileForm, ProfileInfoForm
from judge.models import Profile, Rating, Submission, Friend, ProfileInfo
from judge.performance_points import get_pp_breakdown
from judge.ratings import rating_class, rating_progress
from judge.tasks import import_users
@ -390,21 +390,25 @@ class UserPerformancePointsAjax(UserProblemsPage):
@login_required
def edit_profile(request):
profile = request.profile
profile_info, created = ProfileInfo.objects.get_or_create(profile=profile)
if request.method == "POST":
form_user = UserForm(request.POST, instance=request.user)
form = ProfileForm(
request.POST, request.FILES, instance=profile, user=request.user
)
form_info = ProfileInfoForm(request.POST, instance=profile_info)
if form_user.is_valid() and form.is_valid():
with revisions.create_revision():
form_user.save()
form.save()
form_info.save()
revisions.set_user(request.user)
revisions.set_comment(_("Updated on site"))
return HttpResponseRedirect(request.path)
else:
form_user = UserForm(instance=request.user)
form = ProfileForm(instance=profile, user=request.user)
form_info = ProfileInfoForm(instance=profile_info)
tzmap = settings.TIMEZONE_MAP
@ -415,6 +419,7 @@ def edit_profile(request):
"require_staff_2fa": settings.DMOJ_REQUIRE_STAFF_2FA,
"form_user": form_user,
"form": form,
"form_info": form_info,
"title": _("Edit profile"),
"profile": profile,
"TIMEZONE_MAP": tzmap or "http://momentjs.com/static/img/world.png",

View file

@ -25,3 +25,25 @@ class DateTimePickerWidget(forms.DateTimeInput):
attrs, {"type": self.input_type, "name": name, "value": value}
)
return format_html("<input{}>", flatatt(final_attrs))
class DatePickerWidget(forms.DateInput):
input_type = "date"
def render(self, name, value, attrs=None, renderer=None):
if value is None:
value = ""
elif isinstance(value, str):
# Attempt to parse the string back to date
parsed_date = parse_date(value)
if parsed_date is not None:
value = parsed_date.strftime("%Y-%m-%d")
else:
value = ""
else:
value = value.strftime("%Y-%m-%d")
final_attrs = self.build_attrs(
attrs, {"type": self.input_type, "name": name, "value": value}
)
return format_html("<input{}>", flatatt(final_attrs))