get_object_or_404 only for DetailView - python

Im new to programming, im working on a app to study a given topic, and after I read the topic, im showing some flashcards, but I want to show only the flashcards related to the topic, but I always get more then one Deck of flashcards, probably because I'm not getting the correct deck_id. Here is the code:
models.py:
class Topic(models.Model):
author = models.ForeignKey(
User, related_name="topic", on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=100)
body = RichTextUploadingField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(max_length=120)
class Meta:
ordering = ["title"]
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('topic:topic-detail', kwargs={
"topic_slug": self.slug,})
class Deck(models.Model):
deckTopic = models.ForeignKey(
Topic, null=True, blank=True, on_delete=models.CASCADE)
description = models.CharField(max_length=510, null=False, blank=True)
is_active = models.BooleanField(default=False)
def __str__(self):
return self.description
def get_number_of_cards(self):
'''
Returns the number of cards in the decks related card_set
'''
return self.card_set.count()
get_number_of_cards.short_description = 'Card Count'
class Card(models.Model):
parentDeck = models.ForeignKey(Deck, on_delete=models.CASCADE)
front = models.TextField()
back = models.TextField()
def __str__(self):
return self.front
def has_prev_card(self):
'''
Returns true if card is not thee first card in the deck.
'''
first_card_in_deck = self.parentDeck.card_set.first()
if self == first_card_in_deck:
return False
return True
def get_prev_card(self):
'''
Return previous card in deck
'''
return self.parentDeck.card_set.filter(id__lt=self.id).last()
def has_next_card(self):
'''
Returns true if card is not the last card in the deck.
'''
last_card_in_deck = self.parentDeck.card_set.last()
if self == last_card_in_deck:
return False
return True
def get_next_card(self):
'''
Return next card in deck
'''
return self.parentDeck.card_set.filter(id__gt=self.id).first()
views:
class TopicDetailView(DetailView):
model = Topic
def get_context_data(self, *args, **kwargs):
context = super(TopicDetailView,
self).get_context_data(*args, **kwargs)
# Here is where i want to get the Deck related to the topic, but get more then one:
deck_obj = get_object_or_404(Deck)
card_list = deck_obj.card_set.all()
card_obj = card_list.first()
if self.request.method == 'GET' and 'card' in self.request.GET:
card_obj = get_object_or_404(Card, id=self.request.GET['card'])
context['deck_obj'] = deck_obj
context['card_obj'] = card_obj
return context
topic_detail.html:
{% extends 'base.html' %}
{% block content %}
<div class="topic-title">
<h5 class="mb-0">
{{object.title}}
</h5>
</div>
<div class="topic-body">
<p class="mb-0">
{{object.body}}
</p>
</div>
<div class="topic-deck">
{% if card_obj %}
<div class="notecard">
<div class="notecard-nav text-center">
{% if card_obj.has_prev_card %}
<a href="{% url 'topic:viewDeck' deck_obj.id %}?card=
{{card_obj.get_prev_card.id}}">Prev</a>
{% endif %}
{% if card_obj.has_next_card %}
<a href="{% url 'topic:viewDeck' deck_obj.id %}?card=
{{card_obj.get_next_card.id}}">Next</a>
{% endif %}
</div>
<div class="notecard-front">
<p class="text-center">Front</p>
<p>{{card_obj.front}}</p>
</div>
<div class="notecard-back">
<p class="text-center">Back</p>
<p>{{card_obj.back}}</p>
</div>
</div>
{% else %}
<p>No card found.</p>
{% endif %}
</div>
{% endblock %}
urls.py:
app_name = 'topic'
urlpatterns = [
path('topic/<slug:topic_slug>/<deck_id>/',
TopicDetailView.as_view(), name='viewDeck'),
]
How can I show only the Deck related to de Topic detail?

