ValueError: No route found for path 'ws/student/start-exam/2'.

consumer.py
‘’'import json
import asyncio
from datetime import datetime, timedelta
from channels.generic.websocket import AsyncWebsocketConsumer
from django.db.models import ObjectDoesNotExist

class CountdownConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.id = self.scope[‘url_route’][‘kwargs’][‘id’]
self.running = True # Flag to control the countdown loop
await self.channel_layer.group_add(self.id, self.channel_name)
await self.accept()

    # Send initial countdown data
    asyncio.create_task(self.send_countdown())

async def disconnect(self, close_code):
    self.running = False
    await self.channel_layer.group_discard(self.id, self.channel_name)

async def send_countdown(self):
    from .models import Course  # Import your model here

    try:
        # Fetch the timer's duration
        timer = await asyncio.to_thread(Course.objects.get, id=self.id)
        if not isinstance(timer.time_duration, timedelta):
            raise ValueError("time_duration must be a timedelta object")

        # Calculate end time
        end_time = datetime.now() + timer.time_duration

        # Loop to send countdown updates
        while self.running:
            remaining = (end_time - datetime.now()).total_seconds()

            if remaining <= 0:
                await self.send(json.dumps({'remaining': 0}))
                break

            await self.send(json.dumps({'remaining': int(remaining)}))
            await asyncio.sleep(1)  # Update every second

    except ObjectDoesNotExist:
        await self.send(json.dumps({'error': 'Timer not found'}))
    except Exception as e:
        await self.send(json.dumps({'error': str(e)}))

‘’’
start-exam.html
‘’’ {% extends ‘student/studentbase.html’ %} {% block content %} {%load static%}

Time left:

Course: {{course.course_name}}

{% for q in questions%}

{{ forloop.counter }}. {{q.question}}

[{{q.marks}} Marks]

    <div class="form-check mx-4 d-flex align-items-center">
        <input class="form-check-input" type="radio" name="{{ forloop.counter }}" id="{{q.option1}}" value="Option1">
        <label class="form-check-label ml-3" for="option1">
          {{q.option1}}
        </label>
    </div>


    <div class="form-check mx-4 d-flex align-items-center">
        <input class="form-check-input" type="radio" name="{{ forloop.counter }}" id="{{q.option2}}" value="Option2">
        <label class="form-check-label ml-3" for="option2">
          {{q.option2}}
        </label>
    </div>


    <div class="form-check mx-4 d-flex align-items-center">
        <input class="form-check-input" type="radio" name="{{ forloop.counter }}" id="{{q.option3}}" value="Option3">
        <label class="form-check-label ml-3" for="option3">
          {{q.option3}}
        </label>
    </div>


    <div class="form-check mx-4 d-flex align-items-center">
        <input class="form-check-input" type="radio" name="{{ forloop.counter }}" id="{{q.option4}}" value="Option4">
        <label class="form-check-label ml-3" for="option4">
          {{q.option4}}
        </label>
    </div>

    {% endfor %}
    <input class="btn btn-primary btn-lg" style="border-radius: 0%;" type="submit" value="Submit Answers"><br>
    <button class="btn btn-danger">go back</button>
</form>







{% endblock content %}‘’’
asgi.py
‘’'“”"
ASGI config for ExamManagementSystem project.

It exposes the ASGI callable as a module-level variable named application.

For more information on this file, see

“”"

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter,URLRouter
from channels.auth import AuthMiddlewareStack
import exam.routing
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘ExamManagementSystem.settings’)

application = ProtocolTypeRouter({
“http”: get_asgi_application(),
“websocket”: AuthMiddlewareStack(
URLRouter(
exam.routing.websocket_urlpatterns
)
),
})
‘’’
setting.py
‘’'“”"
Django settings for ExamManagementSystem project.

Generated by ‘django-admin startproject’ using Django 5.1.3.

For more information on this file, see

For the full list of settings and their values, see

“”"
import os
from pathlib import Path

Build paths inside the project like this: BASE_DIR / ‘subdir’.

BASE_DIR = Path(file).resolve().parent.parent

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(file)))
TEMPLATE_DIR = os.path.join(BASE_DIR,‘templates’)
STATIC_DIR=os.path.join(BASE_DIR,‘static’)
MEDIA_ROOT=os.path.join(BASE_DIR,‘static’)

Quick-start development settings - unsuitable for production

See Deployment checklist | Django documentation | Django

SECURITY WARNING: keep the secret key used in production secret!

SECRET_KEY = ‘django-insecure-50#egag5^2nj4s#hlx15(m+!xi6+cb05lr%l6ata46n(8w$u’

SECURITY WARNING: don’t run with debug turned on in production!

DEBUG = True

ALLOWED_HOSTS =

Application definition

