diff --git a/Pipfile b/Pipfile index e77914a..e5fe1ab 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ name = "pypi" [packages] django = "*" +pillow = "*" [dev-packages] mypy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index ee89292..3b98275 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5b45ddb3eaf4d3c3557de0cab9ab514fcebdfe6e6c80120a11f223011f9beec6" + "sha256": "720e8538e0b5a6418b2f8e2973ef59da19390569ba586790a194f490bdabd90b" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "asgiref": { "hashes": [ - "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", - "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" + "sha256:45a429524fba18aba9d512498b19d220c4d628e75b40cf5c627524dbaebc5cc1", + "sha256:fddeea3c53fa99d0cdb613c3941cc6e52d822491fc2753fba25768fb5bf4e865" ], "markers": "python_version >= '3.7'", - "version": "==3.5.0" + "version": "==3.5.1" }, "django": { "hashes": [ @@ -32,41 +32,49 @@ "index": "pypi", "version": "==4.0.4" }, - "mypy": { + "pillow": { "hashes": [ - "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d", - "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8", - "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de", - "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038", - "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed", - "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334", - "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff", - "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2", - "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22", - "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2", - "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2", - "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605", - "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb", - "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519", - "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0", - "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc", - "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b", - "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f", - "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075", - "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef", - "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb", - "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a", - "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b" + "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e", + "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595", + "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512", + "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c", + "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477", + "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a", + "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4", + "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e", + "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5", + "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378", + "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a", + "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652", + "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7", + "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a", + "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a", + "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6", + "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165", + "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160", + "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331", + "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b", + "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458", + "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033", + "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8", + "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481", + "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58", + "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7", + "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3", + "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea", + "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34", + "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3", + "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8", + "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581", + "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244", + "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef", + "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0", + "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2", + "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97", + "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717" ], "index": "pypi", - "version": "==0.950" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "version": "==0.4.3" + "version": "==9.1.0" }, "sqlparse": { "hashes": [ @@ -75,22 +83,6 @@ ], "markers": "python_version >= '3.5'", "version": "==0.4.2" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" - }, - "typing-extensions": { - "hashes": [ - "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", - "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" - ], - "markers": "python_version >= '3.7'", - "version": "==4.2.0" } }, "develop": { diff --git a/board/models.py b/board/models.py index d76ebb3..8c18d93 100644 --- a/board/models.py +++ b/board/models.py @@ -1,8 +1,42 @@ +import os +from pathlib import Path from django.db import models -from django.db.models.signals import post_save +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): @@ -39,8 +73,52 @@ class Post(models.Model): 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) - # TODO : images + 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: @@ -57,8 +135,16 @@ class Post(models.Model): ) -@receiver(post_save, sender=Post) +@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) diff --git a/board/static/board/style.css b/board/static/board/style.css index cdea25f..5e30e70 100644 --- a/board/static/board/style.css +++ b/board/static/board/style.css @@ -16,6 +16,24 @@ hr { /* Posts */ /*.post_body { }*/ +.post_image_info { + font-size: small; + padding-bottom: 5px; +} + +.post_image_thumbnail { + float: left; + padding-right: 5px; +} + +/* +Not sure if I like the in-line style or each post gets its own row style +.post_content:after { + content: ""; + display: table; + clear: both; +} +*/ .post_id { cursor: pointer; diff --git a/board/templates/board/board_detail.html b/board/templates/board/board_detail.html index 99b4082..b2be430 100644 --- a/board/templates/board/board_detail.html +++ b/board/templates/board/board_detail.html @@ -24,7 +24,7 @@