I am trying to display data from a model inside my post.html which worked before but now dosen't for some reason I can't figure out.
urls.py
urlpatterns = [
path('', ListView.as_view(
queryset=Tutorials.objects.all().order_by("-date")[:25],
template_name="tutorials/blog.html"
)),
path('<int:pk>', DetailView.as_view(
model=Tutorials,
template_name="tutorials/post.html")),
]
blog.html
{% block python %}
{% for Tutorials in object_list %}
{% if Tutorials.categories == "pythonbasics" %}
<h1>{{ Tutorials.title }}</h1>
<br>
Created On : {{ Tutorials.date|date:"Y-m-d" }}
{% endif %}
{% endfor %}
{% endblock %}
post.html
{% block content %}
<h3>{{ Tutorials.title }}</h3>
<h6> on {{ Tutorials.datetime }}</h6>
<div class = "code">
{{ Tutorials.content|linebreaks }}
</div>
{% endblock %}
You should use object name in template:
{% block content %}
<h3>{{ object.title }}</h3>
<h6> on {{ object.datetime }}</h6>
<div class = "code">
{{ object.content|linebreaks }}
</div>
{% endblock %}
Or if you want to use Tutorial variable, you need to pass context_object_name=Tutorials to the view:
path('<int:pk>', DetailView.as_view(
model=Tutorials,
template_name="tutorials/post.html",
context_object_name='Tutorials')),
plus for #neverwalkaloner's answer.
The reason why you can use object or context_object_name is cause you inherit DetailView.
In DetailView, it has get_object() method.
def get_object(self, queryset=None):
"""
Return the object the view is displaying.
Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
Subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
It will return model object, and also you can override this method.
And in get() method in DetailView,
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
As you see, it define self.object to self.get_object() so you can use self.object inside DetailView.
Finally, you can use object in your template because of get_context_data().
DetailView basically add 'object' to context, so you can use it.
def get_context_data(self, **kwargs):
"""Insert the single object into the context dict."""
context = {}
if self.object:
context['object'] = self.object
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
context.update(kwargs)
return super().get_context_data(**context)
It's true that CBV has little bit learning curve, but when you see django source code and read about docs, it's easy to follow.
+I highly recommend ccbv.co.kr - you can see which view and mixin that CBV inherit, and it's methods too.
{% block content %}
{{ object.title }}
<h6> on {{ object.datetime }}</h6>
<div class = "code">
{{ object.content|linebreak }}
</div>
{% endblock %}
Related
I have created a custom mixin GetVerboseNameMixin in order to get the verbose name of model fields, and then display these in my html template using a DetailView. However, whenever I try and render the list of verbose names nothing is returned, and I cannot work out why.
Mixin.py:
class GetVerboseNameMixin:
def get_verbose_name(model, fields=[]):
verbose_names = []
for field in fields:
verbose_names.append(str(model._meta.get_field(field)))
return verbose_names
View:
class ShowProfileView(GetVerboseNameMixin, DetailView):
model = Profile
template_name = 'profiles/user_profile.html'
verbose_model_fields = GetVerboseNameMixin.get_verbose_name(model=Profile, fields=['first_name', 'surname', 'date_of_birth', 'phone_number', 'bio', 'gender', 'emergency_contact_name', 'emergency_contact_number'])
def get_context_data(self, *args, **kwargs):
context = super(ShowProfileView, self).get_context_data(*args, **kwargs)
user_profile = get_object_or_404(Profile, id=self.kwargs['pk'])
context["user_profile"] = user_profile
return context
def get_object(self, *args, **kwargs):
obj = Profile.objects.filter(id=self.kwargs['pk']).values('first_name', 'surname', 'date_of_birth', 'phone_number', 'bio', 'gender', 'emergency_contact_name', 'emergency_contact_number') # list of dictionaries
object = obj[0]
return object
Html template:
{% extends "base.html" %}
{% block content %}
<h1>Profile</h1>
<br/><br/>
{% csrf_token %}
<ul>
{% for v in object.values %}
{% for field_name in verbose_model_fields %}
<p>{{field_name}}: {{ v }}</p>
{% endfor %}
{% endfor %}
</ul>
<a href='{% url "profiles:edit_profile" pk=user.profile.id %}'>Edit Profile</a>
{% endblock %}
Even if I just render:
{{ verbose_model_fields }}
In my html file nothing is being displayed. This leads me to think maybe the problem is in my mixin, or perhaps the function is not being called properly?
I do not get how verbose_model_fields is getting passed to the template from the view, and also, I did not find any reference in DetailView documentation. I assume you want to have this custom implementation, if so, then you need to pass along this parameter via context:
class ShowProfileView(GetVerboseNameMixin, DetailView):
...
def get_context_data(self, *args, **kwargs):
context = super(ShowProfileView, self).get_context_data(*args, **kwargs)
user_profile = get_object_or_404(Profile, id=self.kwargs['pk'])
context["user_profile"] = user_profile # redundant implementation, you can get this value by `object` context variable in template
context["verbose_model_fields"] = self.verbose_model_fields # or just call self.get_verbose_name(...) method
return context
Also in this solution I have marked redundant implementation that you do not need to re-implement how to get object, because it is already taken care by DetailView.
In my blog app I want to allow unkown users to see articles, but I also want to allow logged users to see in the same page (somewhere else) their own articles; something like:
YOUR ARTICLES: list (only if user is logged)
ALL ARTICLES: list
Note that I need to show articles based on the user logged in because the url must be this:
path('<int:user_id>/', views.IndexView.as_view(), name='index'),
index.html:
{% if user.is_authenticated %}
Your articles:
<div class="container py-5">
{% if article_list %}
{% for article in article_list %}
<div class="container">
<div class="row">
<div class="col">
{{article.author}}
</div>
<div class="col">
{{article.title}}
</div>
<div class="col">
{{article.pub_date}}
</div>
<a href=" {% url 'blog_app:detail' user_id = user.id %} ">
<div class="col">
Open article
</div>
</a>
</div>
</div>
{% endfor %}
{% else %}
<b>No articles!</b>
{% endif %}
</div>
{% endif %}
views.py:
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
context_object_name = 'article_list'
#return articles of a particular author
def get_queryset(self):
self.article = get_object_or_404(Article, author_id=self.kwargs['user_id'])
return Article.objects.filter(
author = self.article.author
)
My question is: How can I get from IndexView two different querysets? One with all articles and one with articles filtered by author?
Bonus question:
Can I allow unkown users to reach the articles page if the url needs to specify the user id?
After answers, this is one possible correct solution (don't focus on year and month filters, I added them but obviusly aren't related to the solution):
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_article_list'] = Article.objects.all()
context['author_article_list'] = Article.objects.filter(
pub_date__year = self.kwargs['year'],
pub_date__month = self.kwargs['month'],
author = self.kwargs['user_id']
).order_by('-pub_date')
return context
In django templates I used these context names to iter articles:
Author articles:
{% if user.is_authenticated %}
{% if author_article_list %}
{% for article in author_article_list %}
...
{% endfor %}
{% endif %}
{% endif %}
All articles:
{% if all_article_list %}
{% for article in all_article_list %}
...
{% endfor %}
{% endif %}
You need to specify:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_articles'] = Article.objects.all()
return context
Then you can also use an if statement in th template, to check if the {{all_articles}} exists.
"Can I allow unkown users to reach the articles page if the url needs to specify the user id?"
Unauthenticated users do not have an ID, this will result in an error. If you want users to go to the author of the current article being viewed, wouldn't it be {{article.author.id}}? (Not sure if this is what you want.)
Just use a standard context. Add this metod to you view (changing names, obviously):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book_list'] = Book.objects.all()
return context
Bonus answer:
Well, anyone can enter every instance of such view. The only thing would be to change manually number in the browser, i.e. anyone can access this link:
http://example.com/1/
But if someone is not authenticated, that link: <a href=" {% url 'blog_app:detail' user_id = user.id %} "> would raise error, but of course cause of {% if user.is_authenticated %} it's not rendered anyway.
You need to set proper permissions to your view.
I think you can also override the get_queryset() method according to different conditions, so:
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
context_object_name = 'article_list'
def get_queryset(self):
qs=super().get_queryset()
if self.request.user.is_authenticated:
article=get_object_or_404(Article,author_id=self.kwargs['user_id'])
return qs.filter(author=article.author) #filtered queryset
else:
return qs #default queryset
I'm struggling to set an initial value in a form instance based on the URL parameter in Django 3.0.
I have a Claim model:
# models.py
class Claim(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
text = models.TextField()
date_added = models.DateTimeField(default=timezone.now)
member = models.ForeignKey(User, on_delete=models.CASCADE)
I have a NewClaimForm based on ModelForm:
# forms.py
class NewClaimForm(forms.ModelForm):
class Meta:
model = Claim
fields = ['product', 'text']
I have a NewClaimView based on CreateView:
# views.py
class NewClaimView(LoginRequiredMixin, CreateView):
model = Claim
form_class = NewClaimForm
template_name = 'portal/new_claim.html'
def form_valid(self, form):
form.instance.member = self.request.user
return super(NewClaimView, self).form_valid(form)
And using the following template fragment on the index page...
# index.html
<div class="card-deck">
{% for product in products %}
<div class="card text-center">
<div class="card-header">
<h5 class="card-title text-primary">{{ product }}</h5>
</div>
<div class="card-body">
<ol class="card-text text-left">
<li>Fill in the {{ product }} form</li>
<li>Attach your medical records</li>
<li>Get your claim reviewed within 48 hours</li>
</ol>
Online Form
</div>
</div>
{% endfor %}
</div>
...I pass the product_id parameter to the URL:
# urls.py
app_name = 'portal'
urlpatterns = [
path('new_claim/<int:product_id>/', NewClaimView.as_view(), name='new_claim_product'),
]
And lastly, this is what my new_claim template looks like:
# new_claim.html
{% extends "portal/base.html" %}
{% load bootstrap4 %}
{% block content %}
<p>Submit a new claim</p>
<form action="{% url 'portal:new_claim' %}" method='post' class="form" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button name="submit">Submit claim</button>
{% endbuttons %}
</form>
{% endblock content %}
I would like to now use the product_id to set the initial value of the product field in the form instance according to product_id. How can I achieve this?
Figured out how to do this by modifying the get method for my class-based view:
# views.py
class NewClaimView(LoginRequiredMixin, CreateView):
model = Claim
form_class = NewClaimForm
template_name = 'portal/new_claim.html'
def get(self, request, product_id=None, *args, **kwargs):
form = self.form_class(initial={'product': product_id})
return render(request, self.template_name, {'form': form})
def form_valid(self, form):
form.instance.member = self.request.user
return super(NewClaimView, self).form_valid(form)
Here's a link to the relevant documentation.
Is this an optimal way to solve this?
Do I need the *args and **kwargs in the modified get method? I'm not using them in my code but perhaps it would be useful to keep them there for other purposes in the future?
My html
{% if post.is_liked %}
<i class="fa fa-check" aria-hidden="true"></i>
{% else %}
<i class="fa fa-times" aria-hidden="true"></i>
{% endif %}
My views.py
class PostListView(ListView):
queryset = Post.objects.filter(created__range=['2020-03-01', '2020-03-31'])
template_name = 'main/problems.html'
context_object_name = 'posts'
ordering = ['-created']
def get_liked(self):
post = self.get_object()
user = get_object_or_404(User, username=post.kwargs.get('username'))
if post.likes.filter(username=user).exists():
post.annotate(is_liked=True)
else:
post.annotate(is_liked=False)
Even if I set both conditionals to return true my html does not read is_liked as true.
Your for loop will simply run when you construct the class, and furthermore you will simply define a function (multiple times), not execute the function. Finally note that this should alter the attribute of a Post, not just a general object.
You can annotate your queryset with:
from django.db.models import Exists, OuterRef
from app.models import Post, Like
class PostListView(ListView):
model = Post
template_name = 'main/problems.html'
context_object_name = 'posts'
ordering = ['-created']
def get_queryset(self, *args, **kwargs):
Post.objects.filter(
created__range=['2020-03-01', '2020-03-31']
).annotate(
is_liked=Exists(Like.objects.filter(
user_id=self.request.user.pk, post_id=OuterRef('pk')
))
)
The user_id and post_id might have different names, depending on how you constructed your Like model.
Then in the template, you can check the is_liked attribute of your Post objects:
{% for post in posts %}
{% if post.is_liked %}
…
{% else %}
…
{% endif %}
{% endfor %}
The code below paginates the queryset but how can I paginate context['guser1']
class AuthorList(ListView):
template_name = 'authorList.html'
paginate_by = 10
queryset = Author.objects.order_by('date')
def get_context_data(self, **kwargs):
context = super(AuthorList, self).get_context_data(**kwargs)
context['guser1'] = Author.objects.order_by('date')
return context
You'll have to use a Paginator object like you would in a view. Here are the docs.
The paginator for the view can be accessed under the context variable page_obj in your template. The page number is passed as a GET parameter in the url, page. Here's a simple example:
{% if page_obj.has_previous %}
Previous page
{% endif %}
{% if page_obj.has_next %}
Next page
{% endif %}