Your call to get_object_or_404 is incomplete. You've provided the model class, but not the parameters to get the object with:
deck_obj = get_object_or_404(Deck, pk=self.kwargs['deck_id'])
self.kwargs, contains all keyword arguments from the url. In your case it will contain topic_slug and deck_id.
pk is a shortcut for whatever the primary key is on your model. This makes me not think about how I named it and is resistant to future name changes of the primary key field.
I don't have to worry about deck_id not being in kwargs, because if it's not the URL won't match and the view isn't called.
And there it is :)

Related

My article doesn't display in article_details

I want to show my articles but
My article doesn't display on article_details.html
This is what my site looks like. You can see only my article's title works.
My models.py:
class Article(models.Model):
title = models.CharField(max_length=50, verbose_name="Title")
mini_description = models.TextField(max_length=100, default='', verbose_name='Mini_description')
content = models.TextField(blank=True, verbose_name="Content")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Date of add")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Update date")
photo = models.ImageField(upload_to='photos/', verbose_name="Photo")
is_published = models.BooleanField(default=True, verbose_name="Is published")
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name="Category")
def get_absolute_url(self):
return reverse("article_details", kwargs={'pk': self.pk})
def __str__(self):
return self.title
def __repr__(self):
return f"Article(pk={self.pk}, title='{self.title}')"
class Meta:
verbose_name = "Article"
verbose_name_plural = "Articles"
ordering = ['-created_at']
My views.py:
class ArticleListByCategory(ListView):
model = Article
context_object_name = 'articles'
template_name = 'blog/details.html'
def get_queryset(self):
sort_filed = self.request.GET.get('sorter')
articles = Article.objects.filter(category_id=self.kwargs['pk'])
if sort_filed:
articles = articles.order_by(sort_filed)
return articles
class ArticleDetails(ArticleListByCategory):
model = Article
context_object_name = 'article'
template_name = 'blog/article_details.html'
def get_queryset(self):
article = Article.objects.filter(pk=self.kwargs['pk'], is_published=True).select_related('category')
return article
def get_context_data(self, **kwargs):
context = super().get_context_data()
article = Article.objects.get(pk=self.kwargs['pk'])
context['title'] = f"Article: {article.title}"
return context
My article_details.html:
{% extends "base.html" %}
{% load static %}
{% block style %}
<link href="{% static 'blog/css/main.css' %}" rel="stylesheet">
{% endblock style %}
{% block title %}
{{ title }}
{% endblock %}
{% block main %}
<section class="articles" id="articles">
<div class="card_details">
<h1>{{ article.title }}</h1>
<p><em>{{ article.mini_description }}
</em>
</p>
<img src="">
<div class="description">
<p>
{{ article.content }}
</p>
</div>
<div class="card-footer text-muted">
{{ article.created_at| timesince }} ago
</div>
</div>
</section>
{% endblock main %}
I don't know why my article doesn't display.
What I'd suggest here is using the Django Generic View called DetailView. For example, you could do the following:
from django.views.generic import DetailView
class ArticleDetails(DetailView):
context_object_name = 'article'
template_name = 'blog/article_details.html'
# Using the get_object method here, not get_queryset.
def get_object(self, queryset=None):
article = Article.objects.filter(pk=self.kwargs['pk'], is_published=True).select_related('category')
return article
Ideally, that should work.

django iterate over a list that is an item of a queryset

