How to call function within if statement in Django template - django

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%}

Related

Why {{ form }} represents a form instance in template file?

According to django official doc,
All you need to do to get your form into a template is to place the form instance into the template context. So if your form is called form in the context, {{ form }} will render its <label> and <input> elements appropriately.
Where is the form instance named/defined as form for template context in django source code? and How can I change the name to something else {{ my_form }} for example ?
As you aptly say in your comment:
Context means the repository of data used for rendering via HTML
template files.
As to how form is passed to the context if you use a function based view, this is completely upto you with what key you pass the form, for example below code will pass it as my_form:
return render(request, 'template.html', {'my_form': my_form})
In case of a class based view most of them inherit from ContextMixin which declares the method get_context_data that is used to pass variables into the context. All class based views that deal with forms also inherit from FormMixin, looking at its source code it overrides get_context_data to pass the form into the context:
def get_context_data(self, **kwargs):
"""Insert the form into the context dict."""
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super().get_context_data(**kwargs)
Of course you can decide to again override get_context_data yourself and pass the form with some other name, but doing so wouldn't be very useful:
class YourView(CreateView):
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['my_form'] = context['form']
return context
When rendering your view in Views.py you can do the following to change the name of the form that you render:
Views.py
from .forms import MyForm1
def MyForm(request):
my_form = Myform1
return render(request , 'MyForm.html' , {'my_form' : my_form})
Then when calling your form on the HTML page you can call it as
{{ my_form }}

using CKEditor with Django and an inlineformset_factory with empty_form

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.

Django templates not updating after database changes via admin

I am using Django v2.2 admin to change the information on my database but after I change it and refresh the page, the new data is not there, only the old data.
A fix for this if I restart the server, the templates can now fetch the new data that I input.
views.py
# template with context
class Home(TemplateView):
template = 'home.html'
context = { 'bar': Baby.objects.all() }
def get(self, request):
return render(request, self.template, self.context)
home.html
{% for foo in bar %}
{{ foo.name }}
{{ foo.cost }}
{% endfor %}
How I can get the new data by refreshing the page and not restarting the server?
As others mentioned, use get_context_data() method is good idea, because ContextMixin is parent class (not base class, but part of TemplateView's __mro__ Method Resolution Order) of TemplateView which is responsible to pass data from view to template. But, if you want to render template manually using get() method, You should hit on database on every GET request (in your case).
class Home(TemplateView):
template = 'home.html'
def get(self, request):
self.context = {'bar': Baby.objects.all()}
return render(request, self.template, self.context)
Your code does not work, because static variables are initialized only once. In your case context was static variable.
Hope, it helps you.
Can you please try this?
class Home(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bar'] = Baby.objects.all()
return context

Django about get_context_data()

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

Passing Parameters to Django CreateView

I am trying to implement an appointment-making application where users can create sessions that are associated with pre-existing classes. What I am trying to do is use a django CreateView to create a session without asking the user for an associated class, while under the hood assigning a class to the session. I am trying to do this by passing in the pk of the class in the url, so that I can look up the class within the CreateView and assign the class to the session.
What I can't figure out is how exactly to do this. I'm guessing that in the template I want to have something like <a href="{% url create_sessions %}?class={{ object.pk }}>Create Session</a> within a DetailView for the class, and a url in my urls.py file containing the line
url(r'^create-sessions?class=(\d+)/$', CreateSessionsView.as_view(), name = 'create_sessions'), but I'm pretty new to django and don't exactly understand where this parameter is sent to my CBV and how to make use of it.
My plan for saving the class to the session is by overriding form_valid in my CBV to be:
def form_valid(self, form):
form.instance.event = event
return super(CreateSessionsView, self).form_valid(form)
If this is blatantly incorrect please let me know, as well.
Thank you!
GET parameters (those after ?) are not part of the URL and aren't matched in urls.py: you would get that from the request.GET dict. But it's much better to make that parameter part of the URL itself, so it would have the format "/create-sessions/1/".
So the urlconf would be:
url(r'^create-sessions/(?P<class>\d+)/$', CreateSessionsView.as_view(), name='create_sessions')
and the link can now be:
Create Session
and now in form_valid you can do:
event = Event.objects.get(pk=self.kwargs['class'])
urls.py
path('submit/request/<str:tracking_id>', OrderCancellationRequest.as_view(), name="cancel_my_order"),
Template
<form method="POST">
{% csrf_token %}
{{form | crispy}}
<button class="btn" type="submit">Submit</button>
</form>
View
class MyView(CreateView):
template_name = 'submit_request.html'
form_class = MyForm
model = MyModel
def form_valid(self, form, **kwargs):
self.object = form.save(commit=False)
self.object.created_at = datetime.datetime.now()
self.object.created_for = self.kwargs.get('order_id')
self.object.submitted_by = self.request.user.email
super(MyView, self).form_valid(form)
return HttpResponse("iam submitted")
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['header_text'] = "My Form"
context['tracking_id'] = self.kwargs.get('order_id')
return context