Extra "row-level" data in ListView django - python

I'm writing my first django app and can't seem to pass "row-level" data to the template through ListView. Specifically I'm trying to show all Polls and their corresponding Vote information using a PollListView.
Currently I am only able to pass all votes to the template, but would like to pass only the votes that belong to the specific poll.
models.py
class Poll(models.Model):
user = models.ForeignKey(User, unique=False, blank=False, db_index=True)
title = models.CharField(max_length=80)
class Vote(models.Model):
poll = models.ForeignKey(Poll, unique=False, blank=False, db_index=True)
user = models.ForeignKey(User, unique=False, blank=True, null=True, db_index=True)
vote = models.CharField(max_length=30, blank=False, default='unset', choices=choices)
views.py
class PollListView(ListView):
model = Poll
template_name = 'homepage.html'
context_object_name="poll_list"
def get_context_data(self, **kwargs):
context = super(PollListView, self).get_context_data(**kwargs)
context['vote_list'] = Vote.objects.all()
return context
urls.py
urlpatterns = patterns('',
...
url(r'^$', PollListView.as_view(), name="poll-list"),
}
homepage.html
{% for poll in poll_list %}
{{ poll.title }}
{% for vote in vote_list %}
{{ vote.id }} {{ vote.vote }}
{% endfor %}
{% endfor %}
Seems like an easy task, but I can't seem to figure out how to do this using class-based views. Should I be using mixins or extra_context? Overwrite queryset? Or should I just used function-based views to solve this.
Any help would be greatly appreciated.

I'm not sure if it's gonna work, but you can try the following:
models.py (Vote class)
poll = models.ForeignKey(Poll, related_name="votes", unique=False, blank=False, db_index=True)
views.py
class PollListView(ListView):
queryset = Poll.objects.all().prefetch_related('votes')
with that you can access related votes:
template
{% for poll in poll_list %}
{{ poll.title }}
{% for vote in poll.votes.all %}
{{ vote.id }} {{ vote.vote }}
{% endfor %}
{% endfor %}

Related

Comments under Post

I created model Post and Comment, now I want to display comments from Post but I have a problem. In tutorial there is a line of code in html {% for comment in post.comments.all %} but isn't working in my app. If I set {% for comment in comments %} it works but displays all comments from models (I want only comments from the post). How to fix that? Below I pasted my code.
models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=64, blank=False, null=False)
short_text = models.TextField(blank=False, null=False)
text = models.TextField(blank=False, null=False)
image = models.TextField(blank=False, null=False)
date = models.DateTimeField(auto_now_add=True, blank=True)
views = models.IntegerField(default=0)
class Comment(models.Model):
post = models.ForeignKey('main.Post', on_delete=models.CASCADE)
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(auto_now_add=True, blank=True)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
views.py
from django.shortcuts import get_object_or_404, render, redirect
from .models import Post, Comment
def index_pl(request):
posts = Post.objects.all()
return render(request, 'index_pl.html', {'posts': posts})
def single_article_pl(request, id):
posts = get_object_or_404(Post, pk=id)
posts.views = posts.views + 1
posts.save()
comments = Comment.objects.all()
return render(request, 'single_article_pl.html', {'posts': posts, 'comments': comments})
html
{% for comment in post.comments.all %}
<div class="comment">
<div class="date">{{ comment.created_date }}</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}
admin.py
from django.contrib import admin
from .models import Post, Comment
admin.site.register(Post)
admin.site.register(Comment)
In tutorial there is a line of code in html {% for comment in post.comments.all %} but isn't working in my app.
This is likely because they specified the related_name=… parameter [Django-doc] in the ForeignKey from Comment to Post, like:
# Option 1: set the related_name to 'comments'
class Comment(models.Model):
post = models.ForeignKey(
'main.Post',
related_name='comments',
on_delete=models.CASCADE
)
# …
The related_name=… specifies the name of the relation in reverse, so in this case accessing the Comments of a given Post object. By default this is model_name_set, so in this case comment_set.
You thus can either specify a related name; or you can acces the comment_set manager:
Option 2: use the default related_name
{% for comment in post.comment_set.all %}
<div class="comment">
<div class="date">{{ comment.created_date }}</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}

How to acces the the django ManyToMany Field in django Template