INSTALLED_APPS = [
‘channels’,
‘daphne’,
‘jazzmin’,
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘teacher’,
‘student’,
‘exam’,
‘widget_tweaks’,
‘crispy_forms’,
‘crispy_bootstrap4’,
]
ASGI_APPLICATION = ‘ExamManagementSystem.asgi.application’
CRISPY_TEMPLATE_PACK = ‘bootstrap4’
CHANNEL_LAYERS = {
“default”:{
“BACKEND”:“channels.layers.InMemoryChannelLayer”,
},
}
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’,
]
JAZZMIN_SETTINGS = {
# title of the window (Will default to current_admin_site.site_title if absent or None)
“site_title”: “Admin Portal”,

# Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_header":  "Admin Portal",

# Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_brand": "Admin Dashboard",

# Logo to use for your site, must be present in static files, used for brand on top left


# Logo to use for your site, must be present in static files, used for login form logo (defaults to site_logo)
"login_logo": None,

# Logo to use for login form in dark themes (defaults to login_logo)
"login_logo_dark": None,

# CSS classes that are applied to the logo above
"site_logo_classes": "img-circle",

# Relative path to a favicon for your site, will default to site_logo if absent (ideally 32x32 px)
"site_icon": None,

# Welcome text on the login screen
"welcome_sign": "Welcome to the Exam management",

# Copyright on the footer


# List of model admins to search from the search bar, search bar omitted if excluded
# If you want to use a single search field you dont need to use a list, you can use a simple string 
"search_model": ["auth.User", "auth.Group"],

# Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user
"user_avatar": None,

############
# Top Menu #
############

# Links to put along the top menu
"topmenu_links": [

    # Url that gets reversed (Permissions can be added)
    {"name": "Home",  "url": "admin:index", "permissions": ["auth.view_user"]},

    # external url that opens in a new window (Permissions can be added)
    {"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True},

    # model admin to link to (Permissions checked against model)
    {"model": "auth.User"},

    # App with dropdown menu to all its models pages (Permissions checked against models)
    {"app": "student"},
],

#############
# User Menu #
#############

# Additional links to include in the user menu on the top right ("app" url type is not allowed)

“”" “usermenu_links”: [
{“name”: “Support”, “url”: “Issues · farridav/django-jazzmin · GitHub”, “new_window”: True},
{“model”: “auth.user”}
],
“”"
#############
# Side Menu #
#############

# Whether to display the side menu
"show_sidebar": True,

# Whether to aut expand the menu
"navigation_expanded": True,

# Hide these apps when generating side menu e.g (auth)
"hide_apps": [],

# Hide these models when generating side menu (e.g auth.user)
"hide_models": [],

# List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models)
"order_with_respect_to": ["auth", "books", "books.author", "books.book"],

# Custom links to append to app groups, keyed on app name
"custom_links": {
    "books": [{
        "name": "Make Messages", 
        "url": "make_messages", 
        "icon": "fas fa-comments",
        "permissions": ["books.view_book"]
    }]
},

# Custom icons for side menu apps/models See https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.0.2,5.0.3,5.0.4,5.0.5,5.0.6,5.0.7,5.0.8,5.0.9,5.1.0,5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.9.0,5.8.2,5.8.1,5.7.2,5.7.1,5.7.0,5.6.3,5.5.0,5.4.2
# for the full list of 5.13.0 free icon classes
"icons": {
    "auth": "fas fa-users-cog",
    "auth.user": "fas fa-user",
    "auth.Group": "fas fa-users",
},
# Icons that are used when one is not manually specified
"default_icon_parents": "fas fa-chevron-circle-right",
"default_icon_children": "fas fa-circle",

#################
# Related Modal #
#################
# Use modals instead of popups
"related_modal_active": False,

#############
# UI Tweaks #
#############
# Relative paths to custom CSS/JS scripts (must be present in static files)
"custom_css": None,
"custom_js": None,
# Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise)
"use_google_fonts_cdn": True,
# Whether to show the UI customizer on the sidebar
"show_ui_builder": False,

###############
# Change view #
###############
# Render out the change view as a single form, or in tabs, current options are
# - single
# - horizontal_tabs (default)
# - vertical_tabs
# - collapsible
# - carousel
"changeform_format": "horizontal_tabs",
# override change forms on a per modeladmin basis
"changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"},
# Add a language dropdown into the admin
# "language_chooser": True,

}

ROOT_URLCONF = ‘ExamManagementSystem.urls’
AUTH_USER_MODEL = ‘student.CustomUser’

TEMPLATES = [
{
‘BACKEND’: ‘django.template.backends.django.DjangoTemplates’,
‘DIRS’: [TEMPLATE_DIR],
‘APP_DIRS’: True,
‘OPTIONS’: {
‘context_processors’: [
‘django.template.context_processors.debug’,
‘django.template.context_processors.request’,
‘django.contrib.auth.context_processors.auth’,
‘django.contrib.messages.context_processors.messages’,
],
},
},
]

