Django template for loop iteration by distinct foreign key - python

In my app, I have 3 models: Issue, Series, and Character. The Issue model has a Series ForeignKey, and a Character ManyToManyField. Here they are simplified:
class Character(models.Model):
name = models.CharField('Character name', max_length=200)
desc = models.TextField('Description', max_length=500)
class Series(models.Model):
name = models.CharField('Series name', max_length=200)
desc = models.TextField('Description', max_length=500)
class Issue(models.Model):
series = models.ForeignKey(Series, on_delete=models.CASCADE, blank=True)
name = models.CharField('Issue name', max_length=200)
number = models.PositiveSmallIntegerField('Issue number')
date = models.DateField('Cover date')
desc = models.TextField('Description', max_length=500)
characters = models.ManyToManyField(Character, blank=True)
cover = models.FilePathField('Cover file path', path="media/images/covers")
I have a Character template that displays information about the character. I want to also display Issues the Character is in, sorted by Series.
{% extends "app/base.html" %}
{% block page-title %}{{ character.name }}{% endblock page-title %}
{% block content %}
<div class="description">
<p>{{ character.desc }}</p>
</div>
<div class="issues">
<h3>Issues</h3>
{% for series in character.issue_set.all %}
<div>
{{ series.name }}
<ul>
{% for issue in character.issue_set.all %}
{% if issue.series.name == series.name %}
<li>
<img src="/{{ issue.cover }}" alt = "{{ series.name }}" >
<p>Issue #{{ issue.number }}</p>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endblock content %}
Obviously, the way this currently formats is that for every issue in the set, it outputs the series title, and then each issue in the set.
<div class="issues">
<h3>Issues</h3>
<div>
Series 1
<ul>
<li>
<img src="/media/images/covers/01.jpg" alt="Series 1">
<p>Issue #1</p>
</li>
<li>
<img src="/media/images/covers/02.jpg" alt="Series 1">
<p>Issue #2</p>
</li>
</ul>
</div>
<div>
Series 1
<ul>
<li>
<img src="/media/images/covers/01.jpg" alt="Series 1">
<p>Issue #1</p>
</li>
<li>
<img src="/media/images/covers/02.jpg" alt="Series 1">
<p>Issue #2</p>
</li>
</ul>
</div>
</div>
Here's what I would like to see:
<div class="issues">
<h3>Issues</h3>
<div>
Series 1
<ul>
<li>
<img src="/media/images/covers/01.jpg" alt="Series 1">
<p>Issue #1</p>
</li>
<li>
<img src="/media/images/covers/02.jpg" alt="Series 1">
<p>Issue #2</p>
</li>
</ul>
</div>
</div>
I've researched quite a bit on templating, and I'm not seeing a way to get a listing based on distinct values. I've also tried creating a new set in my Character or Issue model that I could use to replace issue_set.all, but I have yet to get it working.
EDIT: Upon request of marcusshep, the Character view is using the generic DetailView:
class CharacterView(generic.DetailView):
model = Character
template_name = 'app/character.html'

I would use a function based view rather than a class based generic view. Reason being that your required behavior is going beyond something generic.
In your function you can build the queryset you desire instead of having to fight with the one provided by generic.DetailView.
def my_view(request, *args, **kwargs):
character = Character.objects.get(id=request.GET.get("id", None))
issues = character.issue_set.all().order_by("series__name")
return render(request, 'app/character.html', {"issues": issues})
Alternatively, you can use what you already have and override the DetailView's get_queryset() method.
class CharacterView(generic.DetailView):
model = Character
template_name = 'app/character.html'
def get_queryset():
# return correct queryset
The biggest problem though is that there will be more aspects that will need to use this set. For instance, I'll be adding Creators, Story Arcs, etc. they will have their own pages and will need to display related issues, sorted by series as well. It would be nice to have a solution that can be used by any of these templates without much code re-use.
This is a very common problem in all areas of programming. A very simple way to solve this would be to isolate the logic in one function and call that function whenever you need it.
def my_issues_query():
# find the objects you need
def my_view(request, *args, **kwargs):
issues = my_issues_query()
You can also take advantage of pythons decorator functions. (Which is my favorite approach.)
def has_issues(view_function):
def get_issues(request, *args, **kwargs):
# find all the issues you need here
# you'll only need to write this logic once.
issues = Issues.objects.filter(...)
return issues
return get_issues
#has_issues
def my_view(request, *args, **kwargs):
# this functions namespace now contains
# the variable `issues`.
# which allows for the use of the query ie.
return render(
request,
"my_templates/template.html",
{"issues":issue}
)

