How do I include my def clean_slug function into my views or template so that it will work and show “title alr exist”

半城伤御伤魂 提交于 2021-02-05 08:11:34

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!