WSGI_APPLICATION = ‘ExamManagementSystem.wsgi.application’

Database

Settings | Django documentation | Django

DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.sqlite3’,
‘NAME’: os.path.join(BASE_DIR, ‘db.sqlite3’),
}
}

Password validation

Settings | Django documentation | Django

AUTH_PASSWORD_VALIDATORS = [
{
‘NAME’: ‘django.contrib.auth.password_validation.UserAttributeSimilarityValidator’,
},
{
‘NAME’: ‘django.contrib.auth.password_validation.MinimumLengthValidator’,
‘OPTIONS’: {
‘min_length’: 5,
}
},
{
‘NAME’: ‘django.contrib.auth.password_validation.CommonPasswordValidator’,
},
{
‘NAME’: ‘django.contrib.auth.password_validation.NumericPasswordValidator’,
},
]

Internationalization

Internationalization and localization | Django documentation | Django

LANGUAGE_CODE = ‘en-us’

TIME_ZONE = ‘UTC’

USE_I18N = True

USE_TZ = True

Static files (CSS, JavaScript, Images)

How to manage static files (e.g. images, JavaScript, CSS) | Django documentation | Django

STATIC_URL = ‘/static/’
STATICFILES_DIRS=[
STATIC_DIR,
]
LOGIN_REDIRECT_URL=‘/’

Default primary key field type

Settings | Django documentation | Django

DEFAULT_AUTO_FIELD = ‘django.db.models.BigAutoField’
‘’’
routing.py
‘’'from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
re_path(r’ws/student/start-exam/(?P\d+)/$‘, consumers.CountdownConsumer.as_asgi())
]’‘’

it gives me this error in the console: WebSocket connection to ‘ws://127.0.0.1:8000/ws/student/start-exam/2’ failed:

Side note: When trying to mark code using the three backticks, you need to use the backtick - ` character and not the apostrophe - ' or “smart quote” (‘ or ’) characters.
Also, the line of three backticks - ``` must be lines by themselves. They must not be a part of any other line.

The first thing I’ve noticed is that your url is defined as:

But the url you’re attempting to access:

doesn’t have the trailing slash on it - therefore it doesn’t match.

There may be more issues than this, but this was the first I found.

the error is solved. but it doesn’t display the countdwon

That’s a different issue. Please explain in more detail what it is that you’re expecting to see.

Look at your server logs, are there any messages in it that may help diagnose this issue?

Is this something you’re running in the development environment with runserver, or is this a production deployment?

Also, if you want people to read all the code you posted, I would suggest that you correct your post to properly mark your code as preformatted text.

okay.
I am developing online exam system in which countdown timer should be retrieved from database and displayed for user in real time.
consumer.py

import asyncio
from datetime import datetime, timedelta
from channels.generic.websocket import AsyncWebsocketConsumer
from django.db.models import ObjectDoesNotExist

class CountdownConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.id = self.scope['url_route']['kwargs']['id']
        self.running = True  # Flag to control the countdown loop
        await self.channel_layer.group_add(self.id, self.channel_name)
        await self.accept()
         
        # Send initial countdown data
        asyncio.create_task(self.send_countdown())

    async def disconnect(self, close_code):
        self.running = False
        await self.channel_layer.group_discard(self.id, self.channel_name)

    async def send_countdown(self):
        from .models import Course  # Import your model here

        try:
            # Fetch the timer's duration
            timer = await asyncio.to_thread(Course.objects.get, id=self.id)
            if not isinstance(timer.time_duration, timedelta):
                raise ValueError("time_duration must be a timedelta object")

            # Calculate end time
            end_time = datetime.now() + timer.time_duration

            # Loop to send countdown updates
            while self.running:
                remaining = (end_time - datetime.now()).total_seconds()

                if remaining <= 0:
                    await self.send(json.dumps({'remaining': 0}))
                    break

                await self.send(json.dumps({'remaining': int(remaining)}))
                await asyncio.sleep(1)  # Update every second

        except ObjectDoesNotExist:
            await self.send(json.dumps({'error': 'Timer not found'}))
        except Exception as e:
            await self.send(json.dumps({'error': str(e)}))

routing.py

from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/student/start-exam/(?P<id>\w+)$', consumers.CountdownConsumer.as_asgi())
]```
asgi.py
"""
ASGI config for ExamManagementSystem project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter,URLRouter
from channels.auth import AuthMiddlewareStack
import exam.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ExamManagementSystem.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(
            exam.routing.websocket_urlpatterns
        )
    ),
})
     ```
and my setting.py
ASGI_APPLICATION = 'ExamManagementSystem.asgi.application'

