Files
yitao-ren-gallery/gallery/models.py
2026-02-25 16:47:17 +08:00

279 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from django.utils.text import slugify
from django.utils import timezone
from django.core.validators import MinValueValidator, MaxValueValidator
from django.conf import settings
import os
class Category(models.Model):
"""作品分类模型"""
name = models.CharField('分类名称', max_length=100)
slug = models.SlugField('URL标识', max_length=100, unique=True, blank=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '作品分类'
verbose_name_plural = '作品分类'
ordering = ['name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
# 生成唯一的 slug
if not self.slug:
base_slug = slugify(self.name)
if not base_slug: # 如果 slugify 返回空字符串
base_slug = f'category-{self.id}' if self.id else 'category'
# 确保 slug 唯一
slug = base_slug
counter = 1
while Category.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f'{base_slug}-{counter}'
counter += 1
self.slug = slug
super().save(*args, **kwargs)
class Artwork(models.Model):
"""摄影作品模型"""
title = models.CharField('作品标题', max_length=200)
description = models.TextField('作品描述', blank=True)
slug = models.SlugField('URL标识', max_length=200, unique=True, blank=True)
# 图片字段
image = models.ImageField('作品图片', upload_to='artworks/%Y/%m/%d/')
thumbnail = models.ImageField('缩略图', upload_to='thumbnails/%Y/%m/%d/', blank=True, null=True)
# 关联字段
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
verbose_name='作品分类',
null=True,
blank=True
)
# 元数据字段
order = models.IntegerField('排序序号', default=0, help_text='数字越小越靠前')
# 时间字段
created_at = models.DateTimeField('创建时间', default=timezone.now)
updated_at = models.DateTimeField('更新时间', auto_now=True)
# 统计字段
view_count = models.PositiveIntegerField('浏览次数', default=0)
class Meta:
verbose_name = '摄影作品'
verbose_name_plural = '摄影作品'
ordering = ['order', '-created_at']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# 生成唯一的 slug
if not self.slug:
base_slug = slugify(self.title)
if not base_slug: # 如果 slugify 返回空字符串
base_slug = f'artwork-{self.id}' if self.id else 'artwork'
# 确保 slug 唯一
slug = base_slug
counter = 1
while Artwork.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f'{base_slug}-{counter}'
counter += 1
self.slug = slug
# 如果是新对象或图片被更新,生成缩略图
if self.pk is None or 'image' in kwargs.get('update_fields', []):
super().save(*args, **kwargs)
self.generate_thumbnail()
else:
super().save(*args, **kwargs)
def generate_thumbnail(self):
"""生成缩略图"""
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
import os
if not self.image:
return
# 打开原图
img = Image.open(self.image)
# 计算目标像素总量约90万像素如1280x720
target_pixels = 900000
# 获取原图尺寸
width, height = img.size
current_pixels = width * height
# 计算缩放比例使像素总量接近90万但限制最大边不超过1600px
max_dimension = 1600
if current_pixels <= 0:
# 如果图片尺寸无效,使用原尺寸但限制最大边
if width > max_dimension or height > max_dimension:
scale = max_dimension / max(width, height)
new_width = int(width * scale)
new_height = int(height * scale)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
else:
# 首先计算缩放至目标像素的比例
scale_to_target = (target_pixels / current_pixels) ** 0.5
# 然后检查缩放后是否超过最大边限制
new_width_temp = int(width * scale_to_target)
new_height_temp = int(height * scale_to_target)
# 如果缩放后的尺寸超过最大边限制,使用最大边限制的比例
if new_width_temp > max_dimension or new_height_temp > max_dimension:
scale_to_max = max_dimension / max(new_width_temp, new_height_temp)
scale = scale_to_target * scale_to_max
else:
scale = scale_to_target
new_width = int(width * scale)
new_height = int(height * scale)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# 保存到内存
thumb_io = BytesIO()
# 保持原格式如果是JPEG则优化
# 如果img.format为空根据文件扩展名判断格式
format_to_use = img.format
if not format_to_use:
# 根据文件扩展名判断格式
filename = os.path.basename(self.image.name)
name, ext = os.path.splitext(filename)
ext = ext.lower()
if ext in ['.jpg', '.jpeg', '.jpe', '.jfif']:
format_to_use = 'JPEG'
elif ext in ['.png']:
format_to_use = 'PNG'
elif ext in ['.gif']:
format_to_use = 'GIF'
elif ext in ['.webp']:
format_to_use = 'WEBP'
else:
# 默认使用JPEG格式
format_to_use = 'JPEG'
if format_to_use == 'JPEG':
img.save(thumb_io, format='JPEG', quality=85, optimize=True)
else:
img.save(thumb_io, format=format_to_use)
# 生成缩略图文件名
filename = os.path.basename(self.image.name)
name, ext = os.path.splitext(filename)
thumb_filename = f'{name}_thumb{ext}'
# 保存缩略图
self.thumbnail.save(
thumb_filename,
ContentFile(thumb_io.getvalue()),
save=False
)
super().save(update_fields=['thumbnail'])
def increment_view_count(self):
"""增加浏览次数"""
self.view_count += 1
self.save(update_fields=['view_count'])
def get_dynamic_grid_class(self):
"""根据图片宽高比动态返回网格CSS类"""
try:
if not self.thumbnail:
return 'col-span-1 row-span-1'
# 尝试从缩略图获取尺寸
from PIL import Image
import os
thumb_path = self.thumbnail.path
if not os.path.exists(thumb_path):
return 'col-span-1 row-span-1'
with Image.open(thumb_path) as img:
width, height = img.size
aspect_ratio = width / height
# 根据宽高比返回不同的网格类
if aspect_ratio > 1.5: # 很宽的图片
return 'col-span-2 row-span-1' # 占2列宽
elif aspect_ratio < 0.67: # 很高的图片
return 'col-span-1 row-span-2' # 占2行高
elif aspect_ratio > 1.2: # 中等宽度
return 'col-span-2 row-span-1' # 占2列宽
elif aspect_ratio < 0.83: # 中等高度
return 'col-span-1 row-span-2' # 占2行高
else: # 接近正方形
return 'col-span-1 row-span-1'
except Exception as e:
# 发生错误时返回默认
print(f"Error getting grid class for artwork {self.id}: {e}")
return 'col-span-1 row-span-1'
class About(models.Model):
"""关于页面模型"""
title = models.CharField('标题', max_length=200)
content = models.TextField('内容')
image = models.ImageField('图片', upload_to='about/', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '关于页面'
verbose_name_plural = '关于页面'
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# 确保只有一个关于页面
if not self.pk and About.objects.exists():
# 如果已经存在关于页面,更新它而不是创建新的
existing = About.objects.first()
existing.title = self.title
existing.content = self.content
if self.image:
existing.image = self.image
existing.save()
return existing
super().save(*args, **kwargs)
class Comment(models.Model):
"""评论模型"""
artwork = models.ForeignKey(Artwork, on_delete=models.CASCADE, verbose_name='作品')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='用户')
text = models.TextField('评论内容', blank=True)
image = models.ImageField('评论图片', upload_to='comments/%Y/%m/%d/', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
is_active = models.BooleanField('是否有效', default=True)
class Meta:
verbose_name = '评论'
verbose_name_plural = '评论'
ordering = ['-created_at']
def __str__(self):
return f"{self.user.username}{self.artwork.title} 的评论"