问题
Hi I have written a def clean(self)
function in forms to make sure that if there was previously already a post with the same title, they will return a message saying that the title already exists and hence the form cannot be submitted.
Problem now:
When I enter a title that already exists and I try to create the post, all data previously input will be removed and I will be redirected to a fresh form. No errors were raised. What I want is for the error to be raised and shown to the user when I try to click on the create button so all data remains there and the user knows and can change the title before attempting the create the blog post again.
return cleaned_data
in forms.py is not defined too...giving a nameerror
Guideline:
Note! There is NO slug field in my form. The slug is only for the url for each individual blogpost. But basically the slug consists of the title. Eg if my username is hello
and my chief_title is bye
, my slug will be hello-bye
. Both the slug and the title has to be unique as you can see in the model, but there is no slug in the form.
models.py
class BlogPost(models.Model):
chief_title = models.CharField(max_length=50, null=False, blank=False, unique=True)
brief_description = models.TextField(max_length=300, null=False, blank=False)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(blank=True, unique=True)
views.py
def create_blog_view(request):
context = {}
user = request.user
if not user.is_authenticated:
return redirect('must_authenticate')
if request.method == 'POST':
form = CreateBlogPostForm(request.POST or None, request.FILES or None)
if form.is_valid():
obj= form.save(commit = False)
author = Account.objects.filter(email=user.email).first()
obj.author = author
obj.save()
obj.members.add(request.user)
context['success_message'] = "Updated"
return redirect('HomeFeed:main')
else:
form = CreateBlogPostForm()
context['form'] = form
return render(request, "HomeFeed/create_blog.html", {})
forms.py
class CreateBlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['chief_title']
def clean(self):
chief_title = self.cleaned_data['chief_title']
qs = BlogPost.objects.filter(chief_title=chief_title)
if qs.exists():
raise forms.ValidationError('Post already exists')
return cleaned_data
html
<form class="create-form" method="post" enctype="multipart/form-data">{% csrf_token %}
{% if form.non_field_errors %}
{{form.non_field_errors}}
{% endif %}
<!-- chief_title -->
<div class="form-group">
<label for="id_title">Chief Title!</label>
<input class="form-control" type="text" name="chief_title" id="id_title" placeholder="Title" required autofocus>
</div> {{form.chief_title.errors}}
<button class="submit-button btn btn-lg btn-primary btn-block" type="submit">CREATE</button>
</form>
回答1:
Well you can create a custom validator to check if the slug exists:
from django.core.exceptions import ValidationError
def validate_slug_exists(value):
blog_post = BlogPost.objects.filter(slug=value)
if blog_post.exists():
raise ValidationError('The post with a given title already exists')
Then in your form add this validator to the fields validators list:
class CreateBlogPostForm(forms.ModelForm):
chief_title = forms.CharField(validators = [validate_slug_exists])
UPDATE
You can also try using validate_unique()
in your form class:
def validate_unique(self, exclude=None):
qs = BlogPost.objects.all()
if qs.filter(chief_title=self.chief_title).exists():
raise ValidationError("Blog post with this title already exists")
回答2:
You can raise a ValidationError as shown in the docs. This would then be displayed in the form for the user.
def clean_slug(self):
slug = slugify(self.cleaned_data.get("chief_title")) if len(self.cleaned_data.get("slug", )) == 0 \
else self.cleaned_data.get("slug", )
if BlogPost.objects.filter(slug=slug).exists():
raise ValidationError(_('Slug already exists.'), code='invalid')
return slug
回答3:
If you set unique = True
Django takes care of checking whether another entry already exists in the database and adds an error in ModelForm.errors
.
This is happening inModelForm.validate_unique.
There is no need to bother with this method unless you want to add more info in the error, such as the url of the existing object (it will cost 1 db hit).
Otherwise, the error already exists and you can just return the form with its errors instead of returning a new instance of the form as you currently do in views.py
.
Therefore the following views.py
as explained in this post should do what you are trying to do:
app/views.py:
def create_blog_view(request):
context = {}
# ...
if request.method == 'POST':
form = CreateBlogPostForm(request.POST or None, request.FILES or None)
if form.is_valid():
# do your thing
else:
context['form'] = form
return render(request, "HomeFeed/create_blog.html", context) # context instead of {}
If you want to get fancier and hit the db once more, you can add the url of the existing object in the errors list as such:
project/settings.py
...
# If you want to provide more info
MY_UNIQUE_BLOGPOST_ERROR_MESSAGE = "This BlogPost already exists"
...
app/models.py
from django.db import models
from django.conf import settings
from django.urls import reverse
class BlogPost(models.Model):
# ...
slug = models.SlugField(
blank=True,
unique=True,
error_messages={"unique": settings.MY_UNIQUE_BLOGPOST_ERROR_MESSAGE),
)
def get_admin_url(self):
return reverse("admin:app_blogpost_change", args=(self.id,))
...
app/forms.py
from django import forms
from django.conf import settings
from django.utils.html import format_html
from itertools import chain
from app.models import BlogPost
class CreateBlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = '__all__'
def validate_unique(self):
'''
If unique error exists, find the relevant object and return its url.
1 db hit
There is no other need to override this method.
'''
super().validate_unique()
if settings.SHOP_ENTITY_UNIQUE_ERROR in chain.from_iterable(
self.errors.values()
):
instance = BlogPost.objects.get(slug=self.instance.slug)
admin_url = instance.get_admin_url()
instance_name = instance.__str__()
self.add_error(
None,
forms.ValidationError(
format_html(
"This entry already exists: <a href=\"{0}\">{1}</a>",
admin_url,
instance_name,
),
code="unique",
),
)
来源:https://stackoverflow.com/questions/65879639/how-do-i-include-my-def-clean-slug-function-into-my-views-or-template-so-that-it