Add board locking, update tests, and update clean() methods

* Boards can be locked from allowing posts - this is can be useful for
  things like archived boards or locking down in the event of an
  emergency
* Some validation checks for new posts are from the reply/thread form to
  the Post model's clean() method
* Add some new tests, I've really been falling behind on those

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-07-12 18:30:16 -07:00
parent 250ebe2b84
commit d3e1f5a978
3 changed files with 70 additions and 6 deletions

View File

@@ -68,8 +68,6 @@ class ReplyForm(PostForm):
def clean(self): def clean(self):
super().clean() super().clean()
if self.instance.op.lock:
raise ValidationError(_("This thread is locked, you cannot reply to it"))
capcode = self.cleaned_data["capcode"] capcode = self.cleaned_data["capcode"]
if capcode: if capcode:
if not self.user or not self.user.has_perm("board.use_capcode", capcode): if not self.user or not self.user.has_perm("board.use_capcode", capcode):

View File

@@ -58,6 +58,8 @@ class Board(models.Model):
# Auto-sink threshhold. This is the number of replies that a thread can have # Auto-sink threshhold. This is the number of replies that a thread can have
# before it stops being bumped. # before it stops being bumped.
autosink = models.IntegerField(default=300) autosink = models.IntegerField(default=300)
# Whether this board is read-only or not.
readonly = models.BooleanField(default=False)
@property @property
def threads(self): def threads(self):
@@ -115,10 +117,10 @@ class Post(models.Model):
# Thumbnail # Thumbnail
thumbnail = models.ImageField(upload_to=thumbs_upload, editable=False, null=True) thumbnail = models.ImageField(upload_to=thumbs_upload, editable=False, null=True)
# Original image name # Original image name
original_image_name = models.CharField(max_length=255, null=True) original_image_name = models.CharField(max_length=255, null=True, blank=True)
# Image width and height # Image width and height
image_width = models.IntegerField(null=True) image_width = models.IntegerField(null=True, blank=True)
image_height = models.IntegerField(null=True) image_height = models.IntegerField(null=True, blank=True)
class Meta: class Meta:
permissions = [ permissions = [
@@ -183,6 +185,15 @@ class Post(models.Model):
_("Image supplied is too large. Maximum image size is %(max)s"), _("Image supplied is too large. Maximum image size is %(max)s"),
params={"max": settings.MAX_UPLOAD_SIZE}, params={"max": settings.MAX_UPLOAD_SIZE},
) )
# Check if board is readonly
if self.board.readonly:
raise ValidationError(
_("This board is in readonly mode, you cannot create new posts.")
)
# Check if OP is locked
if self.op and self.op.lock:
raise ValidationError(_("This thread is locked, you cannot reply to it"))
# Rate limiting for posts # Rate limiting for posts
# TODO BUG: if a user's last post is deleted, and it is their only post, # TODO BUG: if a user's last post is deleted, and it is their only post,
# they will not hit rate limit. This could probably be abused # they will not hit rate limit. This could probably be abused

View File

@@ -1,10 +1,15 @@
from datetime import timedelta
from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase
from board.forms import PostForm, ReplyForm
from board.models import Board, Post from board.models import Board, Post
class BumpTestCase(TestCase): class BumpTestCase(TestCase):
def setUp(self): def setUp(self):
Board.objects.create(url="test", name="test") Board.objects.create(
url="test", name="test", post_cooldown=timedelta(seconds=0)
)
def test_bumping(self): def test_bumping(self):
board = Board.objects.get(url="test") board = Board.objects.get(url="test")
@@ -63,3 +68,53 @@ class BumpTestCase(TestCase):
self.assertEquals( self.assertEquals(
list(board.threads.order_by("-last_bump")), [post4, post3, post2, post1] list(board.threads.order_by("-last_bump")), [post4, post3, post2, post1]
) )
def test_thread_sticky(self):
board = Board.objects.get(url="test")
self.assertEquals(board.threads.count(), 0)
board = Board.objects.get(url="test")
self.assertEquals(board.threads.count(), 0)
post1 = Post.objects.create(
board=board, text="test 1", ip="127.0.0.1", sticky=True
)
post2 = Post.objects.create(board=board, text="test 2", ip="127.0.0.1")
post3 = Post.objects.create(board=board, text="test 3", ip="127.0.0.1")
post4 = Post.objects.create(board=board, text="test 4", ip="127.0.0.1")
self.assertEquals(
list(board.threads.order_by("-sticky", "-last_bump")),
[post1, post4, post3, post2],
)
Post.objects.create(board=board, text="bump", ip="127.0.0.1", op=post3)
self.assertEquals(
list(board.threads.order_by("-sticky", "-last_bump")),
[post1, post3, post4, post2],
)
def test_thread_lock(self):
board = Board.objects.get(url="test")
op = Post.objects.create(board=board, text="test 1", ip="127.0.0.1", lock=True)
reply = Post(board=board, text="reply 1", op=op, ip="127.0.0.1")
self.assertRaises(ValidationError, reply.full_clean)
# Also, make sure that thread locks work after new posts are made
op = Post.objects.create(board=board, text="test 1", ip="127.0.0.1")
reply1 = Post(board=board, text="reply 1", op=op, ip="127.0.0.1")
reply1.full_clean() # should not raise
op.lock = True
reply2 = Post(board=board, text="reply 2", op=op, ip="127.0.0.2")
self.assertRaises(ValidationError, reply2.full_clean)
class BoardReadonlyTestCase(TestCase):
def setUp(self):
Board.objects.create(
url="test", name="test", post_cooldown=timedelta(seconds=0), readonly=True
)
def test_board_readonly(self):
board = Board.objects.get(url="test")
op = Post(board=board, text="test 1", ip="127.0.0.1")
self.assertRaises(ValidationError, op.full_clean)