Использование валидаторов в Django не работает должным образом

#python #django

#python #django

Вопрос:

Недавно я узнал о валидаторах и о том, как они работают, но я пытаюсь добавить функцию в свой проект блога, которая выдает ошибку при использовании плохого слова. У меня есть список плохих слов в текстовом формате, и я добавил код, который должен быть в моделях.py проблема в том, что по какой-то причине я не уверен, что ничего не заблокировано.

Вот models.py

 class Post(models.Model):
       title = models.CharField(max_length=100, unique=True)
       ---------------other unrelated------------------------

def validate_comment_text(text):
    with open("badwords.txt") as f:
        censored_word = f.readlines()
    words = set(re.sub("[^w]", " ", text).split())
    if any(censored_word in words for censored_word in CENSORED_WORDS):
        raise ValidationError(f"{censored_word} is censored!")

class Comment(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    content = models.TextField(max_length=300, validators=[validate_comment_text])
    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now=True)
  

вот views.py:

 class PostDetailView(DetailView):
    model = Post
    template_name = "blog/post_detail.html"  # <app>/<model>_<viewtype>.html

    def get_context_data(self, *args, **kwargs):
        context = super(PostDetailView, self).get_context_data()
        post = get_object_or_404(Post, slug=self.kwargs['slug'])
        comments = Comment.objects.filter(
            post=post).order_by('-id')
        total_likes = post.total_likes()
        liked = False
        if post.likes.filter(id=self.request.user.id).exists():
            liked = True

        if self.request.method == 'POST':
            comment_form = CommentForm(self.request.POST or None)
            if comment_form.is_valid():
                content = self.request.POST.get('content')
                comment_qs = None

                comment = Comment.objects.create(
                    post=post, user=self.request.user, content=content)
                comment.save()
                return HttpResponseRedirect("blog/post_detail.html")
        else:
            comment_form = CommentForm()

        context["comments"] = comments
        context["comment_form"] = comment_form
        context["total_likes"] = total_likes
        context["liked"] = liked
        return context

    def get(self, request, *args, **kwargs):
        res = super().get(request, *args, **kwargs)
        self.object.incrementViewCount()
        if self.request.is_ajax():
            context = self.get_context_data(self, *args, **kwargs)
            html = render_to_string('blog/comments.html', context, request=self.request)
            return JsonResponse({'form': html})
        return res

class PostCommentCreateView(LoginRequiredMixin, CreateView):
    model = Comment
    form_class = CommentForm

    def form_valid(self, form):
        post = get_object_or_404(Post, slug=self.kwargs['slug'])
        form.instance.user = self.request.user
        form.instance.post = post
        return super().form_valid(form)
  

Вот моя пробная версия, которая не сработала

 def validate_comment_text(sender,text, instance, **kwargs):
    instance.full_clean()
    with open("badwords.txt") as f:
        CENSORED_WORDS = f.readlines()

    words = set(re.sub("[^w]", " ", text).split())
    if any(censored_word in words for censored_word in CENSORED_WORDS):
        raise ValidationError(f"{censored_word} is censored!")

pre_save.connect(validate_comment_text, dispatch_uid='validate_comment_text')
  

Я новичок, поэтому, если бы вы могли дать некоторые пояснения к ответу, я был бы благодарен, чтобы я мог избежать повторения одних и тех же ошибок.

Комментарии:

1. (1) Ваш отступ отключен. Я предполагаю, что это просто проблема копирования / вставки в SO. (2) Эта проверка будет учитываться регистр — если вы сохраняете свои слова как все верхние / нижние или что-то в этом роде, есть большая вероятность, что многие из них не совпадут. Предложите преобразовать ваш текст во все строчные буквы и сохранить все строчные CENSORED_WORDS буквы и посмотреть, поможет ли это.

2. @michjnich это просто ошибка копирования / вставки, вызывающая ошибку отступа, но проблема не в том, что она не работает

Ответ №1:

Я уверен, что есть много способов справиться с этим, но я, наконец, решил применить обычную практику во всех моих проектах Django:

когда модель требует проверки, я переопределяю clean(), чтобы собрать всю логику проверки в одном месте и предоставить соответствующие сообщения об ошибках.

В clean() вы можете получить доступ ко всем полям модели, и вам не нужно ничего возвращать; просто вызывайте ValidationErrors по мере необходимости:

 from django.db import models
from django.core.exceptions import ValidationError


class MyModel(models.Model):

    def clean(self):
         
        if (...something is wrong in "self.field1" ...) {
            raise ValidationError({'field1': "Please check field1"})
        }
        if (...something is wrong in "self.field2" ...) {
            raise ValidationError({'field2': "Please check field2"})
        }

        if (... something is globally wrong in the model ...) {
            raise ValidationError('Error message here')
        }
  

