Files
interchan/board/models.py
Alek Ratzloff e6c60ff93c Add image upload support
Images can be uploaded, thumbnails are created, they're displayed within
the threads themselves. Just like four chans!

There is not an upload size limit set yet. Gotta get on that next.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2022-05-04 19:45:36 -07:00

151 lines
4.8 KiB
Python

import os
from pathlib import Path
from django.db import models
from django.db.models import signals
from django.conf import settings
from django.core.files.base import ContentFile
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
from PIL import Image
from io import BytesIO
def image_upload(instance, filename):
op_id = instance.op.id if instance.op else instance.id
now = timezone.now()
now_sec = now.strftime("%s.%f")
ext = Path(filename).suffix.lower()
if ext in (".jpeg", ".jpg"):
ext = ".jpg"
if ext not in (".jpg", ".png", ".gif"):
raise Exception("File type invalid")
if instance.op:
return f"{instance.board.url}/{instance.op.id}/{now_sec}{ext}"
else:
return f"{instance.board.url}/{now_sec}{ext}"
def thumbs_upload(instance, filename):
op_id = instance.op.id if instance.op else instance.id
now = timezone.now()
now_sec = now.strftime("%s.%f")
ext = Path(filename).suffix.lower()
if instance.op:
return f"{instance.board.url}/{instance.op.id}/{now_sec}{ext}"
else:
return f"{instance.board.url}/{now_sec}t{ext}"
class Board(models.Model):
# The short URL name for the board
url = models.CharField(max_length=255, null=False, blank=False, unique=True)
# Human-readable name for the board
name = models.CharField(max_length=255, null=False, blank=False)
@property
def threads(self):
return Post.objects.filter(board=self, op=None)
class Post(models.Model):
# Board that this post was made on
board = models.ForeignKey("Board", on_delete=models.CASCADE)
# Thread that this is a part of
op = models.ForeignKey(
"self", null=True, on_delete=models.CASCADE, related_name="all_replies"
)
# Post that this is replying to
reply = models.ForeignKey(
"self", null=True, on_delete=models.CASCADE, related_name="replies"
)
# User's supplied name for this post
name = models.CharField(max_length=255, null=True, blank=True)
# User's supplied subject for this post
subject = models.CharField(max_length=255, null=True, blank=True)
# Text of this post
text = models.TextField(max_length=10000, null=False, blank=True)
# The IP address of the user that made this post
ip = models.GenericIPAddressField()
# Creation time
created = models.DateTimeField(auto_now_add=True)
# Last bump time
last_bump = models.DateTimeField(auto_now_add=True)
# Image
image = models.ImageField(
upload_to=image_upload,
null=True,
blank=True,
width_field="image_width",
height_field="image_height",
)
# Thumbnail
thumbnail = models.ImageField(upload_to=thumbs_upload, editable=False, null=True)
# Original image name
original_image_name = models.CharField(max_length=255, null=True)
# Image width and height
image_width = models.IntegerField(null=True)
image_height = models.IntegerField(null=True)
def save(self, *args, **kwargs):
if self.image:
self.original_image_name = self.image.name
self.__make_thumbnail()
super(Post, self).save(*args, **kwargs)
def __make_thumbnail(self):
image = Image.open(self.image)
image.thumbnail(settings.THUMB_SIZE, Image.ANTIALIAS)
image_path = Path(self.image.name)
thumb_path = Path(image_path.stem + "t" + image_path.suffix)
ext = thumb_path.suffix.lower()
if ext in (".jpg", ".jpeg"):
FTYPE = "JPEG"
elif ext == ".gif":
FTYPE = "GIF"
elif ext == ".png":
FTYPE = "PNG"
else:
raise Exception("File type invalid")
thumb_temp = BytesIO()
image.save(thumb_temp, FTYPE)
thumb_temp.seek(0)
# save=False stops recursion
self.thumbnail.save(thumb_path, ContentFile(thumb_temp.read()), save=False)
thumb_temp.close()
def get_absolute_url(self):
if self.op is None:
return reverse(
"board:post_detail", kwargs={"url": self.board.url, "id": self.id}
)
else:
return (
reverse(
"board:post_detail",
kwargs={"url": self.board.url, "id": self.op.id},
)
+ f"#p{self.id}"
)
@receiver(signals.post_save, sender=Post)
def post_created(sender, instance, created, **kwargs):
if created and instance.op:
instance.op.last_bump = timezone.now()
instance.op.save()
@receiver(signals.post_delete, sender=Post)
def post_deleted(sender, instance, **kwargs):
if instance.image and Path(instance.image.path).is_file():
os.remove(instance.image.path)
if instance.thumbnail and Path(instance.thumbnail.path).is_file():
os.remove(instance.thumbnail.path)