Initial commit: Django gallery project

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 16:47:17 +08:00
commit 8b57a7b66a
53 changed files with 3633 additions and 0 deletions

279
gallery/models.py Normal file
View File

@@ -0,0 +1,279 @@
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} 的评论"