From 10191eac20c15ff9e8d6b2878011e124818d50a6 Mon Sep 17 00:00:00 2001 From: Toksi86 Date: Fri, 11 Jul 2025 17:31:19 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=D1=82=D0=B0=D1=80=D0=BA=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0,=20=D0=B2=20=D1=81=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B0=D0=B5=20=D0=B5=D1=81=D0=BB=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=20=D0=B1=D0=B5=D0=B7=20=D0=B0=D0=B2=D1=82=D0=B0=D1=80=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B1=D1=83=D0=B4=D0=B5=D1=82=20=D0=B2=D1=8B=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B0=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=B8=D0=B7=20=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=B4=D0=B0=D1=80=D1=82=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/admin.py | 6 ++ ...tprojectcover_datetime_created_and_more.py | 64 +++++++++++++++++++ projects/models.py | 58 ++++++++--------- projects/validators.py | 3 +- 4 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 projects/migrations/0027_alter_defaultprojectcover_datetime_created_and_more.py diff --git a/projects/admin.py b/projects/admin.py index aab45816..d7cbce7e 100644 --- a/projects/admin.py +++ b/projects/admin.py @@ -3,6 +3,7 @@ from projects.models import ( Achievement, Collaborator, + DefaultProjectAvatar, DefaultProjectCover, Project, ProjectLink, @@ -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") diff --git a/projects/migrations/0027_alter_defaultprojectcover_datetime_created_and_more.py b/projects/migrations/0027_alter_defaultprojectcover_datetime_created_and_more.py new file mode 100644 index 00000000..0f755693 --- /dev/null +++ b/projects/migrations/0027_alter_defaultprojectcover_datetime_created_and_more.py @@ -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": "Аватарки проектов", + }, + ), + ] diff --git a/projects/models.py b/projects/models.py index 81d68917..117b65c7 100644 --- a/projects/models.py +++ b/projects/models.py @@ -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 @@ -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: diff --git a/projects/validators.py b/projects/validators.py index 8c20ca0e..f14e813c 100644 --- a/projects/validators.py +++ b/projects/validators.py @@ -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)