Related

Transferring user input from one page to another

I am making a website that allows students to find upcoming study sessions for their courses. I am doing this in Django and HTML. A student uploads their courses to the site and they are shown on the courses page as buttons (ex. CS 101 - Intro to CS). When a student clicks on one of their courses (button), it is supposed to bring them to a page that shows available study sessions for that course. I am stuck because I do not know how to properly filter the available study sessions on the next page based on which course is clicked. Is there a way to store the info of the course as a variable so when the button is clicked I can use that variable to filter the results? EDIT: I have made these changes and now I am getting a ValueError too many values to unpack expected 2. I am almost certain it is happening in my views.
Here is the page that shows a user's courses:
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
<div class="row">
{% if courses_list %}
{% for course in courses_list %}
<a type="button" class="btn btn-outline-secondary" href="{% url 'study:course-session'%}" >{{ course.subject }} {{ course.number}}-{{course.name}} </a>
<br><br><br>
{% endfor %}
{% else %}
<p class="text-center">You have not added any courses yet!</p>
{% endif %}
</div>
</div>
And here is the page that I am trying to filter the list of study sessions (which have a field course that is a ForeignKey to the Courses model):
<h1><center>Upcoming Study Sessions</center></h1>
<div>
<a class="btn btn-success" style="position:absolute; margin-right:2px; top:15%; right:0;" href="{% url 'study:courses' %}" role="button" >Back to My Courses</a>
</div>
<br><br>
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
<div class="row">
<button type="button" class="btn btn-outline-secondary" >Date/Time: {{ session.date }} <br> Location: {{ session.location }} </button>
<br><br><br>
</div>
</div>
View for the template:
def CourseSessionView(request, course_pk):
course_wanted = Course.objects.get(id=course_pk)
try:
return Study.objects.filter(course=course_wanted)
except:
return messages.error(request, 'There are no upcoming study sessions at this time for the requested course.')
Model for course and session:
class Course(models.Model):
SUBJECT_CHOICES = [
('AAS', 'AAS')
]
subject = models.CharField(
max_length=4, choices=SUBJECT_CHOICES, default='')
number = models.PositiveSmallIntegerField(
validators=[MaxValueValidator(9999)], default=0)
name = models.CharField(max_length=100, default='')
roster = models.ManyToManyField(
Student, blank=True, related_name="courses")
# Use [Student object].courses.all() to see all of a student's courses
def __str__(self):
return f"{self.subject} {self.number} - {self.name}"
class Study(models.Model):
organizer = models.ForeignKey(Student, on_delete=models.CASCADE)
date = models.DateTimeField()
# Use [Student object].studies.all() to see all of a student's study sessions
attendees = models.ManyToManyField(Student, related_name="studies")
location = models.CharField(max_length=30)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
def __str__(self):
return f"{self.date} - {self.location}"
Url:
path('<int:course_pk>/sessions/',
views.CourseSessionView, name='course-session')
Note: The function based views' name doesn't require to be in PascalCase as in your case, it should be in snake_case.
The page that show the user's courses, there you need to pk of courses:
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
<div class="row">
{% if courses_list %}
{% for course in courses_list %}
<a type="button" class="btn btn-outline-secondary" href="{% url 'study:course-session' course.pk %}" >{{ course.subject }} {{ course.number}}-{{course.name}} </a>
<br><br><br>
{% endfor %}
{% else %}
<p class="text-center">You have not added any courses yet!</p>
{% endif %}
</div>
</div>
Your view for the template, i am defining it in snake_case, since its recommended way.
def course_session(request, course_pk):
course_wanted = Course.objects.get(id=course_pk)
study_courses=''
try:
study_courses= Study.objects.filter(course=course_wanted)
except:
messages.error(request, 'There are no upcoming study sessions at this time for the requested course.')
else:
return render(request,'anyfolder/anyfile.html',{'study_courses':study_courses})
return render(request,'anyfolder/anyfile.html') #then it will show only your error message.
Your url in urls.py be like:
path('any_route_name/<int:course_pk>/', views.course_session, name='course_session')
Note: Never forget to pass / at the end of your url or route_name.
Then, in your any template file you can access it and run loop:
{% for study in study_courses %}
{{study.organizer}},{{study.date}}
{% endfor %}
Then, you can access all its properties, and take benefit of ManyToOne relation.
This is going to be a very general type of answer since you are not providing your models or your views, but I think the idea would be the following.
First, in your template you can pass a parameter for the course number in the url:
your_template.html
<a class="btn btn-outline-secondary"
href="{% url 'study:course-session' course.pk %}">
{{ course.subject }} {{ course.number}}-{{course.name}}
</a>
Then in your view you can access that value, and from it get the course:
views.py
def the_view_name(request, course_pk):
# Here you now have access to the course's primary key, pk, so you can get the
# course and filter the study sessions by that course, etc...
You will need to modify the urls.py so the view can accept this new parameter:
urls.py
path('the_view_name/<int:course_pk>', views.the_view_name, name='the_view_name'),
EDIT
Make the following changes:
First to your views.py:
def CourseSessionView(request, course_pk):
try:
course_wanted = Course.objects.get(id=course_pk)
except:
return messages.error(request, 'course not found')
study_sessions = Study.objects.filter(course=course_wanted)
if study_sessions.count() < 1:
return messages.error(request, 'There are no upcoming study sessions at this time for the requested course')
context = {
'study_sessions': study_sessions,
}
return render(request, 'study/your_template_file.html', context)
Then in your html
<h1><center>Upcoming Study Sessions</center></h1>
<div>
<a class="btn btn-success" style="position:absolute; margin-right:2px; top:15%; right:0;" href="{% url 'study:courses' %}" role="button" >Back to My Courses</a>
</div>
<br><br>
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
{% for session in study_sessions %}
<div class="row">
<button type="button" class="btn btn-outline-secondary" >Date/Time: {{ session.date }} <br> Location: {{ session.location }} </button>
<br><br><br>
</div>
{% endfor %}
</div>

