Skip to content
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
6 changes: 6 additions & 0 deletions projects/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from projects.models import (
Achievement,
Collaborator,
DefaultProjectAvatar,
DefaultProjectCover,
Project,
ProjectLink,
Expand Down Expand Up @@ -141,3 +142,8 @@ class DefaultProjectCoverAdmin(admin.ModelAdmin):
"datetime_created",
"datetime_updated",
)


@admin.register(DefaultProjectAvatar)
class DefaultProjectAvatarAdmin(admin.ModelAdmin):
list_display = ("id", "image", "datetime_created")
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Generated by Django 4.2.11 on 2025-07-11 11:40

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("files", "0007_auto_20230929_1727"),
("projects", "0026_collaborator_specialization"),
]

operations = [
migrations.AlterField(
model_name="defaultprojectcover",
name="datetime_created",
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name="defaultprojectcover",
name="datetime_updated",
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name="defaultprojectcover",
name="image",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="files.userfile",
),
),
migrations.CreateModel(
name="DefaultProjectAvatar",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("datetime_created", models.DateTimeField(auto_now_add=True)),
("datetime_updated", models.DateTimeField(auto_now=True)),
(
"image",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="files.userfile",
),
),
],
options={
"verbose_name": "Аватарка проекта",
"verbose_name_plural": "Аватарки проектов",
},
),
]
58 changes: 28 additions & 30 deletions projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,48 @@
User = get_user_model()


class DefaultProjectCover(models.Model):
class AbstractDefaultProjectImage(models.Model):
"""
Default cover model for projects, is chosen randomly at project creation

Attributes:
image: A ForeignKey referencing the image of the cover.
datetime_created: A DateTimeField indicating date of creation.
datetime_updated: A DateTimeField indicating date of update.
Абстрактная модель для хранения изображений проекта по умолчанию.
"""

image = models.ForeignKey(
UserFile,
on_delete=models.CASCADE,
related_name="default_covers",
null=True,
blank=True,
)
datetime_created = models.DateTimeField(auto_now_add=True)
datetime_updated = models.DateTimeField(auto_now=True)

datetime_created = models.DateTimeField(
verbose_name="Дата создания",
null=False,
auto_now_add=True,
)
datetime_updated = models.DateTimeField(
verbose_name="Дата изменения",
null=False,
auto_now=True,
)
class Meta:
abstract = True

@classmethod
def get_random_file(cls):
# FIXME: this is not efficient, but for ~10 default covers it should be ok
return cls.objects.order_by("?").first().image
def get_random_file(cls) -> Optional[UserFile]:
if not cls.objects.exists():
return None
obj = cls.objects.order_by("?").first()
return obj.image if obj and obj.image else None

@classmethod
def get_random_file_link(cls):
# FIXME: this is not efficient, but for ~10 default covers it should be ok
return (
cls.objects.order_by("?").first().image.link
if cls.objects.order_by("?").first().image
else None
)
def get_random_file_link(cls) -> Optional[str]:
file = cls.get_random_file()
return file.link if file else None


class DefaultProjectCover(AbstractDefaultProjectImage):
class Meta:
verbose_name = "Обложка проекта"
verbose_name_plural = "Обложки проектов"


class DefaultProjectAvatar(AbstractDefaultProjectImage):
class Meta:
verbose_name = "Аватарка проекта"
verbose_name_plural = "Аватарки проектов"


class Project(models.Model):
"""
Project model
Expand Down Expand Up @@ -193,9 +187,13 @@ def __str__(self):
return f"Project<{self.id}> - {self.name}"

def save(self, *args, **kwargs):
"""Set random cover image if `cover_image_address` blank."""
if self.cover_image_address is None:
"""Set random cover and avatar images if not provided."""
if not self.cover_image_address:
self.cover_image_address = DefaultProjectCover.get_random_file_link()

if not self.image_address:
self.image_address = DefaultProjectAvatar.get_random_file_link()

super().save(*args, **kwargs)

class Meta:
Expand Down
3 changes: 2 additions & 1 deletion projects/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
def validate_project(data):
if not data.get("draft"):
error = {}
allowed_blank = {"image_address"}
for key, value in data.items():
if value == "" or value is None:
if (value == "" or value is None) and key not in allowed_blank:
error[key] = "This field is required"
if error:
raise ValidationError(error)
Expand Down