Django表单纯文本字段报错及删除确认页实现咨询
Let's break down your issues one by one and walk through the fixes, plus a much easier way to build that Django Admin-style delete confirmation page.
1. Fixing AttributeError: 'CharField' object has no attribute 'is_hidden'
Here's why this happens: In your DeleteSiteForm's Meta.widgets dictionary, you incorrectly passed forms.CharField(widget=PlainTextWidget) instead of a Widget instance.
The widgets dict expects direct references to Widget classes/instances, not form fields. When you pass CharField, Django tries to treat it like a Widget—but CharField doesn't have the is_hidden attribute that Widgets require, hence the error.
Fixed Form Code
class DeleteSiteForm(forms.ModelForm): class Meta: model = SiteData fields = ['location'] widgets = { # Pass the Widget instance directly, not a CharField wrapper 'location': PlainTextWidget(), }
2. Fixing TypeError: render() got an unexpected keyword argument 'name'
This error stems from a mismatch between your widget's render method signature and what Django (especially crispy forms) expects.
Django's standard Widget.render method uses named parameters like name and attrs, but you used _name and _attrs (underscore-prefixed aliases). When crispy forms calls render(name="location", ...), your function doesn't recognize the name keyword argument.
Also, directly using mark_safe(value) carries a small XSS risk if your value contains untrusted HTML. Use format_html instead to auto-escape content safely.
Fixed Widget Code
from django.utils.html import format_html from django.forms import Widget class PlainTextWidget(Widget): # Match Django's standard render method signature exactly def render(self, name, value, attrs=None): # Use format_html for safe auto-escaping, handle empty values return format_html('{}', value) if value is not None else '-' # Optional: Explicitly declare this isn't a hidden widget (defaults to False, but clear is better) is_hidden = False
3. Simplified Delete Confirmation Page (Like Django Admin)
You don't need to build a custom form for this! Django's built-in DeleteView generic class view handles the entire flow: displaying a confirmation page on GET, executing the delete on POST, and handling redirects. It's exactly how Django Admin implements delete confirmations.
Step 1: Define the DeleteView
from django.views.generic import DeleteView from django.urls import reverse_lazy from .models import SiteData class SiteDeleteView(DeleteView): model = SiteData # Default template path: <your_app>/<model_name>_confirm_delete.html template_name = 'your_app/site_confirm_delete.html' # Where to redirect after successful deletion (use reverse_lazy for class-based views) success_url = reverse_lazy('site_list') # Optional: Add custom permission checks (e.g., only allow owners to delete) def get_object(self, queryset=None): obj = super().get_object(queryset) # Example: if obj.created_by != self.request.user: # raise PermissionDenied return obj
Step 2: Create the Confirmation Template
Make a template at your_app/templates/your_app/site_confirm_delete.html—this will look and behave just like Django Admin's delete page:
{% extends 'base.html' %} {% load crispy_forms_tags %} {% block content %} <h1>Confirm Site Deletion</h1> <p>Are you sure you want to delete the site located at <strong>{{ object.location }}</strong>?</p> <form method="post"> {% csrf_token %} {{ form|crispy }} <!-- Django auto-generates a hidden confirmation form --> <button type="submit" class="btn btn-danger">Yes, Delete This Site</button> <a href="{% url 'site_list' %}" class="btn btn-secondary">Cancel</a> </form> {% endblock %}
Step 3: Add the URL Route
Update your app's urls.py to point to the view:
from django.urls import path from .views import SiteDeleteView urlpatterns = [ # ... your other routes path('sites/<int:pk>/delete/', SiteDeleteView.as_view(), name='site_delete'), ]
This approach is cleaner, leverages Django's built-in tools, and avoids writing custom form logic from scratch.
内容的提问来源于stack exchange,提问作者AlexW