CHANNEL_LAYERS = {
    "default":{ 
        "BACKEND":"channels.layers.InMemoryChannelLayer",
    },
}

javascript file

        const path = window.location.pathname; //current path
        //extrat integer using regex
        const match = path.match(/\d+/)
        const id = match?parseInt(match[0],10):null;
        const socket = new WebSocket(`ws://${window.location.host}/ws/student/start-exam/${id}`);
       
        socket.onmessage = function (e) {
            const data = JSON.parse(e.data);
            const timerElement = document.getElementById('timer');
            if (data.remaining_time>0){
                timerElement.textContent ='${math.ceil(data.remaining_time)} seconds';
            }
            else {
                timerElement.textContent = data.message;
            }
        };
        // send exam end time
       
 
       
    function saveAns() {

        var ele = document.getElementsByTagName('input');
        for (i = 0; i < ele.length; i++) {
            if (ele[i].type = "radio") {
                if (ele[i].checked) {
                    setCookie(ele[i].name, ele[i].value, 3)
                }
            }
        }

    }

    function setCookie(cname, cvalue, exdays) {
        var d = new Date();
        d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
        var expires = "expires=" + d.toUTCString();
        document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
    }

    
</script>```

What is the name of the key in the json structure that you are sending to the browser?

What is the name of the key that you are looking for in your JavaScript?

i am looking for model course.time_duration from models.py here is the code…

from django.utils.timezone import now
from datetime import timedelta
from student.models import CustomUser
# Create your models here.
class Course(models.Model):
   course_name = models.CharField(max_length=50)
   number_of_questions = models.PositiveIntegerField()
   total_marks = models.PositiveIntegerField(default=None)
   time_duration = models.DurationField(help_text='duration of exam',null=True)
   start_time =models.DateTimeField(default=now) 
   def __str__(self): 
        return self.course_name
   def save(self, *args, **kwargs):
        if not isinstance(self.time_duration, timedelta):
            raise ValueError("time_duration must be a valid timedelta object")
        super().save(*args, **kwargs)
class Question(models.Model):
    course=models.ForeignKey(Course,on_delete=models.CASCADE)
    marks=models.PositiveIntegerField()
    question=models.CharField(max_length=600)
    option1=models.CharField(max_length=200)
    option2=models.CharField(max_length=200)
    option3=models.CharField(max_length=200)
    option4=models.CharField(max_length=200)
    cat=(('Option1','Option1'),('Option2','Option2'),('Option3','Option3'),('Option4','Option4'))
    answer=models.CharField(max_length=200,choices=cat)
    class Meta:
        verbose_name ='question'
        verbose_name_plural = 'questions'
    def __str__(self):
        return self.course.course_name 
class Result(models.Model):
    student = models.ForeignKey(CustomUser,on_delete=models.CASCADE)
    exam = models.ForeignKey(Course,on_delete=models.CASCADE)
    marks = models.PositiveIntegerField()
    date = models.DateTimeField(auto_now=True)
    def __str__(self):
        return self.student.first_name


    ```

i am new for django channels and little bit confused please help

In the send_countdown method of your consumer, you are sending a Python dict to the browser.

That dict has a “key” to identify the name of the value being returned.

What name are you using in that dict?
(This has nothing to do with your database or your models, only your consumer where you are sending the value.)

In your JavaScript, you are receiving that data from the consumer, as a JavaScript object. Within that object, you are trying to access the value being returned by the consumer. What is the name of the value that you are looking for in your JavaScript?

The keys is ‘remaining’

Excellent!

Now, in your JavaScript, what key are you looking for?

(If you’re not sure, then answer this - what line in your JavaScript is trying to get that value from the data being returned to the browser?)

In Line 77 i tried to display the remaining time which was retrieved from consume.

In Line 9 i tried to display the remaining time which was retrieved from server

The consumer is sending a JSON object back to the browser. What line of code (post the code, not a line number) gets the value that you’re looking for from that JSON?

I’m not looking for what you’re trying to display, but where you’re extracting that from the packet sent through the websocket.

full code

            const data = JSON.parse(e.data);
            const timerElement = document.getElementById('timer');
            if (data.remaining_time>0){
                timerElement.textContent ='${math.ceil(data.remaining_time)} seconds';
            }
            else {
                timerElement.textContent = data.message;
            }
        };

Which one line (or better yet, which expression in that line) is trying to get the value from the JSON?

this line
timerElement.textContent ='${math.ceil(data.remaining_time)} seconds'

Excellent.

So, what is the name of the entry in the data object that you’re trying to retrieve?

Is that the name of the value that you’re sending from the consumer? (You have previously identified this at post 11 in this thread)

Here is the data object

socket.onmessage = function (e) {
            const data = JSON.parse(e.data);```