#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
, поэтому просто обновите подпись, как указано в ответе, и она будет работать. Ссылка