I was looking in django source-code to understand super(ExampleView, self).get_context_data(**kwargs) and why I used it in my view:
class ExampleView(TemplateView):
# ... atributes
def get_context_data(self, **kwargs):
context = super(ExampleView, self).get_context_data(**kwargs)
context['key'] = 'value'
return context
I've found:
class ContextMixin(object):
"""
A default context mixin that passes the keyword arguments received by
get_context_data as the template context.
"""
def get_context_data(self, **kwargs):
if 'view' not in kwargs:
kwargs['view'] = self
return kwargs
I can't figure it out what that condition or kwargs['view'] = self does.
I've tried in my view to overwrite get_context_data() without that default condition:
class ExampleView(TemplateView):
# .. atributes
def get_context_data(self, **kwargs):
kwargs['key'] = 'value'
return kwargs
and it worked the same as the first code I've written.
Those 2 lines of code add the view as variable to the context if it was not already present. Most people never use this, but you could do something like this:
class SomeView(TemplateView):
template_name = "something.html"
title = "My list of books"
def books(self): #custom method
return Book.objects.all()
And then in your template you could reference the books method and title attribute through the view variable:
<h1>{{ view.title }}</h1>
<ul>
{% for book in view.books %}
<li>{{ book }}</li>
{% enfor %}
<ul>
Ah yes, and note that you don't even need a custom get_context_data() method in this case
Related
When I render the empty form it is not attaching any media to it. i.e. the CKEditor is not displayed. The element looks like it is missing the css/js - it's like it doesn't get set up properly.
Note : the other sections are displayed correctly.
Where to start? Problem with Django's Empty Form method? Problem with CKEditor? Me :)
<div class="container">
<button type ="button" class="btn-info btn-lg" id="add_section">Add Section</button>
{{form.media }}
{{form|crispy }}
{{sections.media}}
<div>
{{sections.empty_form}}
</div>
<div id = 'section_management'> {{ sections.management_form }} </div>
{% for section in sections %}
{{ section|crispy }}
{% endfor %}
<button class="btn btn-info ml-2" type="submit">Update</button>
Cancel
</div>
Here's my Forms
class SectionForm(forms.ModelForm):
content = RichTextFormField()
class Meta:
model = Section
fields = ('content',)
empty_permitted=True
def __init__(self, *args, **kwargs):
print('section form called')
super().__init__(*args, **kwargs)
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title','category','span')
def __init__(self, *args, **kwargs):
self.is_superuser = kwargs.pop('is_superuser', None)
super().__init__(*args, **kwargs)
if self.is_superuser == False:
self.fields.pop("span")
view code
class ArticleUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
template_name = 'articles/ArticleUpdate.html'
form_class = ArticleForm
model = Article
SectionFormSet = inlineformset_factory(Article, Section, form=SectionForm, extra=0, can_delete=False, fields=('content',))
#if i always pass back at least 1 extra section form, I can grab the html for it in Jquery */
#if i do not pass back extra=0 how would i get the html in jquery for the extra form?
def test_func(self):
article = self.get_object()
if self.request.user == article.author or self.request.user.is_superuser :
return True
else:
return False
def get_context_data(self, **kwargs):
print('get context data called update view')
'''
section_form
'''
context = super().get_context_data(**kwargs)
if self.request.POST:
context['sections'] = self.SectionFormSet(self.request.POST,instance=self.object)
else:
context['sections'] = self.SectionFormSet(instance=self.object)
return context
def get_section_form(self): #we know we can access this in the template
return SectionForm()
def save_sections(self):
print('save sections called update view')
try:
context = self.get_context_data()
section_form = context['sections']
if section_form.is_valid():
# section_form.instance = self.object #if im passing instance in the factory, do I need it here to?
section_form.save()
except Exception as e:
print('failed to save section: ' + str(e))
def form_valid(self, form):
print('form valid called update view')
form.instance.author = self.request.user
response = super().form_valid(form) #save article form
self.save_sections()
return response
def get_success_url(self):
return reverse_lazy('index')
Basically, what I've done so far to overcome this problem is by accessing the form directly from the template, bypassing the inlineFormSet to get an empty form....(hope that makes sense).
I go directly to the view :
{{view.get_section_form}}
with this method in the view
def get_section_form(self): #we know we can access this in the template
return SectionForm()
I have subsequently found out I can do this in the template as well :
{{sections.media}}
{{sections.form}}
The above also passes an empty form - with the media filled in- as long as you pass the model form into the factory to start of with.
These are work-arounds for me currently, but would appreciate a proper answer as to why empty_form doesn't work properly.
My further investigation into this was basically comparing what is returned via accessing the formset to return an empty form, or using the modelForm directly.
Django docs :
empty_formĀ¶
BaseFormSet provides an additional attribute empty_form which returns a form instance with a prefix of __prefix__ for easier use in dynamic forms with JavaScript.
If you replace prefix on the generated html -- everything works. No idea why. You can replace it with anything, i.e. prefix1
at which point CKEditor starts to display the formset correctly.
I have a custom template tag as following:
#register.simple_tag
def call_method(obj, method_name, *args):
"""
Usage
in shell
obj.votes.exists(user_id)
in template
{% call_method obj.votes 'exists' user.id %}
"""
method = getattr(obj, method_name)
return method(*args)
Then I can call it in the template (Class-based detail view) as following.
{% call_method object.votes 'exists' user.id %}
My question is how can use this template tag in If statement?
For example, why I cannot use like:
{% if call_method object.votes 'exists' user.id %}
I am using django-vote [https://github.com/shanbay/django-vote][1]
My goal is to check whether a user already voted so that I can change the class of the vote button.
Otherwise, I can already check it in view. And it works fine.
If it is not possible to use the simple tag with argument within If statement, could you please suggest a way to reach my goal?
Edit:
I am adding view.
def vote(request, slug):
term = Term.objects.get(slug=slug)
if term.votes.exists(user_id=request.user.id):
term.votes.down(user_id=request.user.id)
else:
term.votes.up(user_id=request.user.id)
return HttpResponseRedirect(term.get_absolute_url())
and Model:
class Term(VoteModel, models.Model):
why not to pass the variable from view to template? for example inside of view context you can set your own context variable, for example:
class MyView(generic.DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
obj = self.get_object()
is_user_voted_already = obj.votes.exists(user_id)
context.update({
'is_user_voted_already': is_user_voted_already
})
return context
and in template view you can check. Just like this:
{% if is_user_voted_already %}code here if user voted already{%else}code here user not voted already{%endif%}
So I want to build a custom layout, that extends LayoutObject for a form that I have.
class NewBookForm(LayoutObject):
template = 'library/layouts/new_book_layout.html'
def __init__(self, fields, template=None, *args, **kwargs):
self.fields = fields
# Overrides class variable with an instance level variable
if template:
self.template = template
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
fields = self.get_rendered_fields(form, form_style, context, template_pack, **kwargs)
template = self.get_template_name(template_pack)
return render_to_string(template, {'fields': fields})
And I'm calling it using
self.helper.layout = Layout(
NewBookForm(Fieldset('book_id', 'name', 'author'))
)
Right now Django is saying "template does not exist."
However, this is not getting me the result I'm looking for.
UPDATE 1:
Now that library/layouts/new_book_layout.html has something like
<div class="w-40 mr-2">
{{name|as_crispy_field}}
{{name.errors.as_ul }}
{{author|as_crispy_field}}
{{author.errors.as_ul }}
</div>
I'm now getting the error:
CrispyError at library/layouts/new_book_layout.html
|as_crispy_field got passed an invalid or inexistent field
and highlighted:
{{name|as_crispy_field}}
That is because once you call get_rendered_fields it returns an string object not a crispy object, so instead of using |as_crispy_field filter, you should use the |safe filter since what your context contains is an HTML string.
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
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
I want to show empty form field if there is nothing to show, otherwise a form field with the value inside:
{% if somevalue %}
{{form.fieldname}} #<---- how do i set the `somevalue` as value of fieldname here?
{% else %}
{{form.fieldname}}
{% endif %}
In your view, if it is a class-based view, do it like so:
class YourView(FormView):
form_class = YourForm
def get_initial(self):
# call super if needed
return {'fieldname': somevalue}
If it is a generic view, or not a FormView, you can use:
form = YourForm(initial={'fieldname': somevalue})
There are multiple ways to provide initial data in django form.
At least some of them are:
1) Provide initial data as field argument.
class CityForm(forms.Form):
location = ModelChoiceField(queryset=City.objects.all(), initial='Munchen')
2) Set it in the init method of the form:
class CityForm(forms.Form):
location = ModelChoiceField(queryset=City.objects.all())
def __init__(self, *args, **kwargs):
super(JobIndexSearchForm, self).__init__(*args, **kwargs)
self.fields['location'].initial = 'Munchen'
3) Pass a dictionary with initial values when instantiating the form:
#views.py
form = CityForm(initial={'location': 'Munchen'})
In your case, I guess something like this will work..
class CityForm(forms.Form):
location = ModelChoiceField(queryset=City.objects.all())
def __init__(self, *args, **kwargs):
super(JobIndexSearchForm, self).__init__(*args, **kwargs)
if City.objects.all().exists():
self.fields['location'].initial = ''
else:
self.field['location'].initial = City.objects.all()[:1]
That all is just for demonstration, you have to adapt it to your case.