I have a django project, where I have two templates: notification.html and base.html. Using get_context_data() method I display the number of answers at the notification.html. I tried to use the same variable in base.html, but it didn't work. I also created CBV that and used get_context_data() to pass the same logic and display it at the base.html, but it doesn't work. How do I display the variable in another template?
I don't how to pass 'answers.count' to the base.html, if base.html doesn't have a view (I created this only now trying to display this variable).
forum.views
class NotificationView(ListView):
model = Answer
template_name = 'forum/notification.html'
# If user visited the notification page - mark all answers as read
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
question = Question.objects.filter(user=user)
answer_read = self.model.objects.filter(question__in=question)
answer_read.update(is_read=True)
return answer_read
def get_context_data(self, **kwargs):
context = super().get_context_data()
user = get_object_or_404(User, username=self.kwargs.get('username'))
question = Question.objects.filter(user=user)
context['answers_all'] = Answer.objects.filter(question__in=question).order_by('-date_posted')
context['answers'] = Answer.objects.filter(question__in=question, is_read=False)
return context
forum/notification.html
{% extends 'main/base.html' %}
{% block content %}
<div class="container">
<b>УNotofocations: {{ answers.count }}</b>
{% for answer in answers_all %}
<div class="card mb-3 mt-2">
<div class="card-body">
Ответ на вопрос: {{ answer.question }}
<p>{{ answer.detail }}</p>
{{ answer.user.username }}
<span class="ml-4">{{ answer.date_posted|naturalday }}</span>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
main.views
class BaseView(TemplateView):
template_name = 'main/base.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
user = get_object_or_404(User, username=self.kwargs.get('username'))
question = Question.objects.filter(user=user)
context['answers'] = Answer.objects.filter(question__in=question)
return context
main/base.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
{% if user.is_authenticated %}
{% if answers.count < 1 %}
<i class="fas fa-bell"></i>
{% else %}
<i class="fas fa-bell"></i><span class="pending">{{ answers.count }}</span>
{% endif %}
{% endif %}
</head>
</html>
guess you are only missing the build-in template tags
in forum/notification.html
{% extends "base.html" %}
in main/base.html
{% block content %}{% endblock %}
https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#extends
https://docs.djangoproject.com/en/4.1/ref/templates/language/#template-inheritance
Finally, I solved this problem. The idea was to display the variable from context_data at base.html. As get_context_data didn't help, I found the solution in Django documentation. The solution was so easy, I hope to save time for other learners if they got the same problem.
I used context_processors.py to pass extra information and display it on the page. My step were:
Created a context_processors.py file in my app directory.
Created a function def get_notifications_number(request) where I filter what I want to display at base.html and returned return {'answers': answers}
Finally, I registered the file in Django TEMPLATES 'my_app.context_processors.get_notifications_number'.
Related
I'm having difficulty completely rendering the template of TopicsPage. It's suppose to render sub_heading.html which extends listing.html (both templates reside in the same templates folder). The test passes the self.assertTemplateUsed() assertion.
However an AssertionError is raised at the point of:
self.assertContains(response, "Looking for more?")
AssertionError: False is not true : Couldn't find 'Looking for more?' in response
How can I get the sub_heading.html content to render for the test to pass when the template is being used already? I put the implementation for GET method as pass intentionally just to show how I'm subclassing the View.
test_views.py
class TestTopicsPage__002(TestCase):
'''Verify that a topic and associated information is displayed
on the main page once the topic is created'''
#classmethod
def setUpTestData(cls):
user = User.objects.create_user("TestUser")
python_tag = Tag.objects.create(name="Python")
js_tag = Tag.objects.create(name="JavaScript")
content = {
'heading': "Test Title",
'text': "Mock text content",
'posted': datetime.datetime.now()
}
cls.topic = Topic(**content)
cls.topic.author = user
cls.topic.save()
cls.topic.tags.add(python_tag, js_tag)
def test_topics_main_page_rendered_topics(self):
response = self.client.get(
reverse("listing")
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "topics/sub_listing.html")
self.assertContains(response, "Looking for more?")
views.py
class AbstractListingPage(TemplateView):
template_name = "topics/sub_listing.html"
extra_content = {
'query_links': ['interesting', 'hot', 'week', 'month']
}
def get_context_data(self):
context = super().get_context_data()
context['topics'] = Topic.objects.all()
context['search_form'] = SearchForm()
context['login_form'] = UserLoginForm
return context
def post(self, request):
context = self.get_context_data()
form = context['login_form'](data=request.POST)
if form.is_valid():
resolver = resolve(request.path)
login(request, form.get_user())
if resolver.namespace:
url = f"{resolver.namespace}:{resolver.url_name}"
else:
url = resolver.url_name
return HttpResponseRedirect(
reverse(url)
)
return self.render_to_response(context)
class TopicsPage(AbstractListingPage):
def get(self, request):
pass
listing.html
{% extends 'index.html' %}
{% load static %}
{% block content %}
{% if not topics %}
<h1 class="topics_placeholder">"Whoops...no topics are being talked about"</h1>
<h2>Join the community...NOW!</h2>
{% else %}
{% for topic in topics %}
<ul class="topic_stats">
<li>{{ topic.answers.count }} answers</li>
<li>{{ topic.likes }} likes</li>
<li>{{ topic.views }} views</li>
</ul>
<div class="topic_wrapper">
<h1>{{ topic }}</h1>
<ul>
{% for tag in topic.tags.all %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
<p>{{ topic.posted }}</p>
<p>{{ topic.author }}</P>
</div>
{% endfor %}
{% endif %}
{% endblock content %}
{% block page_branch %}
{% endblock %}
<div class="question-header">
<button class="flex_item_btn button_widget red" type="button">
Ask Question
</button>
</div>
sub_listing.html
{% extends 'topics/listing.html' %}
{% load static %}
{% block page_branch %}
<h2>Looking for more? Browse the complete list of questions
or popular tags</h2>
{% endblock %}
The content at the bottom of listing.html is orphaned, not existing inside a block present in the parent template.
{% block page_branch %}
{% endblock %}
<div class="question-header">
<button class="flex_item_btn button_widget red" type="button">
Ask Question
</button>
</div>
Since listing.html extends index.html, it can only override blocks that exist in index.html. The above content must be put inside {% block content %} to be rendered at all. Then sub_listing.html's use of {% block page_branch %} will be rendered.
I want to make my blog like Facebook, where the option to create a post is in the same view as the list of posts.
I'm trying to include the CreateView template in my ListView, but it gives me this error:
Parameter "form" should contain a valid Django Form.
I also tried to use the same method in comments. Does this method work or is there another way?
This is the post_list.html template:
{% extends "posts/post_base.html" %}
{% load bootstrap4 %}
{% block post_content %}
<div class="col-md-6">
{% for post in post_list %}
{% include "posts/_post.html" %}
{% inclide 'posts/_create_post.html %}
{% endfor %}
</div>
{% endblock %}
This is the create_post.html template:
{% load bootstrap4 %}
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" name="" value="comment">
</form>
What I want to do is just include the create template inside list template. If this will not work, I just want to let the user create a new post on the same page as the list of posts like Facebook.
Within your ListView you need to add the form
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
ctx['form'] = myForm(self.request.POST or None)
return ctx
Then you should be able to use in the template
I'm trying to incorporate an template tag/inclusion tag into my sidebar for the site. The main section of the page updates properly when I put:
{% if user.is_authenticated %}
<h1> Hello {{ user.username }}
{% else %}
<h1> Hello </h1>
{% endif %}
When I try to use the same principle in my template tag/sidebar, it seems to ignore user.is_authenticated and will always show 'login' and 'register', when it should be just showing 'logout'.
The body of the html (main index page):
{% load Kappa_extras %}
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-2" id="side_section">
{% block sidebar %}
{% get_game_list %}
{% endblock %}
</div>
<!--Main section-->
<div class="col-sm-10" id="main_section">
{% block body %}
{% endblock %}
</div>
</div>
</div>
The get_game_list function from 'Kappa_extras':
from django import template
from Kappa.models import Game, Game_Page
from django.contrib.auth.models import User
register = template.Library()
#register.inclusion_tag('Kappa/sidebar.html')
def get_game_list():
return {'game_list': Game.objects.all()}
and the 'Kappa/sidebar.html':
<div id="side_default_list">
<ul class="nav">
<li>Kappa</li>
{% if user.is_authenticated %}
<li>Log Out</li>
{% else %}
<li>Log In</li>
<li>Register</li>
{% endif %}
</div>
I checked a few older inquires though none of them are working properly. I tried putting request into def get_game_list(request): but it just said did not receive value for the argument. How do I get the sidebar to update properly when user.is_authenticated?
You need to pass the user to your inclusion tag.
#register.inclusion_tag('Kappa/sidebar.html')
def get_game_list(user):
return {'game_list': Game.objects.all(), 'user': user}
Then in your template, call the tag with
{% get_game_list user %}
Alternatively, you can set takes_context=True in your inclusion tag, so that you can access the user from the template context.
#register.inclusion_tag('Kappa/sidebar.html', takes_context=True)
def get_game_list(context):
return {'game_list': Game.objects.all(), 'user': context['user']}
In this case, you don't need to pass the user to the template tag any more.
{% get_game_list %}
See the docs for more information and other examples.
I'm trying to have a form post redirect back to the page it was posted from. So my app has a detail page that displays information about a place, /arena/123, and on the same page there is a form that a user can enter some comments or ratings about the current place they are looking at. These are my view functions.
class DetailView(LoginRequiredMixin, generic.DetailView):
model = Arena
template_name = 'arenas/detail.html'
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
if 'form' not in context:
print 'no form in context'
context['form'] = RatingForm
return context
def rate(request):
if request.method == 'POST':
form = RatingForm(request.POST)
if form.is_valid():
# post was valid. go back to page and display data
return DetailView.as_view()(request(), form=RatingForm)
else:
# post was unsuccessful. go back to the page and display errors
print form.errors
return DetailView.as_view()(request(), form=form)
and my template
{% extends "base.html" %}
{% block content %}
<h1>{{ arena.address }}</h1>
<h1>{{ arena.address_2 }}</h1>
<h1>{{ arena.phone }}</h1>
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-error">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-error">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
<form action="/arenas/rate/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
<div>
{{comments}}
</div>
{% endblock %}
So what I'd like to do is after a user submits the form on the page I'd like to go back to the same detail page I was on and either have the data that user posted there shown in the comments section if it was successful or redirect back to the same page but with the form errors if it was unsuccessful. My problem is right now I don't know how to retain the url and form errors to display the detail page again. I don't think I can use return DetailView.as_view()(context(), form=form) because my DetailView doesn't have a post so I get a 405. Every example I see doesn't cover redirecting back to the same page. They usually just show a HttpResponseRedirect(/thanks) or something similar.
Also note. I don't want to post to /arena/123 because eventually that will be another feature to update information about the place so I didn't want to put the comments post on that url.
So I dug into some more Django examples and stumbled across SingleObjectMixin. This combined with a FormView seems to be what I want. The Django docs had an example that was 99.9 percent what I wanted here. Just be sure to read down to the better solution section.
I changed my views to this
class DetailView(LoginRequiredMixin, generic.DetailView):
model = Arena
template_name = 'arenas/detail.html'
def get_context_data(self, **kwargs):
print kwargs
context = super(DetailView, self).get_context_data(**kwargs)
context['form'] = RatingForm
return context
class RatingView(LoginRequiredMixin, detail.SingleObjectMixin, generic.FormView):
model = Arena
template_name = 'arenas/detail.html'
form_class = RatingForm
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(RatingView, self).post(request, *args, **kwargs)
def get_success_url(self):
print "successfully posted"
return reverse('arenas:detail', kwargs={'pk': self.object.pk})
Added a route for posting the form with /rate
urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$',
views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>\d+)/rate/$', views.RatingView.as_view(), name='rate')
)
and modified my template a bit to pass the id of the object to my route in the form post action
{% extends "base.html" %}
{% block content %}
<h1>{{ arena.address }}</h1>
<h1>{{ arena.address_2 }}</h1>
<h1>{{ arena.phone }}</h1>
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-error">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-error">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
<form action="/arenas/{{ arena.id }}/rate/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
<div>
{{comments}}
</div>
{% endblock %}
Now on an error the context is kept and the SingleObjectMixin/FormView combination keeps me on the same page to display the form errors and on a successful post it redirects to the detail page I want using the get_success_url to load the page again with the new information that was posted.
I'm trying to display the current lesson amount on the user profile page, but I cannot get it to show up.
Here is my models.py:
class LessonCount(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
lesson_current_amount = models.PositiveIntegerField(default=0, verbose_name='Current Number of Lessons')
def __unicode__(self):
return str(self.user.username)
views.py:
#login_required
def user_profile(request):
user = None
lessoncount = LessonCount.objects.all()
context = {
'user': user,
'lessoncount': lessoncount
}
return render(request, 'account/profile.html', context)
profile.html:
{% extends 'base.html' %}
{% block content %}
<h3 style="color: red;">{{ lessoncount.lesson_current_amount }}</h3>
<h3 style="color: red;">{{ request.user.username }}</h3>
{% endblock %}
Thanks in advance!
You need to loop through the objects:
{% for lesson in lessoncount %}
<h3 style="color: red;">{{ lesson.lesson_current_amount }}</h3>
{% endfor %}
Read more about for loops in the docs.
You need a LessonCount object, but LessonCount.objects.all() is a QuerySet.
Change that line to:
lessoncount = LessonCount.objects.get(user=request.user)