Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image uploading feature for organization #122

Merged
merged 1 commit into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dmoj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"ERR": "#ffa71c",
}
DMOJ_PROFILE_IMAGE_ROOT = "profile_images"
DMOJ_ORGANIZATION_IMAGE_ROOT = "organization_images"
DMOJ_TEST_FORMATTER_ROOT = "test_formatter"

MARKDOWN_STYLES = {}
Expand Down
1 change: 1 addition & 0 deletions judge/admin/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class OrganizationAdmin(VersionAdmin):
"short_name",
"is_open",
"about",
"organization_image",
"logo_override_image",
"slots",
"registrant",
Expand Down
23 changes: 20 additions & 3 deletions judge/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,32 @@ class Meta:
"slug",
"short_name",
"about",
"logo_override_image",
"organization_image",
"admins",
"is_open",
]
widgets = {"admins": Select2MultipleWidget()}
widgets = {
"admins": Select2MultipleWidget(),
"organization_image": ImageWidget,
}
if HeavyPreviewPageDownWidget is not None:
widgets["about"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy("organization_preview")
)

def __init__(self, *args, **kwargs):
super(EditOrganizationForm, self).__init__(*args, **kwargs)
self.fields["organization_image"].required = False

def clean_organization_image(self):
organization_image = self.cleaned_data.get("organization_image")
if organization_image:
if organization_image.size > 5 * 1024 * 1024:
raise ValidationError(
_("File size exceeds the maximum allowed limit of 5MB.")
)
return organization_image


class AddOrganizationForm(ModelForm):
class Meta:
Expand All @@ -214,7 +230,7 @@ class Meta:
"slug",
"short_name",
"about",
"logo_override_image",
"organization_image",
"is_open",
]
widgets = {}
Expand All @@ -226,6 +242,7 @@ class Meta:
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(AddOrganizationForm, self).__init__(*args, **kwargs)
self.fields["organization_image"].required = False

def save(self, commit=True):
res = super(AddOrganizationForm, self).save(commit=False)
Expand Down
21 changes: 21 additions & 0 deletions judge/migrations/0189_organization_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2.21 on 2024-07-08 00:05

from django.db import migrations, models
import judge.models.profile


class Migration(migrations.Migration):

dependencies = [
("judge", "0188_official_contest"),
]

operations = [
migrations.AddField(
model_name="organization",
name="organization_image",
field=models.ImageField(
null=True, upload_to=judge.models.profile.organization_image_path
),
),
]
7 changes: 7 additions & 0 deletions judge/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ def profile_image_path(profile, filename):
return os.path.join(settings.DMOJ_PROFILE_IMAGE_ROOT, new_filename)


def organization_image_path(organization, filename):
tail = filename.split(".")[-1]
new_filename = f"organization_{organization.id}.{tail}"
return os.path.join(settings.DMOJ_ORGANIZATION_IMAGE_ROOT, new_filename)


class Organization(models.Model):
name = models.CharField(max_length=128, verbose_name=_("organization title"))
slug = models.SlugField(
Expand Down Expand Up @@ -104,6 +110,7 @@ class Organization(models.Model):
null=True,
blank=True,
)
organization_image = models.ImageField(upload_to=organization_image_path, null=True)
logo_override_image = models.CharField(
verbose_name=_("Logo override image"),
default="",
Expand Down
64 changes: 64 additions & 0 deletions judge/scripts/migrate_organization_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Download organization images from "logo_override_image" and upload to organization_images folder to use "organization_image"
# In folder online_judge, run python3 manage.py shell < judge/scripts/migrate_organization_image.py

import os
import requests
from urllib.parse import urlparse
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.conf import settings
from django.db import transaction
from judge.models import Organization


def is_valid_image_url(url):
try:
parsed_url = urlparse(url)
_, ext = os.path.splitext(parsed_url.path)
return ext.lower() in [".jpg", ".jpeg", ".png", ".gif", ".svg"]
except Exception as e:
return False


def download_image(url):
response = requests.get(url)
response.raise_for_status()
return ContentFile(response.content)


def organization_image_path(organization, filename):
tail = filename.split(".")[-1]
new_filename = f"organization_{organization.id}.{tail}"
return os.path.join(settings.DMOJ_ORGANIZATION_IMAGE_ROOT, new_filename)


@transaction.atomic
def migrate_images():
print("Start")
organizations = Organization.objects.all()
for org in organizations:
if org.logo_override_image:
if is_valid_image_url(org.logo_override_image):
try:
# Download the image
image_content = download_image(org.logo_override_image)
# Determine the file extension
file_ext = org.logo_override_image.split(".")[-1]
filename = f"organization_{org.id}.{file_ext}"
# Save the image to the new location
new_path = organization_image_path(org, filename)
saved_path = default_storage.save(new_path, image_content)
# Update the organization_image field
org.organization_image = saved_path
org.save()
print(f"Image for organization {org.id} migrated successfully.")
except Exception as e:
print(f"Failed to migrate image for organization {org.id}: {e}")
else:
print(
f"Invalid image URL for organization {org.id}: {org.logo_override_image}"
)
print("Finish")


migrate_images()
2 changes: 1 addition & 1 deletion judge/views/contests.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ def get_context_data(self, **kwargs):
):
context[
"logo_override_image"
] = self.object.organizations.first().logo_override_image
] = self.object.organizations.first().organization_image.url