I have set of attributes in my Models from which one of the attribute is of Type ManyToMany Field. I am able to access all the Attributes in Template instead one which is ManyToMany Field.
I have tried following in my template
{% for post in all_posts %}
{{ post.likes }}
{% endfor %}
models.py
class Posts(models.Model):
title = models.CharField(max_length=250, blank=False)
content = models.CharField(max_length=15000,
help_text="Write Your thought here...")
creation_time = models.DateTimeField(auto_now_add=True, editable=False)
likes = models.ManyToManyField(User, blank=True, related_name='likes')
views.py
def home(request):
template = loader.get_template('home.html')
all_posts = Posts.objects.all()
context = {
'all_posts': all_posts,
}
return HttpResponse(template.render(context, request))
When i Use {{ post.likes }} what renders on page is auth.User.None
You will have to traverse over all the likes for the selected post
Try something like this:
{% for post in all_posts %}
{% for like in post.likes.all %}
{{ like }}
{% endfor %}
{% endfor %}

how to connect two models in html template?

I would like to create a simple forum with Python Django. The main thing I can not figure out is getting informations from two models.
I want to display: Post title, content, author, published date and signature. This is my post_detail.html
{% extends 'Homepage/base.html' %}
{% block content %}
<h1>{{ post.title }}</h1>
<h6>Kategoria: {{ post.category }} | Autor: {{ post.author }} | {{ post.published_date }}</h6>
<p>{{ post.description|linebreaksbr }}</p>
<p><hr>{{ HERE I DON'T KNOW HOW TO SHOW SIGNATURE }}</p>
{% endblock %}
</body>
</html>
Homepage app models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Category(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.title
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
description = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
published_date = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(blank=True, null=True)
views = models.IntegerField(default=0)
def __str__(self):
return self.title
accounts app models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='user', on_delete=models.CASCADE)
website = models.URLField(default='', blank=True)
city = models.CharField(max_length=100, default='', blank=True)
signature = models.TextField(default='', blank=True)
def create_profile(sender, **kwargs):
user = kwargs["instance"]
if kwargs["created"]:
user_profile = UserProfile(user=user)
user_profile.save()
post_save.connect(create_profile, sender=User)
def __str__(self):
return self.user.username
My full code is on https://github.com/Incybro/Forum
You can just follow the relationships:
<p>{{ post.author.user.signature }}</p>
(Note, you've set the related_name from User to UserProfile to user, which makes no sense. You should leave it as the default, which would be userprofile.)
I guess your {{post.author}} won't be returning anything, change in to {{post.author.get_full_name}}
For signature, in your model you don't need to add any related name,
user = models.OneToOneField(User, on_delete=models.CASCADE)
in template,
{{post.author.userprofile.signature}}
Why not just return the relevant UserProfile model when you send the request?
def my_view(request):
context = {}
my_post = Post.objects.all.get(0) # get the post here
my_user = UserProfile.objects.get(user=my_post.author)
context['post'] = my_post
context['user'] = my_user
return TemplateResponse(request, 'my_template.html', context)
Then, in your html, you can use those template tags.
{% block content %}
<h1>{{ post.title }}</h1>
<h6>Kategoria: {{ post.category }} | Autor: {{ post.author }} | {{ post.published_date }}</h6>
{% autoescape on %}
<p>{{ post.description|linebreaksbr }}</p>
<p><hr>{{ user.signature }}</p>
{% endautoescape %}
{% endblock %}
You want the autoescape so people can't change the look or function of your site by injecting malicious HTML/JS into your site.

Django - filtering by user

I want to render data from the Route model that belongs to the Driver in their 'accounts' page - so displaying the leave_from, destination etc data they have saved in the database so far.
Models.py:
class Driver(models.Model):
user = models.OneToOneField(User, default=1)
first_name = models.CharField(max_length=120, blank=True, null=True)
last_name = models.CharField(max_length=120, blank=True, null=True)
tel = models.CharField(max_length=120, blank=True, null=True)
slug = models.SlugField(max_length=120, unique=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.user.username
def get_absolute_url(self):
return reverse("account", kwargs={"slug": self.slug})
def save(self, *args, **kwargs):
self.slug = slugify(self.first_name)
super(Driver, self).save(*args, **kwargs)
class Route(models.Model):
leave_from = models.CharField(max_length=120, blank=True, null=True)
destination = models.CharField(max_length=120, blank=True, null=True)
date = models.DateField(auto_now_add=False, auto_now=False)
time = models.TimeField(auto_now_add=False, auto_now=False)
driver = models.ForeignKey(Driver, on_delete=models.CASCADE)
def __str__(self):
return self.leave_from
I've played with various querysets and the below is the closest to getting there (I think... I'm new to coding and Django).
Views.py:
def route(request, slug):
routedetails = Driver.objects.filter(route=request.user.driver.route_set.all())
context = {
"routedetails": routedetails,
}
return render(request, "route.html", context)
With that I am able to get user to display the same number of instances of data in Route for that Driver.
Template:
{% for route in routedetails %}
<p>{{ route.user }}</p>
{% endfor %}
I've tried all different variations but I feel this has got me the closest as it is at least returning the user the same number of times there is data in Route for this user. In this case there are 2 routes saved in Route and so the username is returned twice. I have tested on other users and it always matches.
I've looked everywhere and this is as far as I've been able to get so appreciate any help.
If you want Route details it is best to query the Route model directly:
routedetails = Route.objects.filter(driver__user=request.user)
You can then iterate through the Route objects in your template:
{% for route in routedetails %}
<p>{{ route.leave_from }}</p>
<p>{{ route.destination }}</p>
...
{% endfor %}
Pocket Kings' solution is great and should be accepted. This is an example if you want to show routes for multiple drivers (admin page?) in order to avoid N+1 queries. This pre-fetches all the routes associated to the drivers and adds an attribute routes to each driver with their specific routes, so that it would eliminate the unneeded SQL queries later.
from django.db.models import Prefetch
drivers = Driver.objects.all()
queryset = drivers.prefetch_related(Prefetch('route_set', queryset=Route.objects.filter(driver_id__in=drivers), to_attr='routes'))
Template
{% for driver in drivers %}
{% for route in driver.routes %}
<p>{{ route.leave_from }}</p>
<p>{{ route.destination }}</p>
...
{% endfor %}
{% endfor %}
To get logged in driver's routes, the simplest approach is.
views.py
routes = request.user.driver.route_set.all()
template
{% for route in routes %}
{{ route.leave_from }}
{{ route.destination }}
{% endfor %}

Django Template - show many-to-many relationship

I'm learning Django after having built some basic apps in Flask. One thing I want to do is show users a list of all posts and whether or not they follow that given post. However, Jinja or Django is throwing some error that I don't quite know how to debug.
Models.py
class User(models.Model):
id = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=120,blank=False)
password = models.CharField(max_length=120, blank=True, null=False)
class Record(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=120, unique=True, blank=True)
followers = models.ManyToManyField(User, through='Follow')
class Follow(models.Model):
id = models.AutoField(primary_key=True)
record = models.ForeignKey(Record)
user = models.ForeignKey(User)
date_followed = models.DateField(null=True, blank=True)
records.html
{% for i in records %}
{% if i.follow.filter(id='1').first() %}
DO SOMETHING
{% endif %}
{% endfor %}
error
TemplateSyntaxError at /records/
Could not parse the remainder: '(id='1').first()' from 'i.follow.filter(id='1').first()'
To test this out when I run the python manage.py shell and execute the following I have no issues:
>>> x = Record.objects.first()
>>> x.followers.filter(id='1').first()
<User: User object>
I had initially prototyped this app using Flask and had the following jinja template and never had an issue:
{% for i in accounts %}
{% if i.follow.filter_by(user_id='1').first() %}
DO SOMETHING
{% endif %}
{% endfor %}
You cannot do that logic in template. You can create a method in Record model that does it for you and you can call it in template
class Record(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=120, unique=True, blank=True)
followers = models.ManyToManyField(User, through='Follow')
def first_follower(self):
if self.follow_set.filter(user_id=1).exists():
return True
return False
and in template:
{% for i in records %}
{% if i.first_follower %}
DO SOMETHING
{% endif %}
{% endfor %}
This is by design https://code.djangoproject.com/ticket/1199
The idea is that a django template should focus on design, for designers, and let the more complex code run in Python, not when the template renders.
So if this is a single instance when you use this check, add it to the view:
def get_context_data(self,*arg,**kwargs):
context = super(MyRecordView,self).get_context_data(*args,**kwargs)
context[has_follow] = self.object.follow.filter_by(user_id='1').exists()
return context
In the template:
{% if has_follow %}
...
{% endif %}
However, if you use this check a lot, you can add it to your model:
def has_follow(self):
return self.follow.filter_by(user_id='1').exists()
And then you can access it in a template, w/o any changes to the view context, since it's a model attribute:
{% if i.has_follow %}
...
{% endif %}

Categories