'str' object has no attribute 'visible

2020-07-26 14:08发布

问题:

I've been struggling with class views all day after starting on them yesterday. My issue is constantly getting 'str' object has no attribute 'visible_fields', so the 'form' item below is not really a form:

template-

<form action="" method="post">
    {% csrf_token %}
    {{form|bootstrap}}
    <input type="submit" name="submit" value="Add new article"/>
</form>

view-

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'index/add_article.html'
    form_class = ArticleForm

    def post(self, request, *args, **kwargs):
        article_form = self.get_form()
        if article_form.is_valid():
            article = article_form.save(commit=False)
            title   = article_form.cleaned_data['title']
            url     = article_form.cleaned_data['url']
            title = process_title(url)
            article.title = title
            article.save()
            return redirect("index:article_list")
        else:
            form = ArticleForm()
            print type(form)
            print dir(self)
            return render(request, 'index/add_article.html')

The worst part is printing type(form) shows it is <class 'index.forms.ArticleForm'>. I'm trying to just have it redirect to the list view if the form saved, and replay the form with the error (You already have an article with that URL) if the form is bad. I heard class views are easier to work with and huge projects I've read through use them, but they really seem worse than the old views. I assume that's because I'm not using them well

Every example I've seen has a template getting a "form" somehow, like

class RangeCreateView(CreateView):
    model = Range
    template_name = 'dashboard/ranges/range_form.html'
    form_class = RangeForm

    def get_success_url(self):
        if 'action' in self.request.POST:
            return reverse('dashboard:range-products',
                           kwargs={'pk': self.object.id})
        else:
            msg = render_to_string(
                'dashboard/ranges/messages/range_saved.html',
                {'range': self.object})
            messages.success(self.request, msg, extra_tags='safe noicon')
            return reverse('dashboard:range-list')

    def get_context_data(self, **kwargs):
        ctx = super(RangeCreateView, self).get_context_data(**kwargs)
        ctx['title'] = _("Create range")
        return ctx

then like magic in range_form.html:

{% include "dashboard/partials/form_fields.html" with form=form %}

My issue here is I need to process the title of the form, with

def process_title(url):
    def _search_for_title(url):
        try:
            r = requests.get(url)
            content = r.text
            t = html.document_fromstring(content)
            return t.find(".//title").text
        except IOError:
            return None

    title = _search_for_title(url)
    return title or 'None'

This kind of ruins the purpose of a class based view. It seems I should be processing the title by overriding 'clean' in the form itself?

Otherwise, how can I make this view pass a form object, render it in the template, and just re-render the template if the form didn't pass?

And how can I access the form in the template?

Thank you

回答1:

You do it by following the example view, rather than overriding post instead as you are doing.

In your case, simply displaying and processing a form is the default behaviour of a CreateView. So there is no need to override any methods at all. Your view should just be:

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'index/add_article.html'
    form_class = ArticleForm