from django.core.exceptions import ValidationError from django.conf import settings from django.db import transaction from django.db.models import Q from django import forms from django.forms import ModelForm, ModelChoiceField from django.forms.models import fields_for_model from django.utils import timezone from django.utils.translation import gettext as _ from guardian.shortcuts import get_objects_for_user from board.models import Ban, Post, Report, ReportReason, ReportRecord from hcaptcha.fields import hCaptchaField class PostForm(ModelForm): """ A form used for new threads for posts. This requires the board and the IP address to be specified. """ hcaptcha = hCaptchaField() if settings.USE_HCAPTCHA else None class Meta: model = Post fields = ["subject", "name", "text", "capcode", "image"] def __init__(self, *args, user, board, ip, **kwargs): super(PostForm, self).__init__(*args, **kwargs) self.user = user self.instance.board = board self.instance.ip = ip self.fields["capcode"].queryset = get_objects_for_user( self.user, "board.use_capcode" ) def clean(self): super().clean() capcode = self.cleaned_data["capcode"] if capcode: if not self.user or not self.user.has_perm("board.use_capcode", capcode): raise ValidationError(_("Could not create post")) class ReplyForm(PostForm): """ A form used for replies to posts. This requires the OP post, the reply post, the board, and the IP address be specified. """ hcaptcha = hCaptchaField() if settings.USE_HCAPTCHA else None class Meta: model = Post fields = ["name", "text", "bump", "capcode", "image"] def __init__(self, *args, op, **kwargs): super(ReplyForm, self).__init__(*args, **kwargs) # Get the true OP of this post while op.op: op = op.op self.instance.op = op self.fields["capcode"].queryset = get_objects_for_user( self.user, "board.use_capcode" ) def clean(self): super().clean() capcode = self.cleaned_data["capcode"] if capcode: if not self.user or not self.user.has_perm("board.use_capcode", capcode): raise ValidationError(_("Could not create post")) class ReportForm(ModelForm): """ A form used to create reports on posts. This requires the board and the IP address to be specified. """ # uses report_form.html class Meta: model = Report fields = ["reason"] def __init__(self, *args, op, board, ip, **kwargs): super(ReportForm, self).__init__(*args, **kwargs) self.instance.ip = ip self.op = op queryset = ReportReason.objects.filter(Q(board=None) | Q(board=board)) self.fields["reason"] = ModelChoiceField(queryset=queryset) def clean(self): # Get or create the record before creating the model with transaction.atomic(): try: record = ReportRecord.objects.get(post=self.op) except ReportRecord.DoesNotExist: record = ReportRecord.objects.create(post=self.op) self.instance.record = record return super(ReportForm, self).clean() class BanForm(ModelForm): """ A form used to create bans based on specific posts. """ # uses ban_form.html duration = forms.IntegerField(label="Duration (days)", min_value=1, required=False) class Meta: model = Ban fields = ["ban_reason", "board"] def __init__(self, *args, op, **kwargs): super(BanForm, self).__init__(*args, **kwargs) self.op = op self.instance.ip = op.ip self.instance.post_id = op.id def clean(self): super(BanForm, self).clean() now = timezone.now() duration = self.cleaned_data["duration"] if duration: expires = now + timezone.timedelta(days=duration) else: expires = None self.instance.expires = expires class PostModifyForm(ModelForm): """ A form used to modify the attributes of a post (sticky, locked, sink, etc) """ class Meta: model = Post # we specify fields up here too because otherwise they won't be # recognized by the form to update values fields = ["sticky", "bump", "lock"] def __init__(self, *args, user, **kwargs): super(PostModifyForm, self).__init__(*args, **kwargs) self.user = user fields = [] if self.user.has_perm("board.set_sticky"): fields += ["sticky"] if self.user.has_perm("board.set_bump"): fields += ["bump"] if self.user.has_perm("board.set_lock") and not self.instance.op: fields += ["lock"] # NOTE: # We do *not* need to check permissions against these fields we're # setting down here in the self.clean() function in the case that a # malicious actor has access to the modify form and injects a "sticky" # value to their modify request. # # We specify fields up in the Meta class, but we reset them down here. # If the field isn't present in this list, then it doesn't get updated. # If the field isn't present in the above list, then it doesn't get updated. self.fields = fields_for_model(Post, fields)