Add profile image

This commit is contained in:
cuom1999 2023-08-23 22:14:09 -05:00
parent a22afe0c57
commit 57136d9652
15 changed files with 529 additions and 438 deletions

View file

@ -84,6 +84,7 @@ DMOJ_STATS_SUBMISSION_RESULT_COLORS = {
"CE": "#42586d", "CE": "#42586d",
"ERR": "#ffa71c", "ERR": "#ffa71c",
} }
DMOJ_PROFILE_IMAGE_ROOT = "profile_images"
MARKDOWN_STYLES = {} MARKDOWN_STYLES = {}
MARKDOWN_DEFAULT_STYLE = {} MARKDOWN_DEFAULT_STYLE = {}

View file

@ -50,6 +50,7 @@ from judge.widgets import (
HeavySelect2Widget, HeavySelect2Widget,
Select2MultipleWidget, Select2MultipleWidget,
DateTimePickerWidget, DateTimePickerWidget,
ImageWidget,
) )
from judge.tasks import rescore_contest from judge.tasks import rescore_contest
@ -78,12 +79,14 @@ class ProfileForm(ModelForm):
"language", "language",
"ace_theme", "ace_theme",
"user_script", "user_script",
"profile_image",
] ]
widgets = { widgets = {
"user_script": AceWidget(theme="github"), "user_script": AceWidget(theme="github"),
"timezone": Select2Widget(attrs={"style": "width:200px"}), "timezone": Select2Widget(attrs={"style": "width:200px"}),
"language": Select2Widget(attrs={"style": "width:200px"}), "language": Select2Widget(attrs={"style": "width:200px"}),
"ace_theme": Select2Widget(attrs={"style": "width:200px"}), "ace_theme": Select2Widget(attrs={"style": "width:200px"}),
"profile_image": ImageWidget,
} }
has_math_config = bool(settings.MATHOID_URL) has_math_config = bool(settings.MATHOID_URL)
@ -100,12 +103,22 @@ class ProfileForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop("user", None) user = kwargs.pop("user", None)
super(ProfileForm, self).__init__(*args, **kwargs) super(ProfileForm, self).__init__(*args, **kwargs)
self.fields["profile_image"].required = False
def clean_profile_image(self):
profile_image = self.cleaned_data.get("profile_image")
if profile_image:
if profile_image.size > 5 * 1024 * 1024:
raise ValidationError(
_("File size exceeds the maximum allowed limit of 5MB.")
)
return profile_image
def file_size_validator(file): def file_size_validator(file):
limit = 1 * 1024 * 1024 limit = 10 * 1024 * 1024
if file.size > limit: if file.size > limit:
raise ValidationError("File too large. Size should not exceed 1MB.") raise ValidationError("File too large. Size should not exceed 10MB.")
class ProblemSubmitForm(ModelForm): class ProblemSubmitForm(ModelForm):

View file

@ -9,14 +9,14 @@ from . import registry
@registry.function @registry.function
def gravatar(email, size=80, default=None): def gravatar(profile, size=80, default=None):
if isinstance(email, Profile): assert isinstance(profile, Profile), "profile should be Profile"
if default is None: profile_image = profile.profile_image
default = email.mute if profile_image:
email = email.user.email return profile_image.url
elif isinstance(email, AbstractUser): if default is None:
email = email.email default = profile.mute
email = profile.user.email
gravatar_url = ( gravatar_url = (
"//www.gravatar.com/avatar/" "//www.gravatar.com/avatar/"
+ hashlib.md5(utf8bytes(email.strip().lower())).hexdigest() + hashlib.md5(utf8bytes(email.strip().lower())).hexdigest()

View file

@ -0,0 +1,21 @@
# Generated by Django 3.2.18 on 2023-08-24 00:50
from django.db import migrations, models
import judge.models.profile
class Migration(migrations.Migration):
dependencies = [
("judge", "0161_auto_20230803_1536"),
]
operations = [
migrations.AddField(
model_name="profile",
name="profile_image",
field=models.ImageField(
null=True, upload_to=judge.models.profile.profile_image_path
),
),
]

View file

@ -1,4 +1,5 @@
from operator import mul from operator import mul
import os
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -27,6 +28,12 @@ class EncryptedNullCharField(EncryptedCharField):
return super(EncryptedNullCharField, self).get_prep_value(value) return super(EncryptedNullCharField, self).get_prep_value(value)
def profile_image_path(profile, filename):
tail = filename.split(".")[-1]
new_filename = f"user_{profile.id}.{tail}"
return os.path.join(settings.DMOJ_PROFILE_IMAGE_ROOT, new_filename)
class Organization(models.Model): class Organization(models.Model):
name = models.CharField(max_length=128, verbose_name=_("organization title")) name = models.CharField(max_length=128, verbose_name=_("organization title"))
slug = models.SlugField( slug = models.SlugField(
@ -229,6 +236,7 @@ class Profile(models.Model):
blank=True, blank=True,
help_text=_("Notes for administrators regarding this user."), help_text=_("Notes for administrators regarding this user."),
) )
profile_image = models.ImageField(upload_to=profile_image_path, null=True)
@cached_property @cached_property
def organization(self): def organization(self):

View file

@ -402,12 +402,12 @@ class UserPerformancePointsAjax(UserProblemsPage):
@login_required @login_required
def edit_profile(request): def edit_profile(request):
profile = Profile.objects.get(user=request.user) profile = request.profile
if profile.mute:
raise Http404()
if request.method == "POST": if request.method == "POST":
form_user = UserForm(request.POST, instance=request.user) form_user = UserForm(request.POST, instance=request.user)
form = ProfileForm(request.POST, instance=profile, user=request.user) form = ProfileForm(
request.POST, request.FILES, instance=profile, user=request.user
)
if form_user.is_valid() and form.is_valid(): if form_user.is_valid() and form.is_valid():
with transaction.atomic(), revisions.create_revision(): with transaction.atomic(), revisions.create_revision():
form_user.save() form_user.save()

View file

@ -3,3 +3,4 @@ from judge.widgets.mixins import CompressorWidgetMixin
from judge.widgets.pagedown import * from judge.widgets.pagedown import *
from judge.widgets.select2 import * from judge.widgets.select2 import *
from judge.widgets.datetime import * from judge.widgets.datetime import *
from judge.widgets.image import *

16
judge/widgets/image.py Normal file
View file

@ -0,0 +1,16 @@
from django import forms
class ImageWidget(forms.ClearableFileInput):
template_name = "widgets/image.html"
def __init__(self, attrs=None, width=80, height=80):
self.width = width
self.height = height
super().__init__(attrs)
def get_context(self, name, value, attrs=None):
context = super().get_context(name, value, attrs)
context["widget"]["height"] = self.height
context["widget"]["width"] = self.height
return context

File diff suppressed because it is too large Load diff

View file

@ -263,7 +263,7 @@
<span id="user-links"> <span id="user-links">
<ul><li><a href="javascript:void(0)"> <ul><li><a href="javascript:void(0)">
<span> <span>
<img src="{{ gravatar(request.user, 32) }}" height="24" width="24">{# -#} <img src="{{ gravatar(request.profile, 32) }}" height="24" width="24">{# -#}
<span> <span>
<b class="{{request.profile.css_class}}">{{ request.user.username }}</b> <b class="{{request.profile.css_class}}">{{ request.user.username }}</b>
</span> </span>

View file

@ -3,7 +3,7 @@
</h3> </h3>
<div class="sidebox-content"> <div class="sidebox-content">
<div class="user-gravatar"> <div class="user-gravatar">
<img src="{{ gravatar(request.user, 135) }}" <img src="{{ gravatar(request.profile, 135) }}"
alt="gravatar" width="135px" height="135px"> alt="gravatar" width="135px" height="135px">
</div> </div>
<div class="recently-attempted"> <div class="recently-attempted">

View file

@ -1,6 +1,6 @@
{% if other_user %} {% if other_user %}
<div class="status-container" style="height: 100%"> <div class="status-container" style="height: 100%">
<img src="{{ gravatar(other_user.user, 135) }}" class="info-pic"> <img src="{{ gravatar(other_user, 135) }}" class="info-pic">
<svg style="position:absolute; height:100%; width: 100%"> <svg style="position:absolute; height:100%; width: 100%">
<circle class="info-circle" <circle class="info-circle"
fill="{{'green' if other_online else 'red'}}"/> fill="{{'green' if other_online else 'red'}}"/>

View file

@ -162,7 +162,7 @@
<section class="message new-message"> <section class="message new-message">
<div class="info"> <div class="info">
<a href="{{ url('user_page', request.user.username) }}" class="user"> <a href="{{ url('user_page', request.user.username) }}" class="user">
<img src="{{ gravatar(request.user, 135) }}" class="gravatar"> <img src="{{ gravatar(request.profile, 135) }}" class="gravatar">
<div class="username {{ request.profile.css_class }}">{{ request.user.username }}</div> <div class="username {{ request.profile.css_class }}">{{ request.user.username }}</div>
</a> </a>
</div> </div>

View file

@ -79,11 +79,13 @@
{% block body %} {% block body %}
<div id="center-float"> <div id="center-float">
<form id="edit-form" action="" method="post" class="form-area"> <form id="edit-form" action="" method="post" class="form-area" enctype="multipart/form-data">
{% if form.errors %} {% if form.errors or form_user.errors %}
<div class="alert alert-danger alert-dismissable"> <div class="alert alert-danger alert-dismissable">
<a href="#" class="close">x</a> <a href="#" class="close">x</a>
{{ form.non_field_errors() }} {{ form.errors }}
<br>
{{ form_user.errors }}
</div> </div>
{% endif %} {% endif %}
@ -98,6 +100,10 @@
<td> {{ _('School') }}: </td> <td> {{ _('School') }}: </td>
<td> {{ form_user.last_name }} </td> <td> {{ form_user.last_name }} </td>
</tr> </tr>
<tr>
<td>{{ _('Avatar') }}: </td>
<td>{{ form.profile_image }}</td>
</tr>
</table> </table>
<hr> <hr>
@ -127,12 +133,6 @@
<td><span class="fullwidth">{{ form.math_engine }}</span></td> <td><span class="fullwidth">{{ form.math_engine }}</span></td>
</tr> </tr>
{% endif %} {% endif %}
<tr>
<td colspan="2">
<a href="http://www.gravatar.com/" title="{{ _('Change your avatar') }}"
target="_blank" class="inline-header">{{ _('Change your avatar') }}</a>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<a href="{{ url('password_change') }}" class="inline-header"> <a href="{{ url('password_change') }}" class="inline-header">

View file

@ -0,0 +1,13 @@
{% if widget.is_initial %}
<div>
<a href="{{widget.value.url}}" target=_blank>
<img src="{{widget.value.url}}" width="{{widget.width}}" height="{{widget.height}}" style="border-radius: 3px;">
</a>
<div>
{{ widget.input_text }}:
{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}">
{% if widget.is_initial %}
</div>
</div>
{% endif %}