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>
151 lines
4.8 KiB
Python
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)
|