2

I'm still new at django so please bear with me. I'm trying to make a website about books. Now I'm having error like this

MultipleObjectsReturned at /premium/1/ get() returned more than one Book -- it returned 2!

I don't know where to look for the error. Here is my example code.

class PageDetailView(LoginRequiredMixin, generic.View):

    def get(self, request, *args, **kwargs):
        book = get_object_or_404(Book)
        page = get_object_or_404(Page)
        user_membership = get_object_or_404(Customer, user=request.user)
        user_membership_type = user_membership.membership.membership_type
        user_allowed = book.allowedMembership.all()
        context = {'object': None}
        if user_allowed.filter(membership_type=user_membership_type).exists():
            context = {'object': page}
        return render(request, "catalog/page_detail.html", context)

Traceback:

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\core\handlers\exception.py" in inner 34. response = get_response(request)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\core\handlers\base.py" in _get_response 126. response = self.process_exception_by_middleware(e, request)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\core\handlers\base.py" in _get_response 124. response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\views\generic\base.py" in view 68. return self.dispatch(request, *args, **kwargs)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\contrib\auth\mixins.py" in dispatch 52. return super().dispatch(request, *args, **kwargs)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\views\generic\base.py" in dispatch 88. return handler(request, *args, **kwargs)

File "C:\Users\admin\thesis\blackink_website\catalog\views.py" in get 127. book = get_object_or_404(Book)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\shortcuts.py" in get_object_or_404 93. return queryset.get(*args, **kwargs)

File "C:\Users\admin\AppData\Local\Programs\Python\Python37-32\lib\site-packages\django\db\models\query.py" in get 403. (self.model._meta.object_name, num)

Exception Type: MultipleObjectsReturned at /premium/1/ Exception Value: get() returned more than one Book -- it returned 2!

I will update the post if the information is not enough. Thanks in advance.

UPDATE

here's my models.py

class Book(models.Model):


title = models.CharField(max_length=200) #i deleted some info to make it shorter

allowedMembership = models.ManyToManyField(Membership, blank=True)


def get_absolute_url(self):
    return reverse('book-detail',  args=[str(self.id)])

def __str__(self):
    return self.title

@property
def pages(self):
    return self.page_set.all()



class Page(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, null=True)
file = models.FileField(upload_to='book_content', validators=[pdf_file_extension], help_text="PDF File Only")
timestamp = models.DateTimeField(default=timezone.now)


def __str__(self):
    return self.book.title

def get_absolute_url(self):
    return reverse('page-detail',   args=[str(self.id)])

page_detail.html