how show favorite list in django

I have a list of favorites and I want to show them when I click on the interest button after I click on the list and my heart will be bold. The second part, ie filling the heart, is done correctly, but when I want to show the list, it does not show anything and gives the following error.
Reverse for 'show_Book' not found. 'show_Book' is not a valid view function or pattern name.
model.py
class Book (models.Model):
BookID= models.AutoField(primary_key=True)
Titel=models.CharField(max_length=150 )
Author=models.ForeignKey('Author',on_delete=models.CASCADE)
Publisher=models.ForeignKey('Publisher',on_delete=models.CASCADE) translator=models.ForeignKey('Translator',null='true',blank='true',on_delete=models.SET_NULL)
favorit=models.ManyToManyField('orderapp.Customer',related_name='favorit', blank=True)
view.py
def show_Book(request,BookID):
showBook=get_object_or_404(Book,BookID=BookID)
is_favorite=False
if showBook.favorit.filter(id=request.user.id).exists():
is_favorite=True
return render (request , 'showBook.html', {'is_favorite':is_favorite,})
def favoritbook (request, BookID):
showBook=get_object_or_404(Book,BookID=BookID)
if showBook.favorit.filter(id=request.user.id).exists():
showBook.favorit.remove(request.user)
else:
showBook.favorit.add(request.user)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
def favoritlist(request):
user=request.user
favoritbooks=user.favorit.all()
context={'favoritbooks':favoritbooks}
return render (request,'favotritlist.html',context)
url.py
path('showbookstor/<int:id>', views.show_BookStor, name='show_BookStor'),
path('books/favorit/<int:BookID>/', views.favoritbook, name='favoritbook'),
path('books/favorit/', views.favoritlist, name='favoritlist'),
showbook.html
{% if is_favorite %}
<li id="sell">add to favorit <i class="fas fa-heart"></i> </li>
{% else %}
<li id="sell"> delete favorit <i class="far fa-heart"></i> </li>
{% endif %}
favoritlist.html
{% for Book in favoritbooks %}
<section id="card" class="col-md-6 col-lg-3 col-sm-6 col-xs-12">
<img src= {{Book.Image.url}}>
<h1 id="bookname">{{Book.Titel}}</h1>
<p>{{Book.Author}}</p>
</section>
{% endfor %}
you use {% url 'show_Book' Book.BookID %} but you don't have any definition in the urls.py with name show_Book try changing
path('showbookstor/<int:id>', views.show_BookStor, name='show_BookStor')
to
path('showbook/<int:id>', views.show_Book, name='show_Book')

