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:
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user