{% if user_membership and user_membership.user == request.user %}
        {% for content in book.pages %}
            <a href="{{ content.get_absolute_url }}" class="site-btn">Read</a>
        {% endfor %}
        {% else %}
        <button class="site-btn" disabled="disabled">VIP</button>
        {% endif %}
  • Is there a model relationship between your `Book` and `Page`? Please post your `models.py` – Daniel Holmes Mar 18 '19 at 15:45
  • Possible duplicate of [How to deal with a MultiObjectsReturned Error](https://stackoverflow.com/questions/14332792/how-to-deal-with-a-multiobjectsreturned-error) – dirkgroten Mar 18 '19 at 15:47
  • i posted my models.py. – Giranyx Pornasdoro Mar 18 '19 at 15:58
  • `get_object_or_404` expects to return a single object. `book = get_object_or_404(Book)` is failing because there are two books in the database. Which book do you want to fetch? On the next line you have `page = get_object_or_404(Page)` - which page do you want to fetch? Finally, please show your URL patterns. – Alasdair Mar 18 '19 at 17:01

2 Answers2

3

The MultipleObjectsReturned exception is raised by a query if only one object is expected, but multiple objects are returned. A base version of this exception is provided in django.core.exceptions; each model class contains a subclassed version that can be used to identify the specific object type that has returned multiple objects.

I am assuming that you are using the recent Django version. See the proper documentation based on your own version by switching from very bottom the official site.

The following would be a best links for you.

Just try like this, I have not tested but I am sure it will work as I had already tested before. Also check the above documentation & that is enough to get rid of this problem.

Configure your url based on https://docs.djangoproject.com/en/2.1/topics/class-based-... so that you could pass page_id and your url pattern should look something like path('pages/<int:page_id>', PageDetailView.as_view()) or re_path("^pages/(?<page_id>\d+)$", PageDetailView. as_view()).

Be careful while using path(), re_path(), url() as they have their own style + advantage.

Update:

After looking into the relationship between Book (parent model) and Page (child model), I changed the urlpattern from path('pages/<int:page_id>/books/<int:book' to path('pages/<page_id> and id of page is enough to fetch related book as there's a ForeignKey relationship between both the model.

As your view is PageDetailView, it's good to pass only id of the page for better design as others suggest (you can pass several url parameters too based on requirements but here we don't need).

from django.http import Http404

class PageDetailView(LoginRequiredMixin, generic.View):

    def get(self, request, *args, **kwargs):
        try:
            # page = get_object_or_404(Page)
            page_id = self.kwargs["page_id"]
            page = Page.objects.get(pk=page_id) 

            # book = get_object_or_404(Book)
            book_id = page.book.pk
            book = Book.objects.get(pk=book_id)

            # user_membership = get_object_or_404(Customer, user=request.user)
            user_membership = Customer.objects.get(user=request.user)

            user_membership_type = user_membership.membership.membership_type
            user_allowed = book.allowedMembership.all()
            context = {'object': None}
            if user_allowed.filter(membership_type=user_membership_type).exists():
                context = {'object': page}
            return render(request, "catalog/page_detail.html", context)
        except Book.DoesNotExist:
            raise Http404("Book with id {0} does not exist".format(book_id))
        except Page.DoesNotExist:
            raise Http404("Page with id {0} does not exist".format(page_id))
        except Customer.DoesNotExist:
            raise Http404("Cutomer does not exist")
        except Exception as e: 
            raise Exception(str(e)) 
hygull
  • 8,464
  • 2
  • 43
  • 52
  • i've tried this and i got this kind of error.. Reverse for 'page-detail' with arguments '('2',)' not found. 1 pattern(s) tried: ['books/(?P[^/]+)/page/(?P[^/]+)$'] – Giranyx Pornasdoro Mar 19 '19 at 22:43
  • Can you paste the urlpattern for this and tell me the version of Django you are using? – hygull Mar 20 '19 at 01:42
  • Also check if you are using `{% url 'page-detail' book_id=1 page_id=2 %}` etc. in template. There may be number of reasons for this. It is urlpattern related issue. Also tell me when you got this error. While clicking on any link or just after running `manage.py runserver` – hygull Mar 20 '19 at 01:52
  • Check https://stackoverflow.com/questions/19336076/django-reverse-for-detail-with-arguments-and-keyword-arguments-n and https://docs.djangoproject.com/en/2.1/ref/templates/builtins/ for your version of Django (for url template tag, if this is issue after clicking on any link of UI or because of invalid template tag syntax). – hygull Mar 20 '19 at 01:55
  • Hi~ i think its getting closer. i Updated my post again. – Giranyx Pornasdoro Mar 20 '19 at 20:20
  • I have updated my answer and also changed the urlpattern. Please try and let me know if you are still stuck. As **Book** is the parent model of **Page** model, you don't need to pass 2 parameters on urlpattern now. You can get a book's **pk** using `page.book.pk`. Number of pages can belong to a single **Book** instance. – hygull Mar 21 '19 at 00:42
  • And here we are seeing Exception while fetching Book instance, this time you won't get it as **page.book.pk** will give you the related unique instance of **Book** for any **Page**. – hygull Mar 21 '19 at 00:58
  • 1
    It's now working. I will be testing it to check if there will be any other issues. So, thank you for getting me at this point. – Giranyx Pornasdoro Mar 21 '19 at 11:34
0

The reason you are getting this error is because you are using get_object_or_404 to get a single book, but the issue is your query is returning multiple books. This usually happens when there is no unique constraint on the field you use to lookup the book with (i.e. getting object by the title "Jungle Book" returns two books with the same title).

To fix this, get the object using a field that is guaranteed to be unique, such as an id. For example:

urls.py

path('page-details/<str:book_id>/<str:page_id>/', PageDetailView.as_view(), name='page_details'),

views.py

class PageDetailView(LoginRequiredMixin, generic.View):
    def get(self, request, *args, **kwargs):
        book = get_object_or_404(id=kwargs['book_id'])
        page = get_object_or_404(id=kwargs['page_id'])
        [...]
Hybrid
  • 6,741
  • 3
  • 25
  • 45
  • i've tried this but i got Reverse for 'page-detail' not found. 'page-detail' is not a valid view function or pattern name. error. – Giranyx Pornasdoro Mar 19 '19 at 22:35
  • @GiranyxPornasdoro anywhere you have “page-detail” in `reverse`, change it to “page_details” to match the URL name I used – Hybrid Mar 19 '19 at 22:38
  • uhm its still the same i think.. it says NoReverseMatch at /book-detail/1/ Reverse for 'page_details' with arguments '('1',)' not found. 1 pattern(s) tried: ['page\\-details/(?P[^/]+)/(?P[^/]+)/$'] – Giranyx Pornasdoro Mar 19 '19 at 23:26
  • @GiranyxPornasdoro add your `urls.py` in your post so I can see whats going on – Hybrid Mar 19 '19 at 23:33