return context

Expand Down
2 changes: 1 addition & 1 deletion judge/views/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def get_context_data(self, **kwargs):
context["is_admin"] = self.is_admin(self.organization)
context["can_edit"] = self.can_edit_organization(self.organization)
context["organization"] = self.organization
context["logo_override_image"] = self.organization.logo_override_image
context["organization_image"] = self.organization.organization_image
context["organization_subdomain"] = (
("http" if settings.DMOJ_SSL == 0 else "https")
+ "://"
Expand Down
2 changes: 1 addition & 1 deletion templates/organization/form.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<form action="" method="post">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-dismissable">
Expand Down
5 changes: 4 additions & 1 deletion templates/organization/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
{% block middle_title %}
<div class="page-title">
<div class="tabs" style="border: none;">
<h2><img src="{{logo_override_image}}" style="height: 3rem; vertical-align: middle; border-radius: 5px;">
<h2>
{% if organization_image %}
<img src="{{organization_image.url}}" style="height: 3rem; vertical-align: middle; border-radius: 5px;">
{% endif %}
{{title}}
</h2>
<span class="spacer"></span>
Expand Down
4 changes: 2 additions & 2 deletions templates/organization/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
<div class="organization-container">
{% for org in queryset %}
<div class="organization-card" style="cursor: pointer;" onclick="location.href='{{ org.get_absolute_url() }}';">
{% if org.logo_override_image %}
<img class="org-logo" loading="lazy" src="{{ org.logo_override_image }}">
{% if org.organization_image %}
<img class="org-logo" loading="lazy" src="{{ org.organization_image.url }}">
{% else %}
<img class="org-logo" loading="lazy" src="{{ static('icons/icon.svg') }}" onerror="this.onerror=null;this.src='{{ static('icons/logo.svg') }}';">
{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions templates/organization/tag.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<a href="{{ org.get_absolute_url() }}">
<span class="organization-tag" style="gap: 0.2em;">
{% if org.logo_override_image %}
<img class="user-img" style="height: 1.5em; width: 1.5em;" loading="lazy" src="{{ org.logo_override_image }}">
{% if org.organization_image %}
<img class="user-img" style="height: 1.5em; width: 1.5em;" loading="lazy" src="{{ org.organization_image.url }}">
{% endif %}
{{ org.name }}
</span>
Expand Down
4 changes: 2 additions & 2 deletions templates/recent-organization.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ <h3 class="bold-text colored-text"><i class="fa fa-building-columns"></i>{{ _('R
<div class="toggled sidebox-content">
{% for organization in recent_organizations %}
<a href="{{ url('organization_home', organization.pk, organization.slug) }}" class="organization-row">
{% if organization.logo_override_image %}
<img class="org-logo user-img" loading="lazy" src="{{ organization.logo_override_image }}">
{% if organization.organization_image %}
<img class="org-logo user-img" loading="lazy" src="{{ organization.organization_image.url }}">
{% else %}
<img class="org-logo" loading="lazy" src="{{ static('icons/icon.svg') }}" onerror="{{static('icons/logo.svg')}}">
{% endif %}
Expand Down
4 changes: 3 additions & 1 deletion templates/site-logo-fragment.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{% if request.in_contest_mode and request.participation.contest.logo_override_image %}
<img src="{{ request.participation.contest.logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
{% elif request.organization %}
<img src="{{ request.organization.logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
<img src="{{ request.organization.organization_image.url|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
{% elif organization_image is defined and organization_image %}
<img src="{{ organization_image.url|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
{% elif logo_override_image is defined and logo_override_image %}
<img src="{{ logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
{% else %}
Expand Down
Loading