Weird Django error when adding annotations annotations
Question:
I am trying to add comments to my django forum, but am running into an issue with annotate. whenever I add instructions to add comments com=Comment.objects.order_by("-published_date")
it gives me an issue with a compleatly unrelated part of my template that checks the username.
here is my view:
def index(request):
# check if there is any incomming data (comments) and make sure the user is authenticated
POST = request.POST
if POST != {} and request.user.is_authenticated:
# a comment has been recived, time to move forward with creating it
# figure out if the post even exists
get_object_or_404(Post, id = POST['post_id'])
# grab the user object
user_active = User.objects.get(id = POST['user'])
# grab the post object
post_active = Post.objects.get(id = POST['post_id'])
# make and save the comment
n_comment = Comment(user = user_active, comment = POST['comment'], post = post_active)
n_comment.save()
posts = Post.objects.order_by("-published_date").annotate(num_likes=Count("likes"),com=Comment.objects.order_by("-published_date"))
return render(request, 'home.html', {'posts': posts})
here is my model:
class Comment(models.Model):
comment = models.CharField(max_length=500)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments')
published_date = models.DateTimeField('date_published')
and here is the error:
Environment:
Request Method: GET
Request URL: http://localhost:8000/
Django Version: 3.2.18
Python Version: 3.6.9
Installed Applications:
['core',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Template error:
In template /vagrant/templates/base.html, error at line 12
subquery must return only one column
LINE 1: ...d_by_id", COUNT("core_like"."id") AS "num_likes", (SELECT "c...
^
2 : <title>{% block title %}{% endblock %} - Pelican</title>
3 :
4 : {%load static%}
5 :
6 : <link rel='stylesheet' href="{% static 'core/style.css' %}">
7 :
8 : <nav>
9 : <h1>Pelican</h1>
10 : <ul>
11 : {% if user.is_authenticated %}
12 : <li><span>{{ u ser.username }}</span>
13 : <li><a href="{% url 'logout' %}">Log Out</a>
14 : {% else %}
15 : <li><a href="{% url 'signup' %}">Sign Up</a>
16 : <li><a href="{% url 'login' %}">Log In</a>
17 : {% endif %}
18 : </ul>
19 : </nav>
20 : <section class="content">
21 : <header>
22 : {% block header %}{% endblock %}
Traceback (most recent call last):
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
The above exception (subquery must return only one column
LINE 1: ...d_by_id", COUNT("core_like"."id") AS "num_likes", (SELECT "c...
^
) was the direct cause of the following exception:
File "/vagrant/venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/vagrant/venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/vagrant/venv/lib/python3.6/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/vagrant/core/views.py", line 33, in index
return render(request, 'home.html', {'posts': posts})
File "/vagrant/venv/lib/python3.6/site-packages/django/shortcuts.py", line 19, in render
content = loader.render_to_string(template_name, context, request, using=using)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/loader.py", line 62, in render_to_string
return template.render(context, request)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/backends/django.py", line 61, in render
return self.template.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 170, in render
return self._render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 162, in _render
return self.nodelist.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 938, in render
bit = node.render_annotated(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 905, in render_annotated
return self.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/loader_tags.py", line 150, in render
return compiled_parent._render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 162, in _render
return self.nodelist.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 938, in render
bit = node.render_annotated(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 905, in render_annotated
return self.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/loader_tags.py", line 62, in render
result = block.nodelist.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 938, in render
bit = node.render_annotated(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 905, in render_annotated
return self.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/defaulttags.py", line 171, in render
len_values = len(values)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/query.py", line 262, in __len__
self._fetch_all()
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/query.py", line 1324, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
cursor.execute(sql, params)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 98, in execute
return super().execute(sql, params)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 66, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
Exception Type: ProgrammingError at /
Exception Value: subquery must return only one column
LINE 1: ...d_by_id", COUNT("core_like"."id") AS "num_likes", (SELECT "c...
^
the weird thing is, whenever I remove the annotation everything works. right now the template is very basic, it should only show the comment objects.
Answers:
You don’t use annotations for this. As the error says, a Subquery
can, at most, return a single item.
You can prefetch the comments in an ordered manner, so:
from django.db.models import Count, Prefetch
from django.shortcuts import redirect
def index(request):
# check if there is any incomming data (comments) and make sure the user is authenticated
POST = request.POST
if request.method == 'POST' and request.user.is_authenticated:
# a comment has been recived, time to move forward with creating it
# figure out if the post even exists
active_post = get_object_or_404(Post, id=POST['post_id'])
# make and save the comment
n_comment = Comment(
user=request.user, comment=POST['comment'], post=post_active
)
n_comment.save()
return redirect('name-of-some-view')
posts = (
Post.objects.order_by('-published_date')
.prefetch_related(
Prefetch('comments', Comment.objects.order_by('-published_data'))
)
.annotate(num_likes=Count('likes'))
)
return render(request, 'home.html', {'posts': posts})
In the template you render this with:
{% for post in posts %}
…
{% for comment in post.comments.all %}
{% endfor %}
{% endfor %}
Note: It is normally better to make use of the settings.AUTH_USER_MODEL
[Django-doc] to refer to the user model, than to use the User
model [Django-doc] directly. For more information you can see the referencing the User
model section of the documentation.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: It is better to use a Form
[Django-doc]
than to perform manual validation and cleaning of the data. A Form
will not
only simplify rendering a form in HTML, but it also makes it more convenient
to validate the input, and clean the data to a more convenient type.
I am trying to add comments to my django forum, but am running into an issue with annotate. whenever I add instructions to add comments com=Comment.objects.order_by("-published_date")
it gives me an issue with a compleatly unrelated part of my template that checks the username.
here is my view:
def index(request):
# check if there is any incomming data (comments) and make sure the user is authenticated
POST = request.POST
if POST != {} and request.user.is_authenticated:
# a comment has been recived, time to move forward with creating it
# figure out if the post even exists
get_object_or_404(Post, id = POST['post_id'])
# grab the user object
user_active = User.objects.get(id = POST['user'])
# grab the post object
post_active = Post.objects.get(id = POST['post_id'])
# make and save the comment
n_comment = Comment(user = user_active, comment = POST['comment'], post = post_active)
n_comment.save()
posts = Post.objects.order_by("-published_date").annotate(num_likes=Count("likes"),com=Comment.objects.order_by("-published_date"))
return render(request, 'home.html', {'posts': posts})
here is my model:
class Comment(models.Model):
comment = models.CharField(max_length=500)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments')
published_date = models.DateTimeField('date_published')
and here is the error:
Environment:
Request Method: GET
Request URL: http://localhost:8000/
Django Version: 3.2.18
Python Version: 3.6.9
Installed Applications:
['core',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Template error:
In template /vagrant/templates/base.html, error at line 12
subquery must return only one column
LINE 1: ...d_by_id", COUNT("core_like"."id") AS "num_likes", (SELECT "c...
^
2 : <title>{% block title %}{% endblock %} - Pelican</title>
3 :
4 : {%load static%}
5 :
6 : <link rel='stylesheet' href="{% static 'core/style.css' %}">
7 :
8 : <nav>
9 : <h1>Pelican</h1>
10 : <ul>
11 : {% if user.is_authenticated %}
12 : <li><span>{{ u ser.username }}</span>
13 : <li><a href="{% url 'logout' %}">Log Out</a>
14 : {% else %}
15 : <li><a href="{% url 'signup' %}">Sign Up</a>
16 : <li><a href="{% url 'login' %}">Log In</a>
17 : {% endif %}
18 : </ul>
19 : </nav>
20 : <section class="content">
21 : <header>
22 : {% block header %}{% endblock %}
Traceback (most recent call last):
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
The above exception (subquery must return only one column
LINE 1: ...d_by_id", COUNT("core_like"."id") AS "num_likes", (SELECT "c...
^
) was the direct cause of the following exception:
File "/vagrant/venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/vagrant/venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/vagrant/venv/lib/python3.6/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/vagrant/core/views.py", line 33, in index
return render(request, 'home.html', {'posts': posts})
File "/vagrant/venv/lib/python3.6/site-packages/django/shortcuts.py", line 19, in render
content = loader.render_to_string(template_name, context, request, using=using)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/loader.py", line 62, in render_to_string
return template.render(context, request)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/backends/django.py", line 61, in render
return self.template.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 170, in render
return self._render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 162, in _render
return self.nodelist.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 938, in render
bit = node.render_annotated(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 905, in render_annotated
return self.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/loader_tags.py", line 150, in render
return compiled_parent._render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 162, in _render
return self.nodelist.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 938, in render
bit = node.render_annotated(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 905, in render_annotated
return self.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/loader_tags.py", line 62, in render
result = block.nodelist.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 938, in render
bit = node.render_annotated(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/base.py", line 905, in render_annotated
return self.render(context)
File "/vagrant/venv/lib/python3.6/site-packages/django/template/defaulttags.py", line 171, in render
len_values = len(values)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/query.py", line 262, in __len__
self._fetch_all()
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/query.py", line 1324, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
cursor.execute(sql, params)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 98, in execute
return super().execute(sql, params)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 66, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/vagrant/venv/lib/python3.6/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/vagrant/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
Exception Type: ProgrammingError at /
Exception Value: subquery must return only one column
LINE 1: ...d_by_id", COUNT("core_like"."id") AS "num_likes", (SELECT "c...
^
the weird thing is, whenever I remove the annotation everything works. right now the template is very basic, it should only show the comment objects.
You don’t use annotations for this. As the error says, a Subquery
can, at most, return a single item.
You can prefetch the comments in an ordered manner, so:
from django.db.models import Count, Prefetch
from django.shortcuts import redirect
def index(request):
# check if there is any incomming data (comments) and make sure the user is authenticated
POST = request.POST
if request.method == 'POST' and request.user.is_authenticated:
# a comment has been recived, time to move forward with creating it
# figure out if the post even exists
active_post = get_object_or_404(Post, id=POST['post_id'])
# make and save the comment
n_comment = Comment(
user=request.user, comment=POST['comment'], post=post_active
)
n_comment.save()
return redirect('name-of-some-view')
posts = (
Post.objects.order_by('-published_date')
.prefetch_related(
Prefetch('comments', Comment.objects.order_by('-published_data'))
)
.annotate(num_likes=Count('likes'))
)
return render(request, 'home.html', {'posts': posts})
In the template you render this with:
{% for post in posts %}
…
{% for comment in post.comments.all %}
{% endfor %}
{% endfor %}
Note: It is normally better to make use of the
settings.AUTH_USER_MODEL
[Django-doc] to refer to the user model, than to use theUser
model [Django-doc] directly. For more information you can see the referencing theUser
model section of the documentation.
Note: In case of a successful POST request, you should make a
redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: It is better to use a
Form
[Django-doc]
than to perform manual validation and cleaning of the data. AForm
will not
only simplify rendering a form in HTML, but it also makes it more convenient
to validate the input, and clean the data to a more convenient type.