NoReverseMatch 1 pattern(s) tried: ['PUTData/(?P<pk>[0-9]+)/$']

So I'm doing my first project using django showing examples of REST API methods like POST, GET, PUT, and DELETE through a made up database of first name, last name, and e-mail. I've been successful with POST and GET but now I am having trouble with PUT.
So I have three functions. First, a simple def function that shows all inputs of information so far. Second a class based function that lists the information in a specific order. And third, another class based function that shows the detail of that specific information. All functions work but now when I am linking the two html files together I am getting a error. I've tried a number of different ids when wanting to link to that specific information but they are just not working.
But here is my models.py:
class Information(models.Model):
"""Placeholder code that the viewer will be seeing."""
info_id = models.AutoField(primary_key=True,unique=True)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
e_mail = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return information."""
return f"Info ID: {self.info_id}, First Name: {self.first_name}, Last Name: {self.last_name}, E-mail: {self.e_mail}"
Here is my forms.py:
class NameForm(forms.ModelForm):
class Meta:
model = Information
fields = ['info_id', 'first_name', 'last_name', 'e_mail']
labels = {'first_name': 'First Name:', 'last_name': 'Last Name:', 'e_mail': 'E-mail:'}
widgets = {'first_name': '',
'last_name': '',
'e_mail': '',
}
Here is my urls.py:
# Page(s) that shows the viewer an example of PUT.
path('PUT/', InfoDataListView.as_view(), name='put_data'),
path('PUT/<int:pk>/', InfoDataDetailView.as_view(), name='put_detail'),
Here is my views.py:
def put(request):
put = Information.objects.all()
context = {'put': put}
return render(request, 'just_projects/put.html', context)
class InfoDataListView(ListView):
model = Information
template_name = 'just_projects/put.html'
context_object_name = 'put'
ordering = ['-date_added']
class InfoDataDetailView(DetailView):
model = Information
Here are my two htmls: put.html and information_detail.html
{% extends "just_projects/base.html" %}
{% block content %}
<p><h3>Information:</h3></p>
<ul>
{% for pt in put %}
<div class="card mb-3">
<h4 class="card-header">
{{ pt.date_added|date:'M d, Y H:i' }}
<small>Edit info.</small>
</h4>
<div class="card-body">
{{ pt }}
</div>
</div>
{% empty %}
<li>There is no information yet.</li>
{% endfor %}
</ul>
<hr />
{% endblock content %}
{% extends "just_projects/base.html" %}
{% block content %}
<p><h3>Information:</h3></p>
<ul>
<div class="card mb-3">
<h4 class="card-header">
{{ object.date_added|date:'M d, Y H:i' }}
</h4>
<div class="card-body">
{{ object }}
</div>
</div>
</ul>
<hr />
<p>
</p>
{% endblock content %}
So the error message is a reversematch because they can't find the correct id right? The specific exception error and value is:
Exception Type: NoReverseMatch
Exception Value:
Reverse for 'put_detail' with arguments '('',)' not found. 1 pattern(s) tried: ['PUT/(?P[0-9]+)/$']
EDIT: Thank you Daniel Roseman!!!
You are using the wrong variable name within your url tag. Your object is called pd, so that is where the ID needs to come from.
<a href="{% url 'just_projects:put_detail' pd.id %}">
(Unrelated, but your clean method is pointless; it is within the Meta class so it will never actually be called, but also it does nothing useful. You should delete it.)
Edit looking at your models, your primary key is called info_id for some reason. So either do pd.info_id, or just pd.pk.
Although I would question why you need to change the name of the primary key in the first place.

Nothing is shown in a tag

Nothing is shown in a tag.
I wrote in views.py
def top(request):
content = POST.objects.order_by('-created_at')[:5]
category_content = Category.objects.order_by('-created_at')[:5]
return render(request, 'top.html',{'content':content,'category_content':category_content})
in models.py
class Category(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
in top.html
<ul class="list-group">
<li class="list-group-item active text-center">category</li>
<div class="collapse">
{% for category in category_content %}
<a href="" class="list-group-item">
{{ category.name }}
</a>
{% endfor %}
</div>
</ul>
Now output is like
so category.name is not shown.My ideal output is
I really cannot understand why category.name is not shown.I print out print(category_content) in views.py's top method,so , ]> is shown.How should I fix this?What is wrong in my codes?
Categories are not visible, because you used collapse CSS class in the div, which means it's not displayed.
If you check Bootstrap 4 documentation on collapse, you'll see that it states
.collapse hides content

Django for loop in html template not displaying

So I am trying to get the latest post and I ran into a problem where it will not display the post
views.py
def latest_post(request):
latest = Post.objects.all().order_by('-id')[:1]
context = {'latestPost': latest}
return render(request, 'latest_post.html', context)
Note: I also tried it with this, latest = Post.objects.all()
There are entries in the database, I tested with the shell
from blog.models import Post
>>> Post.objects.all()
<QuerySet [<Post: test>, <Post: okay so>, <Post: okay-okay>]>
latest_post.html
{% for newest in latestPost %}
<section class="hero is-primary is-medium">
<div class="hero-body header">
<div class="container">
<div class="font">
<h1 class="title is-1">
<span id="blog_title">{{ newest.title }}</span>
</h1>
<h2 class="subtitle is-3 subup">
<span id="blog-subtitle">{{ newest.content|truncatechars:20|safe }}</span>
</h2>
<h2 class="subtitle is-5 dateup">
<span id="blogdate">{{ newest.timestamp }}</span><br><br>
Read More >>
</h2>
</div>
</div>
</div>
</section>
{% endfor %}
in my blog_index.html I have the following
{% extends "blog_base.html" %}
{% block blog_main %}
{% load staticfiles %}
{% include 'latest_post.html' %}
<p> other html here </p>
{% endblock %}
Latest_post.html displays when i use {% include 'latest_post.html' %} only if I don't use
{% for newest in latestPost %}
{% endfor %}
So i am sure there aren't any typos somewhere that prevents the latest_post.html from displaying in my index page.
my models.py
class Post(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(unique=True)
content = models.TextField()
draft = models.BooleanField(default=False)
publish = models.DateField(auto_now=False, auto_now_add=False)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
def __str__(self):
return self.title
def blog_url(self):
return reverse("blogposts:blogdetail", kwargs={"slug": self.slug})
Additional Notes: python3, django 1.11, sqlite.
Also, No errors are displayed in the console. any help would be appreciated! thank you!!
Looks like you are passing context variable to latest_post.html directly:
return render(request, 'latest_post.html', context)
But there is no such context variable latestPost in blog_index.html.
What you need to do is add context to blog_index.html. Add this to index view also:
latest = Post.objects.all().order_by('-id')[:1]
context = {'latestPost': latest}
return render(request, 'blog_index.html', context)
Also you can use first to select first element in queryset.
In your previous code you are limiting it by using [:1] so it only returns one item. Then you are using a forloop again on one item. Not the best way to do things.
Are you trying to get only one item from the post? If yes, Forloop is not needed change your latest_post.html to
<section class="hero is-primary is-medium">
<div class="hero-body header">
<div class="container">
<div class="font">
<h1 class="title is-1">
<span id="blog_title">{{ latestPost.title }}</span>
</h1>
<h2 class="subtitle is-3 subup">
<span id="blog-subtitle">{{ latestPost.content|truncatechars:20|safe }}</span>
</h2>
<h2 class="subtitle is-5 dateup">
<span id="blogdate">{{ latestPost.timestamp }}</span><br><br>
Read More >>
</h2>
</div>
</div>
</div>
</section>
You don't even need to use the [:1] in your views. There are better ways to do this
I also stuck in the same problem and I was damn confirmed that I was doing any silly mistakes.
Unfortunately, I found one. So,
Try this:
context = {'latest': latest}
Instead of:
context = {'latestPost': latest}
I hope it works.

Categories

Resources