Add profile info
This commit is contained in:
parent
55a85689e9
commit
8d0045ec82
10 changed files with 353 additions and 157 deletions
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
69
judge/migrations/0187_profile_info.py
Normal file
69
judge/migrations/0187_profile_info.py
Normal 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",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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 (
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue