Add profile image
This commit is contained in:
parent
a22afe0c57
commit
57136d9652
15 changed files with 529 additions and 438 deletions
|
@ -84,6 +84,7 @@ DMOJ_STATS_SUBMISSION_RESULT_COLORS = {
|
|||
"CE": "#42586d",
|
||||
"ERR": "#ffa71c",
|
||||
}
|
||||
DMOJ_PROFILE_IMAGE_ROOT = "profile_images"
|
||||
|
||||
MARKDOWN_STYLES = {}
|
||||
MARKDOWN_DEFAULT_STYLE = {}
|
||||
|
|
|
@ -50,6 +50,7 @@ from judge.widgets import (
|
|||
HeavySelect2Widget,
|
||||
Select2MultipleWidget,
|
||||
DateTimePickerWidget,
|
||||
ImageWidget,
|
||||
)
|
||||
from judge.tasks import rescore_contest
|
||||
|
||||
|
@ -78,12 +79,14 @@ class ProfileForm(ModelForm):
|
|||
"language",
|
||||
"ace_theme",
|
||||
"user_script",
|
||||
"profile_image",
|
||||
]
|
||||
widgets = {
|
||||
"user_script": AceWidget(theme="github"),
|
||||
"timezone": Select2Widget(attrs={"style": "width:200px"}),
|
||||
"language": Select2Widget(attrs={"style": "width:200px"}),
|
||||
"ace_theme": Select2Widget(attrs={"style": "width:200px"}),
|
||||
"profile_image": ImageWidget,
|
||||
}
|
||||
|
||||
has_math_config = bool(settings.MATHOID_URL)
|
||||
|
@ -100,12 +103,22 @@ class ProfileForm(ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop("user", None)
|
||||
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):
|
||||
limit = 1 * 1024 * 1024
|
||||
limit = 10 * 1024 * 1024
|
||||
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):
|
||||
|
|
|
@ -9,14 +9,14 @@ from . import registry
|
|||
|
||||
|
||||
@registry.function
|
||||
def gravatar(email, size=80, default=None):
|
||||
if isinstance(email, Profile):
|
||||
def gravatar(profile, size=80, default=None):
|
||||
assert isinstance(profile, Profile), "profile should be Profile"
|
||||
profile_image = profile.profile_image
|
||||
if profile_image:
|
||||
return profile_image.url
|
||||
if default is None:
|
||||
default = email.mute
|
||||
email = email.user.email
|
||||
elif isinstance(email, AbstractUser):
|
||||
email = email.email
|
||||
|
||||
default = profile.mute
|
||||
email = profile.user.email
|
||||
gravatar_url = (
|
||||
"//www.gravatar.com/avatar/"
|
||||
+ hashlib.md5(utf8bytes(email.strip().lower())).hexdigest()
|
||||
|
|
21
judge/migrations/0162_profile_image.py
Normal file
21
judge/migrations/0162_profile_image.py
Normal 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
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,5 @@
|
|||
from operator import mul
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -27,6 +28,12 @@ class EncryptedNullCharField(EncryptedCharField):
|
|||
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):
|
||||
name = models.CharField(max_length=128, verbose_name=_("organization title"))
|
||||
slug = models.SlugField(
|
||||
|
@ -229,6 +236,7 @@ class Profile(models.Model):
|
|||
blank=True,
|
||||
help_text=_("Notes for administrators regarding this user."),
|
||||
)
|
||||
profile_image = models.ImageField(upload_to=profile_image_path, null=True)
|
||||
|
||||
@cached_property
|
||||
def organization(self):
|
||||
|
|
|
@ -402,12 +402,12 @@ class UserPerformancePointsAjax(UserProblemsPage):
|
|||
|
||||
@login_required
|
||||
def edit_profile(request):
|
||||
profile = Profile.objects.get(user=request.user)
|
||||
if profile.mute:
|
||||
raise Http404()
|
||||
profile = request.profile
|
||||
if request.method == "POST":
|
||||
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():
|
||||
with transaction.atomic(), revisions.create_revision():
|
||||
form_user.save()
|
||||
|
|
|
@ -3,3 +3,4 @@ from judge.widgets.mixins import CompressorWidgetMixin
|
|||
from judge.widgets.pagedown import *
|
||||
from judge.widgets.select2 import *
|
||||
from judge.widgets.datetime import *
|
||||
from judge.widgets.image import *
|
||||
|
|
16
judge/widgets/image.py
Normal file
16
judge/widgets/image.py
Normal 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
|
@ -263,7 +263,7 @@
|
|||
<span id="user-links">
|
||||
<ul><li><a href="javascript:void(0)">
|
||||
<span>
|
||||
<img src="{{ gravatar(request.user, 32) }}" height="24" width="24">{# -#}
|
||||
<img src="{{ gravatar(request.profile, 32) }}" height="24" width="24">{# -#}
|
||||
<span>
|
||||
<b class="{{request.profile.css_class}}">{{ request.user.username }}</b>
|
||||
</span>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
</h3>
|
||||
<div class="sidebox-content">
|
||||
<div class="user-gravatar">
|
||||
<img src="{{ gravatar(request.user, 135) }}"
|
||||
<img src="{{ gravatar(request.profile, 135) }}"
|
||||
alt="gravatar" width="135px" height="135px">
|
||||
</div>
|
||||
<div class="recently-attempted">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% if other_user %}
|
||||
<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%">
|
||||
<circle class="info-circle"
|
||||
fill="{{'green' if other_online else 'red'}}"/>
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
<section class="message new-message">
|
||||
<div class="info">
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -79,11 +79,13 @@
|
|||
|
||||
{% block body %}
|
||||
<div id="center-float">
|
||||
<form id="edit-form" action="" method="post" class="form-area">
|
||||
{% if form.errors %}
|
||||
<form id="edit-form" action="" method="post" class="form-area" enctype="multipart/form-data">
|
||||
{% if form.errors or form_user.errors %}
|
||||
<div class="alert alert-danger alert-dismissable">
|
||||
<a href="#" class="close">x</a>
|
||||
{{ form.non_field_errors() }}
|
||||
{{ form.errors }}
|
||||
<br>
|
||||
{{ form_user.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -98,6 +100,10 @@
|
|||
<td> {{ _('School') }}: </td>
|
||||
<td> {{ form_user.last_name }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Avatar') }}: </td>
|
||||
<td>{{ form.profile_image }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
|
@ -127,12 +133,6 @@
|
|||
<td><span class="fullwidth">{{ form.math_engine }}</span></td>
|
||||
</tr>
|
||||
{% 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>
|
||||
<td colspan="2">
|
||||
<a href="{{ url('password_change') }}" class="inline-header">
|
||||
|
|
13
templates/widgets/image.html
Normal file
13
templates/widgets/image.html
Normal 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 %}
|
Loading…
Reference in a new issue