I have a list that is generated by a method on one of my models. On the home page it works wonderfully, however when I go to a detail view of one project I can access all the parts of that project as they are direct fields of the Model, but I can't access the items in the list.
Model:
class Project(models.Model):
date_published = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
area = models.ForeignKey(Area, on_delete=models.PROTECT)
title = models.CharField(max_length=128, unique=True)
slug = models.SlugField(max_length=64)
summary = models.CharField(max_length=256)
others = models.CharField(max_length=128, blank=True)
deadline = models.DateField(null=True, blank=True)
priority = models.ForeignKey(Priority, on_delete=models.PROTECT)
closed = models.DateTimeField(null=True, blank=True)
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Project, self).save(*args, **kwargs)
#property
def updates(self):
updates = []
sequence_id = 1
categories = set(self.update_set.all().values_list(
'category__id', flat=True))
for cat_id in categories:
a = Update.objects.filter(
project=self, category__id=cat_id).order_by('added').last()
if cat_id == sequence_id:
updates.append(a)
else:
for i in range(cat_id - sequence_id):
updates.append('null')
updates.append(a)
sequence_id = cat_id
sequence_id += 1
return updates
class Update(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
category = models.ForeignKey(UpdateCategory, on_delete=models.PROTECT)
update = models.TextField(max_length=240, blank=True)
added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.update
The view is simple:
class ProjectDetailView(DetailView):
template_name = 'project_portal/project_detail.html'
queryset = Project.objects.all()
and here is the dynamic url that I am using:
path('project/<int:pk>/',
ProjectDetailView.as_view(), name='project_detail'),
As for the template, I'm lost, here is one of the things I have tried:
<!DOCTYPE html>
{% extends "project_portal/base.html" %}
{% block home %}
<div id="main">
<div id="content">
<div>
<h1>{{ object.title }}</h1>
<h1>hello</h1>
{% if object_list %}
{% for item in updates %}
<p>{{ item }}</p>
{% endfor %}
{% else %}
<h2>No records found for this project</h2>
{% endif %}
</div>
</div>
</div>
{% endblock %}
What do I need to do to access the "updates" list that gets generated?
update is a property of the model instance, you need to access it from there like any other attribute. Also note, there is no object_list in a detail view.
<div>
<h1>{{ object.title }}</h1>
<h1>hello</h1>
{% for item in object.updates %}
<p>{{ item }}</p>
{% endfor %}
</div>

Filter queries inside templates django

models:
class Level(models.Model):
number = models.IntegerField()
badge = models.ImageField()
locked_badge = models.ImageField()
timestamp =
models.DateTimeField(auto_now_add=True,auto_now=False,blank=True,
null=True)
unlock = models.CharField(max_length=10,default="A")
def __str__(self):
return str(self.number)
def get_absolute_url(self):
return reverse('student:level-detail', kwargs={'pk': self.pk})
class ToDo(models.Model):
level = models.ForeignKey(Level, on_delete=models.CASCADE)
name = models.CharField(max_length=150)
description = models.TextField()
timestamp =
models.DateTimeField(auto_now_add=True,auto_now=False,blank=True,
null=True)
def __str__(self):
return self.name
class Task(models.Model):
level = models.ForeignKey(Level, on_delete=models.CASCADE)
todo = models.ForeignKey(ToDo, on_delete=models.CASCADE)
student = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
content = models.TextField()
timestamp = models.TimeField(auto_now=True)
datestamp = models.DateField( auto_now=True)
like =
models.ManyToManyField(User,related_name='user_likes',blank=True)
is_verified=models.BooleanField(default=False,blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('student:dashboard')
objects = PostManager()
#property
def comments(self):
instance = self
qs = Comment.objects.filter_by_instance(instance)
return qs
#property
def get_content_type(self):
instance = self
content_type =
ContentType.objects.get_for_model(instance.__class__)
return content_type
Template:
{% for level in levels %}
{% if tasks.filter(student=student,level=level).count ==
todos.filter(level=level).count %}
<li>
<img src="{{ level.badge.url }}" alt="" />
</li>
{% else %}
<li>
<a href="javascript:;"><img src="{{
level.locked_badge.url }}" alt="" /></a>
</li>
{% endif %}
{% endfor %}
sample view:
#login_required(login_url='/account/login/')
def StudentPublicProfile(request,pk=None):
student = User.objects.get(pk=pk)
levels = Level.objects.all()
todos=ToDo.objects.all()
tasks=Task.objects.all()
context={
'student': student,
'levels' : levels,
'tasks' : tasks,
'todos' : todos,
}
return render(request, 'student_public_profile2.html', context)
This might need a little explanation. I have 3 models- Level Todo and Task. Each level contains many todos.When a todo is completed,ie,when a todo form is submitted,It gets saved in the Task model.The form uses model form,so basically task is the set of completed todos of the user of a level. I want to display badges for the levels based on a condition which is : If all the todos of a level is completed,it should display one type of badge,if not it should display a locked badge!How do i go about this?
I came accross template tags..I make my custom tags like this:
#register.filter
def completed(tasks,student, level):
return tasks.filter(student=student,level=level).count()
#register.filter
def alltodos(todo, level):
return todo.filter(level=level).count()
How to call function from template for this to work correctly?
{% for level in levels %}
{% if tasks|completed:tasks,student,level ==
todos|alltodos:todos,level %}
<li>
<a href="javascript:;"><img src="{{
level.badge.url }}" alt="" /></a>
</li>
{% else %}
<li>
<a href="javascript:;"><img src="{{
level.locked_badge.url }}" alt="" /></a>
</li>
{% endif %}
{% endfor %}

