butterfly
butterfly

Reputation: 47

Django error: NoReverseMatch

I'm using Django 1.10 and python 3.4

The precise error is

NoReverseMatch at /movies/movie/Twilight/
Reverse for 'movie-details' with arguments '(8,)' and keyword arguments '{}' not found. 1 pattern(s) tried: ['movies/movie/(?P<movie_id>\\d+)|(?P<movie_name>[a-zA-Z\\ ]+)/$']

The error is caused by this line: {% url 'moviesrating:movie-details' movie.id %} in template moviesrating/select_movie.html

In file moviesrating/urls.py, which is correctly included in the main urls file, there are those lines:

app_name = 'moviesrating'

urlpatterns = [
    url(r'^movie/(?P<movie_id>\d+)|(?P<movie_name>[a-zA-Z\ ]+)/$', view_movie, name = 'movie-details'),
]

which refers to function view_movie in moviesrating/views.py:

def view_movie(request, movie_id, movie_name):
    if movie_id:
        movie = get_object_or_404(Movie, pk = movie_id)
    elif movie_name:
        try:
            movie = get_object_or_404(Movie, name = movie_name)
        except MultipleObjectsReturned: # There are two movies named 'Twilight'
            movies = get_list_or_404(Movie, name = movie_name)
            return render(request, "moviesrating/select_movie.html", {'movies': movies})
    else:
        movie = None
    return render(request, "moviesrating/movie.html", {'movie': movie})

The purpose of the url /movies/movie/... is to show a movie found by name or by id, the specific url pattern comes from this need.

The point is that the error shows that even if it doesn't find the reverse match it finds the right url pattern so I thought the pattern didn't match. Then I tried to change the line to:

{% url 'moviesrating:movie-details' movie.id %}
{% url 'moviesrating:movie-details' movie_id=movie.id movie_name=None %}
{% url 'moviesrating:movie-details' movie.id None %}
{% url 'moviesrating.views.view_movie' movie.id %}
{% url 'moviesrating.views.view_movie' movie_id=movie.id %}
{% url 'moviesrating.views.view_movie' movie_id=movie.id movie_name=None %}

but none of them worked.

Viewing the link in the browser (ex. http://localhost:8081/movies/movie/8) works, so it's not that page that causes the problem.

At this point really I can't understand the problem, I've followed the passages explained in the django docs and read a lot of questions already asked but none of them solved my problem.

If something is unclear or more information is needed please let me know and I will edit the post. Thanks

Here is the full stacktrace of the error:

Environment:


Request Method: GET
Request URL: http://localhost:8081/movies/movie/Twilight/

Django Version: 1.10.1
Python Version: 3.5.2
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'todolist.apps.TodolistConfig',
 'moviesrating.apps.MoviesratingConfig']
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 C:\Users\fra\Programmazione\Python\myserver\moviesrating\templates\moviesrating\select_movie.html, error at line 12
   Reverse for 'movie-details' with arguments '(8,)' and keyword arguments '{}' not found. 1 pattern(s) tried: ['movies/movie/(?P<movie_id>\\d+)|(?P<movie_name>[a-zA-Z\\ ]+)/$']   2 : <html>
   3 : <head>
   4 : <meta charset="ISO-8859-1">
   5 : <title>Choice your movie</title>
   6 : </head>
   7 : <body>
   8 : <h2>Choice of which movie named {{ movies.0.name }} would you see the details</h2>
   9 : 
   10 : <ul>
   11 : {% for movie in movies %}
   12 :     <li> {% url 'moviesrating:movie-details' movie.id %} </li>
   13 :     <li><a href="{{ movie_url }}">{{ movie.name }} del {{ movie.year }} diretto da {{ movie.director }}</a></li>
   14 : {% endfor %}
   15 : </ul>
   16 : </body>
   17 : </html>
   18 : 

Traceback:

File "C:\Users\fra\Programmazione\Python\myserver\moviesrating\views.py" in view_movie
  31.             movie = get_object_or_404(Movie, name = movie_name)

File "C:\Program Files (x86)\Python\lib\site-packages\django\shortcuts.py" in get_object_or_404
  85.         return queryset.get(*args, **kwargs)

