Django Template - show many-to-many relationship - python

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 %}

Related

How do you get queryset objects related to queryset passed to templates in Django

I have these two models and as you can see they have a relationship.
class Post(models.Model):
text = models.TextField()
class PostImage(models.Model):
post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
image = models.FileField(upload_to = 'media/',blank=True, null=True)
As far as I understand if I query posts and push them to a template and post, I would expect to use something like this in my templates to retrieve the images URL attached to the posts but it doesn't seem to work.
{% for post in posts %}
{% for post_image in post.post_image_set.all %}
{{post_image.image.url}}
{% endfor %}
{% endfor %}
What am I doing wrong?
Here is my views.py file.
views.py
# Create your views here.
def index(request):
posts=Post.objects.filter(approved=True).order_by('-published_date')
context = {"posts":posts}
return render(request, 'post/home.html',context)
The default related name for a foreign key relational is the name of the model (PostImage) but in your template for loop you called it post_image Following relationships “backward”
change
{% for post_image in post.post_image_set.all %}
into
{% for post_image in post.postimage_set.all %}
Template code (with change)
{% for post in posts %}
{% for post_image in post.postimage_set.all %}
{{post_image.image.url}}
{% endfor %}
{% 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 %}

Django displaying all users

I want to display all the users, in my template but I cannot. I think there is a mistake in my codes but I cannot find it. Here is codes.
views.py
class UsersView(TemplateView):
template_name = 'users.html'
def get_context_data(self, **kwargs):
context = super(UsersView, self).get_context_data(**kwargs)
context['object_list'] = UserList.objects.all()
return context
models.py
class UserList(AbstractBaseUser):
first_name = models.CharField(max_length=200, blank=True, help_text="The first name of the user.")
last_name = models.CharField(max_length=200, blank=True, help_text="The last name of the user.")
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
help_text="The email and username of the user. Required."
)
users.html
{% extends 'blog/base.html' %}
{% block content %}
<h1>Users:</h1>
<ul>
{% for users in object_list %}
<li class="user">{{ user }}</li>
{% endfor %}
</ul>
{% endblock %}
And this is the page that I get:
Where is my mistake? Thank you.
Your code includes the following:
{% for users in object_list %}
<li class="user">{{ user }}</li>
{% endfor %}
The trouble with this is that the user template variable is not defined. Change users to user in the loop header and you should be OK.
You can use django.contrib.auth.models.User on your view with request.user.get_username() with this you use a standard user with Django. If go go admin URL you see user default.
For call in template :
{{ user.get_username }}

Listing ForeignKey associated instances within template (queryset within a queryset)

I have a site which catalogs local hikes, and users can log that they have been on the hike. I have a search page which contains the hikes, and one of the fields I'm trying to display is a list of all the people who have been on the hike. I've got this figured out within the individual detail page of the hike, but can't figure out how to create a new queryset within the queryset which is printing the hikes, in order to display this info on a search page.
Here's some code:
models.py:
class Hike(models.Model):
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(unique=True)
...
class UserLog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
hike = models.ForeignKey(Hike, on_delete=models.CASCADE)
views.py:
def hike_list(request):
qs = Hike.objects.all()
... some other filters here
?-->users = UserLog.objects.filter(id=qs.id)
template:
{% for qs in qs %}
{{ hike.name }}{{ hike.other_details_and_stuff }}
?----> {% for users in hikes %}{{ user.name }}{% endfor %}
{% endfor %}
Here's the working code within the individual hike's detail page:
views.py:
def hike_detail (request, slug)
users = UserLog.objects.filter(hike__slug=slug)
How do I call on the slug from each individual item in the queryset, then run a queryset on that?
The easiest is to add a ManyToManyField to Hike:
class Hike(models.Model):
...
users = models.ManyToManyField(User, through='app.UserLog')
If you have no extra fields in UserLog, you can even remove the UserLog model and the through parameter alltogether. In the template you can do:
{% for hike in qs %}
{{ hike.name }}{{ hike.other_details_and_stuff }}
{% for user in hike.users.all %}{{ user.name }}{% endfor %}
{% endfor %}
In order avoid too many queries, you should prefetch the users in the Hike query in the view:
qs = Hike.objects.all().prefetch_related('users')
Without the ManyToManyField, you could add a property and user the same template, but the prefetch clause could not be used that easily:
class Hike(models.Model):
...
#property
def users(self):
return User.objects.filter(userlog__hike=self)

Django Aggragate query issue in template

Here's my code:
MODEL
class compilation (models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100, null=False, blank=False)
slot = models.PositiveIntegerField()
slotpergroup = models.PositiveIntegerField()
songs = models.ManyToManyField(song,related_name="slotsongs", null=True, blank=True)
class song(models.Model):
title = models.CharField(max_length=150)
content = models.FileField(upload_to='static/canzoni', validators=[validate_file_audio])
groups = models.ManyToManyField(group)
class group(models.Model):
user = models.ManyToManyField(User)
name = models.CharField(max_length=50)
genr = models.CharField(max_length=50)
avatar = models.FileField(upload_to='static/logogruppi', validators=[validate_file_extension])
VIEW
c = {}
compilation = compilation.objects.all()
c.update({'compilation': compilation })
return render_to_response('compilation.html', c, context_instance=RequestContext(request))
TEMPLATE compilation.html
{% for cd in compilation %}
{{ cd.title }}<br/>
PLAYLIST
{% for song in cd.songs.all %}
{{ song.title | capfirst }}<br/>
{% for g in song.groups.all %}
{{ g | capfirst }}
{% endfor %}
{% endfor %}
{% for gr in user.group_set.all %}
--> here my problem, I need something like this:
var = song.objects.filter(groups=gr).filter(slotsongs=cd).count()
if cd.slotpergropu <= var:
print: "all slots are busy for this group" <--
{% endfor %}
{% endfor %}
I need to show for each compilation, for each user's group, if there are available slots or if someone of his group took all own available slot yet.
Django enforces MVC framework by not implicitly allowing any Buseness logic in Templates.
By standards these kind of things should be done in Views and the result should be passed as context to Templates.
But, if it is so necessary to Make this query in the Templates then you can use Template tags for this purpose like following:
Use Assginment tag here:
{% load songs_tags %}
{% get_songs_for_group gr cd as var %}
Create template tag file
### templatetags/songs_tags.py ###
#register.assignment_tag
def get_songs_for_groups(group, collection):
return song.objects.filter(groups=group).filter(slotsongs=collection).count()
See documentation for more details: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#howto-custom-template-tags
Unfortunately, you can't call a function that requires parameters in a template. You have to assemble what you need in views.py, write a template tag or filter instead. Take a look at the documentation to learn more about it.

Categories