django, does not display the avatar in the comments

I extended standart django user model by one-to-one field. Made news block, and added comments there. In comments i cant display user avatar from UserProfile model, cause dont understand how correctly ask database for it D;. Here my code:
main/models.py
from django.db import models
from django.utils import timezone
from django.contrib import auth
from django.contrib.auth.forms import User
from django.shortcuts import render, redirect
from profiles.models import UserProfile
# Create your models here.
class News(models.Model):
news_title = models.CharField(max_length=250)
news_body = models.TextField(max_length=2000, blank=True)
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
image = models.FileField()
published_date = models.DateTimeField(blank=True, null=True)
def publish(self, request):
self.published_date = timezone.now()
self.save()
return redirect('index')
def __str__(self):
return self.news_title
class Comment(models.Model):
news = models.ForeignKey('main.News', related_name='comments',
on_delete=models.CASCADE)
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
profiles/models.py
class UserProfile(models.Model):
JEW_CHOICE = (
('Да', 'Да'),
('Нет', 'Нет'),
)
MF_CHOICE = (
('М', 'М'),
('Ж', 'Ж')
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
country = models.CharField(max_length=100, default='', blank=True)
city = models.CharField(max_length=100, default='', blank=True)
description = models.CharField(max_length=500, default='', blank=True)
website = models.URLField(default='', blank=True)
avatar = models.ImageField(default='', blank=True)
gender = models.CharField(max_length=100, choices = MF_CHOICE, default = 'М', blank=True)
jew = models.CharField(max_length=100, choices = JEW_CHOICE, default = 'Да', blank=True)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.get_or_create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
#property
def avatar_url(self):
if self.avatar and hasattr(self.avatar, 'url'):
return self.avatar.url
main/views.py (meme_detail is the view, where should be comments with user info)
def meme_detail(request, pk):
news = get_object_or_404(News, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.author = request.user
comment.news = news
comment.save()
return redirect('main:meme_detail', pk=news.pk)
else:
form = CommentForm()
return render(request, 'main/meme_detail.html', {'news': news, 'form': form,})
meme_detail.html (news template with comments)
{% extends 'main/base.html' %}
{% block body %}
<h2>{{news.news_title}}</h2>
<img src='{{news.image.url}}' name='image' width='500px;'><br>
{{news.news_body}} <br><br>
<div class="row">
<div class="col">
<b>{{news.author}}</b>
</div>
<div class="col">
<i>{{news.published_date}}</i>
</div>
</div>
<div class="underline"></div>
<h3>Комментарии:</h3><br>
{% for comment in news.comments.all %}
<div class="row">
<div class="col-"><img src="{{ userprofile.avatar.url }}" alt="user-avatar" width="100px" height="100px"></div>
<div class="col">{{ comment.text }}</div>
</div>
<div class="row">
<div class="col"><strong>{{ comment.author }}</strong></div>
<div class="col">{{ comment.created_date}}</div>
</div>
<div class="underline"></div>
<br>
{% empty %}
<p>Пока ещё нет комментариев :(</p>
{% endfor %}
{% if request.user.is_authenticated %}
<div class="row">
<form method="POST">
{% csrf_token %}
{{form.text}}<br><br>
<a class="btn btn-success" href="{% url 'main:meme_detail' pk=news.pk %}"><button class='btn btn-success'>Добавить коммент! </button></a>
</form>
</div>
{% else %}
<i>Вы не можете писать комментарии, необходимо зарегистрироваться!</i>
{% endif %}
{% endblock %}
So, in this template, where "userprofile.avatar.url" should be object reference on User avatar. I tryed a lot of things, but it always the same:not displaying
You should do:
<img src="{{ comment.author.userprofile.avatar.url }}" alt="user-avatar" width="100px" height="100px">
Your comment has a foreign key to User (author), and User has a one to one field to UserProfile, which is the one that has the avatar attribute.
Also another tip:
You shouldn't really reduce the image in CSS (width: 100px; height: 100px;), but instead use a tool that allows you to create thumbnails of images. I use sorl-thumbnail and can't recommend it enough.
The reason is that if every user uploads a 1000x1000 image, you are downloading those big images that you don't really need, hence your site will be slower.
Maybe you should try accesing the User object in the template, not the Userprofile.
<img src="{{ user.userprofile.avatar.url }}" ...

Calling a method from template

I seem to have a problem with calling a method from a django template. Given the model:
class Gallery(models.Model):
class Meta:
verbose_name_plural = "Galerie"
def photo_count(self):
return self.photo_set.count()
def random_image(self):
return self.photo_set.get(id=random.randint(1,self.photo_count()))
title = models.CharField(max_length=50)
imgur_id = models.CharField(max_length=15)
date_created = models.DateTimeField('Data utworzenia', auto_now=True)
is_active = models.BooleanField()
def __unicode__(self):
return self.title
where Gallery is foreignKey for Photo
views.py:
def index(request):
galleries = Gallery.objects.get(is_active=True)
if galleries.count() is 0:
messages.warning(request, "Niestety, żadna galeria nie jest aktywna, zaglądnij niebawem!")
return redirect(reverse(home))
elif galleries.count() is 1:
return render(request, 'gallery/home.html', {'gallery': galleries})
else:
return render(request, 'gallery/index.html', {'galleries': galleries})
I want to do this in template:
{% for gallery in galleries %}
{{ gallery.random_image }} <br />
{% endfor %}
The result I get is:
[ Photo object ]
[]
[]
[]
But when i call
{% for gallery in galleries %}
{{ gallery.photo_count }} <br />
{% endfor %}
The result is correct.
Why does this not work?
In my opinion you are complicated a little bit.
class Gallery(models.Model):
class Meta:
verbose_name_plural = "Galerie"
def random_image(self):
return self.photo_set.order_by('?')[0] if self.photo_set.count() else None
title = models.CharField(max_length=50)
imgur_id = models.CharField(max_length=15)
date_created = models.DateTimeField('Data utworzenia', auto_now=True)
is_active = models.BooleanField()
def __unicode__(self):
return self.title
and in the template,
{% for gallery in galleries %}
{% if gallery.random_image %}
{{ gallery.random_image }} <br />
{% else %}
No images found.
{% endif %}
{% endfor %}
Read more on order_by here
Note that ? could prove slightly heavy. If so, get thelist of ids of the photo_set associated with this gallery object, and get a random id from this list, rather than doing a
random.randint(1,self.photo_count())
Add #property to your fonction.
#property
def photo_count(self):
return self.photo_set.count()

Categories