Администратор уже извлекает из этого выгоду, вызывая clean() из ModelAdmin.save_model()
и показывая любую ошибку в представлении изменений; когда поле обращается к ValidationError,
соответствующий виджет будет выделен в форме.

Чтобы выполнить ту же самую проверку при программном сохранении модели, просто переопределите save() следующим образом:

 class MyModel(models.Model):

    def save(self, *args, **kwargs):
        self.full_clean()
        ...
        return super().save(*args, **kwargs)
  

Доказательство:

файл models.py

 from django.db import models


class Model1(models.Model):

    def clean(self):
        print("Inside Model1.clean()")

    def save(self, *args, **kwargs):
        print('Enter Model1.save() ...')
        super().save(*args, **kwargs)
        print('Leave Model1.save() ...')
        return

class Model2(models.Model):

    def clean(self):
        print("Inside Model2.clean()")

    def save(self, *args, **kwargs):
        print('Enter Model2.save() ...')
        self.full_clean()
        super().save(*args, **kwargs)
        print('Leave Model2.save() ...')
        return
  

файл test.py

 from django.test import TestCase
from project.models import Model1
from project.models import Model2

class SillyTestCase(TestCase):

    def test_save_model1(self):
        model1 = Model1()
        model1.save()

    def test_save_model2(self):
        model2 = Model2()
        model2.save()
  

Результат:

 ❯ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
Enter Model1.save() ...
Leave Model1.save() ...
.Enter Model2.save() ...
Inside Model2.clean()
Leave Model2.save() ...
.
----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK
Destroying test database for alias 'default'...
  

Комментарии:

1. Вы можете добавить self.instance.full_clean() в def clean(self) для запуска проверки модели. Если есть проблема, она вызовет такую ValidationError же проблему, как и любая другая.

2. Как говорится в другом ответе… Также было бы неплохо переключиться на ModelForm, и проверки модели будут выполняться автоматически, а также настройка полей.

3. @TimTisdall … поскольку вы сказали «и добавили код, который должен быть в models.py «, это то, что я сделал. И я действительно считаю, что для этого есть очень веская причина: если вы сохраните модель программно, она будет проходить ту же проверку. Пока код модели защищает себя, ваши данные всегда будут в хорошем состоянии, независимо от того, насколько хорошо или плохо ведет себя пользовательский интерфейс (т. Е. ModelForm)

4. @TimTisdall «Вы можете добавить self.instance.full_clean() в def clean(self)» … Я действительно считаю, что Django работает в противоположном направлении: сначала вы вызываете full_clean() в Model.save() , затем (как следствие) фреймворк вызовет clean() . Я согласен, что имена методов здесь довольно запутанные 😉

5. @TimTisdall Я, честно говоря, не понимаю, откуда взялось это понятие, что ValidationErrors предназначены для форм. DRF также использует их в сериализаторах и для моделей, вот официальная документация , в которой конкретно указано, что следует использовать ValidationError, в каком порядке вызываются вещи и почему clean() необходим помимо отдельных методов очистки полей.

Ответ №2:

Валидаторы запускаются только при использовании ModelForm . Если вы вызываете напрямую comment.save() , валидатор не запустится. ссылка на документы

Итак, либо вам нужно проверить поле с помощью ModelForm , либо вы можете добавить pre_save сигнал и запустить проверку там (вам нужно будет вручную вызвать метод или использовать full_clean для запуска проверок). Что-то вроде:

 from django.db.models.signals import pre_save

def validate_model(sender, instance, **kwargs):
    instance.full_clean()

pre_save.connect(validate_model, dispatch_uid='validate_models')
  

Комментарии:

1. Не ясно, почему это было отклонено? Недостаточно информации из вопроса, чтобы определить, является ли это реальным решением, но кажется разумным предложением!

2. Я согласен: этот ответ полезен, поэтому я поддержу его 😉 Просто по личному вкусу я бы предпочел переопределить save() , поскольку это кажется мне более понятным. Сигналы более уместны, когда вам нужен хук для модели, объявленной в другом ap

3. @Aman Garg Я пытался использовать pre_save, но мне не удалось его реализовать, я обновил сообщение своей пробной версией.

4. @michjnich Я включил модель Post и представления, чтобы добавить больше деталей к вопросу. Спасибо

5. у @Shiko validate_comment_text(sender,text, instance, **kwargs) нет правильной подписи. Вторым аргументом является instance , поэтому просто обновите подпись, как указано в ответе, и она будет работать. Ссылка