File "C:\Program Files (x86)\Python\lib\site-packages\django\db\models\query.py" in get
  389.             (self.model._meta.object_name, num)

During handling of the above exception (get() returned more than one Movie -- it returned 2!), another exception occurred:

File "C:\Program Files (x86)\Python\lib\site-packages\django\core\handlers\exception.py" in inner
  39.             response = get_response(request)

File "C:\Program Files (x86)\Python\lib\site-packages\django\core\handlers\base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "C:\Program Files (x86)\Python\lib\site-packages\django\core\handlers\base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "C:\Users\fra\Programmazione\Python\myserver\moviesrating\views.py" in view_movie
  34.             return render(request, "moviesrating/select_movie.html", {'movies': movies})

File "C:\Program Files (x86)\Python\lib\site-packages\django\shortcuts.py" in render
  30.     content = loader.render_to_string(template_name, context, request, using=using)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\loader.py" in render_to_string
  68.     return template.render(context, request)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\backends\django.py" in render
  66.             return self.template.render(context)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\base.py" in render
  208.                     return self._render(context)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\base.py" in _render
  199.         return self.nodelist.render(context)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\base.py" in render
  994.                 bit = node.render_annotated(context)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\base.py" in render_annotated
  961.             return self.render(context)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\defaulttags.py" in render
  209.                     nodelist.append(node.render_annotated(context))

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\base.py" in render_annotated
  961.             return self.render(context)

File "C:\Program Files (x86)\Python\lib\site-packages\django\template\defaulttags.py" in render
  439.             url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)

File "C:\Program Files (x86)\Python\lib\site-packages\django\urls\base.py" in reverse
  91.     return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))

File "C:\Program Files (x86)\Python\lib\site-packages\django\urls\resolvers.py" in _reverse_with_prefix
  392.             (lookup_view_s, args, kwargs, len(patterns), patterns)

Exception Type: NoReverseMatch at /movies/movie/Twilight/
Exception Value: Reverse for 'movie-details' with arguments '(8,)' and keyword arguments '{}' not found. 1 pattern(s) tried: ['movies/movie/(?P<movie_id>\\d+)|(?P<movie_name>[a-zA-Z\\ ]+)/$']

Upvotes: 3

Views: 805

Answers (3)

Stonecold
Stonecold

Reputation: 418

You can add parentheses around capturing groups:

urlpatterns = [
    url(r'^movie/((?P<movie_id>\d+)|(?P<movie_name>[a-zA-Z\ ]+))/$', view_movie, name = 'movie-details'),
]

and add defaults to view parameters:

def view_movie(request, movie_id=None, movie_name=None):
    if movie_id:
        return HttpResponse('1')
    if movie_name:
        return HttpResponse('2')

Idk why but i've tried this on my project and it works.

Upvotes: 0

Matt Cremeens
Matt Cremeens

Reputation: 5151

You are only sending one parameter to your view, though it expects two. If you want to stick with one that can be interpreted as an id or a name, why not accept an alpha-numeric parameter

urlpatterns = [
url(r'^movie/(?P<movie_id>[A-z0-9]+)/$', view_movie, name = 'movie-details'),
]

and then check in your view which one it is so you know if you should treat it as an id or a name

def view_movie(request, movie_id):
     try: 
         int(movie_id)
         movie_name = False
    except:
         movie_name = True

    if not movie_name:
        movie = get_object_or_404(Movie, pk = movie_id)
    elif movie_name:

    # etc.

Upvotes: 0

knbk
knbk

Reputation: 53669

Django's reverse() cannot handle disjunctive patterns (using a |) outside of a capturing group. It's one of those things you'd hope someone would've fixed somewhere in the past 10 or so years, but this limitation has been around since 1.0.

A workaround is to split up the pattern into two patterns:

urlpatterns = [
    url(r'^movie/(?P<movie_id>\d+)/$', view_movie, name='movie-details'),
    url(r'^movie/(?P<movie_name>[a-zA-Z\ ]+)/$', view_movie, name='movie-details'),
]

You'll need to add a default to both parameters:

def view_movie(request, movie_id=None, movie_name=None):
    ...

Upvotes: 1

Related Questions