Issue
I am getting the following error in my traceback, I am currently running tests for my new website and when I try to create more than one blog post I get returned a MultipleObjectsReturned error, how would I fix this?
I am guessing the issue lies with get_object_or_404 as other questions on Stack Overflow have suggested that I use primary keys but I don't want just one object to filter, I need to show all the objects in my Post model
traceback: https://dpaste.com/6J3C7MLSU
views.py
```python3
class PostDetail(LoginRequiredMixin, DetailView):
model = Post
form_class = CommentForm
template_name = "cubs/post_detail.html"
def get_form(self):
form = self.form_class(instance=self.object)
return form
def post(self, request, slug):
new_comment = None
post = get_object_or_404(Post)
form = CommentForm(request.POST)
if form.is_valid():
# Create new_comment object but don't save to the database yet
new_comment = form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
# Save the comment to the database
new_comment.save()
messages.warning(request, "Your comment is awaiting moderation, once moderated it will be published")
return redirect('cubs_blog_post_detail', slug=slug)
else:
return render(request, self.template_name, {'form': form})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = get_object_or_404(Post)
comments = post.cubs_blog_comments.filter(active=True).order_by('-date_posted')
articles = Article.objects.filter(status=1).order_by('-date_posted')[:2]
post_likes = get_object_or_404(Post, slug=self.kwargs['slug'])
total_likes = post_likes.total_likes()
if post_likes.likes.filter(id=self.request.user.id).exists():
liked = True
else:
liked = False
context['liked'] = liked
context['articles'] = articles
context['comments'] = comments
context['total_likes'] = total_likes
context['title'] = 'Post Details'
context.update({
'comment_form': self.get_form(),
})
return context
```
models.py
```python3
class Post(models.Model):
class Status(models.IntegerChoices):
Draft = 0
Published = 1
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='cubs_blog_posts')
updated_on = models.DateTimeField(auto_now=True)
content = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=Status.choices, default=Status.Draft)
likes = models.ManyToManyField(User, related_name="cubs_blog_posts_likes")
class Meta:
ordering = ['-date_posted']
def __str__(self):
return self.title
def total_likes(self):
return self.likes.count()
def get_absolute_url(self):
return reverse("cubs_blog_post_detail", kwargs={"slug": str(self.slug)})
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
```
post_form.html
```html
{% extends "cubs/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST" autocomplete="off">
{% csrf_token %}
{{ form.media }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Blog Post</legend>
{{ form | crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">
<i class="fa-duotone fa-mailbox"></i> Post
</button>
</div>
</form>
</div>
{% endblock content %}
```
Solution
Following the traceback, the issue lies on the second line of the get_context_data method (post = get_object_or_404(Post)).
Here is a equivalent code for get_objects_or_404 from the doc:
try:
obj = MyModel.objects.get(pk=1)
except MyModel.DoesNotExist:
raise Http404("No MyModel matches the given query.")
There is therefore three possible outcomes when calling this function:
- If no object matches the provided filters => Http404 is raised (it will be caught by Django and an 404 HttpResponse will be send)
- If one and only one object matches the provided filters => The object is returned
- If more than one object match the provided filters => MyModel.MultipleObjectsReturned is raised
Since no filters were provided in the seconde line of get_context_data, all Post will match. This means that Http404 will be raised if more than one post exists.
The goal is to provide more details on a post when "read more" is clicked. There is therefore some kind of id to filter Posts upon.
In this case:
get_object_or_404(Post, slug=self.kwargs.get('slug'))
If MultipleObjectsReturned is not an issue then .filter().first() can be used
obj: Optional[MyModel] = MyModel.objects.filter(...).first()
Filter/first will return an object if at least one matches the filters (the first one if multiple match) or None if no object matches the filters.
If the 404 aspect is important:
obj: Optional[MyModel] = MyModel.objects.filter(...).first()
if not obj:
raise Http404("No MyModel matches the given query.")
Answered By - Alombaros
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.