Simple Django-swampdragon + AngularJS real-time app - javascript

I use django-swampdragon + angularjs to create simple django app which shows website requests in real-time.
All of the Django logic is okay, but when I try make it respond in real-time the system kind of fails. I think the problem is in Angular, since I haven't used it before.
So, the django-swampdragon part of code:
# routers.py
from swampdragon import route_handler
from swampdragon.route_handler import ModelRouter
from .serializers import HttpRequestSerializer
from .models import HttpRequest
class HttpRequestRouter(ModelRouter):
serializer_class = HttpRequestSerializer
model = HttpRequest
route_name = 'activity-route'
def get_object(self, **kwargs):
return self.model.objects.get(pk=kwargs['pk'])
def get_query_set(self, **kwargs):
return self.model.all()
route_handler.register(HttpRequestRouter)
The Angular.js part of code
// controllers.js
var ActivityControllers = angular.module('ActivityControllers', []);
ActivityControllers.controller('ActivityCtrl', ['$scope', '$dragon', function ($scope, $dragon) {
$scope.channel = 'httprequests';
$scope.datasource = [];
// Subscribe to the activity router
$dragon.onReady(function() {
$dragon.subscribe('activity-route', $scope.channel, {}).then(function(response) {
this.dataMapper = new DataMapper(response.data);
});
$dragon.getList('activity-route', {}).then(function(response) {
$scope.datasource = response.data
});
});
$dragon.onChannelMessage(function(channels, new_request) {
if (indexOf.call(channels, $scope.channel) > -1) {
$scope.$apply(function() {
$scope.datasource.unshift(new_request);
});
}
});
}]);
And, finally, my template part:
<div ng-controller="ActivityCtrl">
<ul ng-repeat="req in datasource">
<li>
<span class="request_date">{$ req.date $} ...</span>
</li>
</ul>
</div>
When I run server.py and open browser on that url/port (http://localhost:9999/) i have this error (in browser):
Traceback (most recent call last):
File "...lib/python3.4/site-packages/tornado/web.py", line 1323, in _execute
result = self.prepare()
File "...lib/python3.4/site-packages/tornado/web.py", line 2014, in prepare
raise HTTPError(self._status_code)
tornado.web.HTTPError: HTTP 404: Not Found
And also this error on the console:
-------- SwampDragon ------
Running SwampDragon on 127.0.0.1:9999
DRAGON_URL: http://localhost:9999/
Version 0.4.2
Debug: True
Quit the server with ctrl+c
---------------------------
WARNING:tornado.access:404 GET / (127.0.0.1) 206.58ms
And, of course, my page with request don't work either.
PLease help me find the error!

Just to be sure, you are running your django server and your swmapdragon server, correct? As in you are doing both:
python server.py
python manage.py runserver

Related

How to get the laravel route name on js

Hello guys I have search and I didnt find anything, do you know how to get a laravel route name on js?
For example on php we can do $name = Route::currentRouteName();
In your layout.blade.php
<body data-page="{{ Route::currentRouteName() }}">
#yield('content')
<script>
var currentRouteName = document.body.dataset.page;
</script>
</body>
If you for some reason don't like injecting stuff in your DOM just to read it with JavaScript, you can do something else:
routes/web.php
Route::get('your-route', function() {
return response()
->view('your-view', compact('your-data'))
->header('route-name', 'your-route');
})->name('your-route');
your-view.blade.php
var req = fetch(document.location, {
'method': 'OPTIONS'
}).then((res) => alert(res.headers.get('route-name')));
Take a look at Ziggy, this npm package helps to integrate Laravel named routes with your frontend, with it you can easely get current route name and even acces routes for all your application endpoint just like you would do with Laravel:
https://github.com/tighten/ziggy
Examples From the Documentation:
//app.js
//get current route name
// Route called 'events.index', with URI '/events'
// Current window URL is https://ziggy.test/events
route().current(); // 'events.index'
// routes/web.php
Route::get('posts/{post}', fn (Request $request, Post $post) => /* ... */)->name('posts.show');
// app.js
route('posts.show', 1); // 'https://ziggy.test/de/posts/1'

Chrome browser error when executing multiple AJAX requests on Python-Flask application

I am currently using command "python3 -m flask run -h localhost -p 8081" to run a flask server on localhost:8081. The user is able to click buttons to trigger events; additionally, data updates are dynamically pushed to plotly graphs triggered from a javascript interval.
Issue occurs on google-Chrome when both asynchronous AJAX requests are running at the same time-->
There are controls on the page (such as a start button) that handle other actions. When running mozilla firefox, both of these requests will execute with no problems. However, when using google Chrome, the button click request will start to hang after a few seconds, taking longer to reach the "req.done" function until eventually crashing the page. In the code below, I have written some console logs that show in the browser console that the button click event stops replying with "randomstring" from the python route a few seconds after running. This seems to be an issue specific to chrome, as this works consistently on firefox.
Please advise - how could I change this to work across both browsers reliably?
CODE
here are the javascript-jquery AJAX requests:
var numerical_count_rate = document.getElementById("totalCounts");
var numerical_error_rate = document.getElementById("errorRate");
var start_btn = document.getElementById("startButtonClick");
var start_btn_clicked = function (){
console.log("button clicked...")
req = $.ajax({
url : "/_start_button_clicked",
type : "POST",
});
console.log("button got this far...")
req.done(function(data){
console.log("button got even further !!")
var data_test = JSON.parse(data)
var update = data_test.random_string
console.log(update)
});
};
var updateInterval = setInterval(update_values, 1000);
var counts = 0;
console.log("update_interval fired..")
function update_values(){
req = $.ajax({
url : "/_extend_plot",
type : "POST",
});
req.done(function(data){
var updates = JSON.parse(data);
var count_rate_update = {x: [[updates.x_count_rate]],y: [[updates.y_count_rate]]};
var error_rate_update = {x: [[updates.x_error_rate]],y: [[updates.y_error_rate]]};
Plotly.extendTraces('plotly_countrate',count_rate_update, [0], 50);
Plotly.extendTraces('plotly_errorrate',error_rate_update, [0], 50);
numerical_count_rate.innerHTML = "Total Count Rate: " + updates.y_count_rate.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
numerical_error_rate.innerHTML = "Error Rate: " + parseFloat(updates.y_error_rate).toFixed(3).toString() + "%";
});
window.onresize = function() {
Plotly.Plots.resize( 'plotly_countrate' );
Plotly.Plots.resize( 'plotly_errorrate' )
};
counts++;
console.log(counts)
}
Here are snippets from the main thread --> the Flask routes that the ajax requests reference in the main app.py file:
from flask import Flask, render_template, request
import queue
import threading
import plotly
import json
import pandas as pd
import numpy as np
import random
import datetime
app = Flask(__name__)
#app.route("/", methods=['GET'])
def index():
initial_graphs_json = create_plots()
return render_template("index.html", count_rate_plot=initial_graphs_json[0], error_rate_plot=initial_graphs_json[1])
#app.route("/_extend_plot", methods=['GET', 'POST'])
def push_update():
timestamp = get_datetime()
updated_data = queue_q.get()
with queue_q.mutex:
queue_q.queue.clear()
client_post_updates = dict(
x_count_rate=timestamp,
x_error_rate=timestamp,
y_count_rate=updated_data["val0"] + updated_data["val1"],
y_error_rate=updated_data["val2"])
updatesJSON = json.dumps(client_post_updates, cls=plotly.utils.PlotlyJSONEncoder)
return updatesJSON
#app.route("/_start_button_clicked", methods=['GET', 'POST'])
def startstop_clicked():
update_dict = dict(
random_string="randomstring"
)
print("Python click method triggered...")
update = json.dumps(update_dict)
return update
if __name__ == "__main__":
app.run(host="0.0.0.0", port="8081", debug=True)
running- python version 3.7, jquery version 3.5.1
Update: SOLVED
Issue solved by converting functions to asynchronous calls and changing short to long polling. It seems chrome has built in functionality to leave requests in "pending state", and the short polling interval was filling up browser queue with new requests, which caused stalling. Still not completely clear why this does not happen on firefox, but long polling based on a single timer under control of mutex and common memory between threads on the server side is much better overall practice. This way, the frequency of pushed updates is controlled only by data being available, rather then being polled on a timer.
New JS code:
var start_btn_clicked = async function (){
req = $.ajax({
url : "/_start_button_clicked",
type : "POST",
});
req.done(async function(data){
var data_test = JSON.parse(data);
var update = data_test.random_string;
});
};
update_values();
async function update_values(){
req = $.ajax({
url : "/_extend_plot",
type : "POST",
async: true,
cache: false,
timeout: 30000,
success: async function(data){
console.log(req);
var updates = JSON.parse(data);
var count_rate_update = {x: [[updates.x_count_rate]],y: [[updates.y_count_rate]]};
var error_rate_update = {x: [[updates.x_error_rate]],y: [[updates.y_error_rate]]};
Plotly.extendTraces('plotly_countrate',count_rate_update, [0], 50);
Plotly.extendTraces('plotly_errorrate',error_rate_update, [0], 50);
numerical_count_rate.innerHTML = "Total Count Rate: " + updates.y_count_rate.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
numerical_error_rate.innerHTML = "Error Rate: " + parseFloat(updates.y_error_rate).toFixed(3).toString() + "%";
setTimeout(update_values, 1000);
},
});
window.onresize = async function() {
Plotly.Plots.resize( 'plotly_countrate' );
Plotly.Plots.resize( 'plotly_errorrate' );
};
}

How to make multi-file upload widget in Ipython Notebook?

I would like to make a widget in Ipython Notebook 3.x or 4.x (Jupyter, Python 3) for remote file upload which allows a user to select multiple files in the browser's file picker when uploading. Unfortunatelly, I have no clue about the javascript side.
I have found blueimp's widgets, however, I have no idea how to use them inside a notebook.
This is how a single file upload widget is made:
import base64
from __future__ import print_function # py 2.7 compat.
from IPython.html import widgets # Widget definitions.
from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget.
class FileWidget(widgets.DOMWidget):
_view_name = Unicode('FilePickerView', sync=True)
value = Unicode(sync=True)
filename = Unicode(sync=True)
def __init__(self, **kwargs):
"""Constructor"""
widgets.DOMWidget.__init__(self, **kwargs) # Call the base.
# Allow the user to register error callbacks with the following signatures:
# callback()
# callback(sender)
self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])
# Listen for custom msgs
self.on_msg(self._handle_custom_msg)
def _handle_custom_msg(self, content):
"""Handle a msg from the front-end.
Parameters
----------
content: dict
Content of the msg."""
if 'event' in content and content['event'] == 'error':
self.errors()
self.errors(self)
%%javascript
require(["widgets/js/widget", "widgets/js/manager"], function(widget, manager){
var FilePickerView = widget.DOMWidgetView.extend({
render: function(){
// Render the view.
this.setElement($('<input />')
.attr('type', 'file'));
},
events: {
// List of events and their handlers.
'change': 'handle_file_change',
},
handle_file_change: function(evt) {
// Handle when the user has changed the file.
// Retrieve the first (and only!) File from the FileList object
var file = evt.target.files[0];
if (file) {
// Read the file's textual content and set value to those contents.
var that = this;
var file_reader = new FileReader();
file_reader.onload = function(e) {
that.model.set('value', e.target.result);
that.touch();
}
file_reader.readAsText(file);
} else {
// The file couldn't be opened. Send an error msg to the
// back-end.
this.send({ 'event': 'error' });
}
// Set the filename of the file.
this.model.set('filename', file.name);
this.touch();
},
});
// Register the FilePickerView with the widget manager.
manager.WidgetManager.register_widget_view('FilePickerView', FilePickerView);
});
file_widget = FileWidget()
# Register an event to echo the filename when it has been changed.
def file_loading():
print("Loading %s" % file_widget.filename)
file_widget.on_trait_change(file_loading, 'filename')
# Register an event to echo the filename and contents when a file
# has been uploaded.
def file_loaded():
print("Loaded, file contents: %s" % file_widget.value)
file_widget.on_trait_change(file_loaded, 'value')
# Register an event to print an error message when a file could not
# be opened. Since the error messages are not handled through
# traitlets but instead handled through custom msgs, the registration
# of the handler is different than the two examples above. Instead
# the API provided by the CallbackDispatcher must be used.
def file_failed():
print("Could not load file contents of %s" % file_widget.filename)
file_widget.errors.register_callback(file_failed)
file_widget
Comments, suggestions and fixes are welcome.
I got inspiration from Jupyter Notebook (4.x) itself from the NotebookList.prototype.handleFilesUpload function of the notebooklist.js file. After reading up on some javascript syntax, I came up with the following:
(Please note that files are uploaded in text mode without checking.)
import base64 # You need it if you define binary uploads
from __future__ import print_function # py 2.7 compat.
import ipywidgets as widgets # Widget definitions.
from traitlets import List, Unicode # Traitlets needed to add synced attributes to the widget.
class FileWidget(widgets.DOMWidget):
_view_name = Unicode('FilePickerView').tag(sync=True)
_view_module = Unicode('filepicker').tag(sync=True)
filenames = List([], sync=True)
# values = List(trait=Unicode, sync=True)
def __init__(self, **kwargs):
"""Constructor"""
super().__init__(**kwargs)
# Allow the user to register error callbacks with the following signatures:
# callback()
# callback(sender)
self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])
# Listen for custom msgs
self.on_msg(self._handle_custom_msg)
def _handle_custom_msg(self, content):
"""Handle a msg from the front-end.
Parameters
----------
content: dict
Content of the msg."""
if 'event' in content and content['event'] == 'error':
self.errors()
self.errors(self)
%%javascript
requirejs.undef('filepicker');
define('filepicker', ["jupyter-js-widgets"], function(widgets) {
var FilePickerView = widgets.DOMWidgetView.extend({
render: function(){
// Render the view using HTML5 multiple file input support.
this.setElement($('<input class="fileinput" multiple="multiple" name="datafile" />')
.attr('type', 'file'));
},
events: {
// List of events and their handlers.
'change': 'handle_file_change',
},
handle_file_change: function(evt) {
// Handle when the user has changed the file.
// Save context (or namespace or whatever this is)
var that = this;
// Retrieve the FileList object
var files = evt.originalEvent.target.files;
var filenames = [];
var file_readers = [];
console.log("Reading files:");
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log("Filename: " + file.name);
console.log("Type: " + file.type);
console.log("Size: " + file.size + " bytes");
filenames.push(file.name);
// Read the file's textual content and set value_i to those contents.
file_readers.push(new FileReader());
file_readers[i].onload = (function(file, i) {
return function(e) {
that.model.set('value_' + i, e.target.result);
that.touch();
console.log("file_" + i + " loaded: " + file.name);
};
})(file, i);
file_readers[i].readAsText(file);
}
// Set the filenames of the files.
this.model.set('filenames', filenames);
this.touch();
},
});
// Register the FilePickerView with the widget manager.
return {
FilePickerView: FilePickerView
};
});
file_widget = FileWidget()
def file_loaded(change):
'''Register an event to save contents when a file has been uploaded.'''
print(change['new'])
i = int(change['name'].split('_')[1])
fname = file_widget.filenames[i]
print('file_loaded: {}'.format(fname))
def file_loading(change):
'''Update self.model when user requests a list of files to be uploaded'''
print(change['new'])
num = len(change['new'])
traits = [('value_{}'.format(i), Unicode(sync=True)) for i in range(num)]
file_widget.add_traits(**dict(traits))
for i in range(num):
file_widget.observe(file_loaded, 'value_{}'.format(i))
file_widget.observe(file_loading, names='filenames')
def file_failed():
print("Could not load some file contents.")
file_widget.errors.register_callback(file_failed)
file_widget
A button with the text Browse... should appear stating how many files are selected. Since print statements are included in the file_loading and file_loaded functions you should see filenames and file contents in the output. Filenames and file types are shown in the console log, as well.
This issue https://github.com/ipython/ipython/issues/8383 answers your question partially. There is already upload button available in jupyter 4.0 on Dashboard screen. This upload button supports selecting multiple files.
Note that up to date links are located here for upload widget:
https://github.com/ipython/ipywidgets/blob/master/docs/source/examples/File%20Upload%20Widget.ipynb
Also there is a extension available for download and quick installation in your notebooks:
https://github.com/peteut/ipython-file-upload
pip install fileupload
or
pip install git+https://github.com/peteut/ipython-file-upload
Note that the extension is confirmed to work on linux only according to the author.
There is an even newer approach than fileupload, which I used in the past and works pretty well (posted by #denfromufa) with a natively supported file-upload widget.
import io
import ipywidgets as widgets
widgets.FileUpload(
accept='.txt', # Accepted file extension e.g. '.txt', '.pdf', 'image/*', 'image/*,.pdf'
multiple=True # True to accept multiple files upload else False
)
A couple tips:
If your notebook has code 'under' the uploader, it will proceed. I use ipython blocking to 'pause' the execution of the rest of the notebook until the file is successfully uploaded... I then have a second button that basically restarts code execution after the user has uploaded the right file(s) and is ready to proceed.
The ipywidget uploader differs from [fileupload] (pip install fileupload) in that it pulls the file into python so you can work with it. If you want to drop it into a directory, you have to use some other method to write the value data to a file:
For an fileupload widget 'myupload' you could write an event-driven function that includes something like the following when a file is uploaded:
# Get the original filename which is the first key in the widget's value dict:
uploaded_filename = next(iter(myupload.value))
content = myupload.value[uploaded_filename]['content']
with open('myfile', 'wb') as f: f.write(content)

How to use AJAX with Google App Engine (Python)

I am completely novice at AJAX. I am familiar with HTML/CSS, jQuery and beginner at GAE and Python.
In an effort to understand how AJAX works, I would like to know how AJAX might be used (actual code) in this example below. Let's use a reddit-like example where vote ups/downs are ajaxified:
Here is the Story Kind:
class Story(ndb.Model):
title = ndb.StringProperty(required = True)
vote_count = ndb.IntegerProperty(default = 0)
The HTML would look like this:
<h2>{{story.title}}</h2>
<div>
{{story.vote_count}} | Vote Up Story
</div>
How does AJAX fit inside here?
Ok Sir here we go... A simple app with one story and infinite votes... ;-)
app.yaml
application: anotherappname
version: 1
runtime: python27
api_version: 1
threadsafe: true
default_expiration: "0d 0h 5m"
libraries:
- name: jinja2
version: latest
- name: webapp2
version: latest
handlers:
- url: .*
script: main.app
main.py
import logging
from controllers import server
from config import config
import webapp2
app = webapp2.WSGIApplication([
# Essential handlers
('/', server.RootPage),
('/vote/', server.VoteHandler)
],debug=True, config=config.config)
# Extra Hanlder like 404 500 etc
def handle_404(request, response, exception):
logging.exception(exception)
response.write('Oops! Naughty Mr. Jiggles (This is a 404)')
response.set_status(404)
app.error_handlers[404] = handle_404
models/story.py
from google.appengine.ext import ndb
class Story(ndb.Model):
title = ndb.StringProperty(required=True)
vote_count = ndb.IntegerProperty(default = 0)
controllers/server.py
import os
import re
import logging
import config
import json
import webapp2
import jinja2
from google.appengine.ext import ndb
from models.story import Story
class RootPage(webapp2.RequestHandler):
def get(self):
story = Story.get_or_insert('Some id or so', title='A voting story again...')
jinja_environment = self.jinja_environment
template = jinja_environment.get_template("/index.html")
self.response.out.write(template.render({'story': story}))
#property
def jinja_environment(self):
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(
os.path.join(os.path.dirname(__file__),
'../views'
))
)
return jinja_environment
class VoteHandler(webapp2.RequestHandler):
def post(self):
logging.info(self.request.body)
data = json.loads(self.request.body)
story = ndb.Key(Story, data['storyKey']).get()
story.vote_count += 1
story.put()
self.response.out.write(json.dumps(({'story': story.to_dict()})))
and finally
views/index.html
<!DOCTYPE html>
<html>
<head>
<base href="/">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
</head>
<body>
<h2>{{story.title}}</h2>
<div>
<span class="voteCount">{{story.vote_count}}</span> | <a href="javascript:VoteUp('{{story.key.id()}}');" >Vote Up Story</a>
</div>
<script>
function VoteUp(storyKey){
$.ajax({
type: "POST",
url: "/vote/",
dataType: 'json',
data: JSON.stringify({ "storyKey": storyKey})
})
.done(function( data ) { // check why I use done
alert( "Vote Cast!!! Count is : " + data['story']['vote_count'] );
$('.voteCount').text(data['story']['vote_count']);
});
};
</script>
</body>
</html>
Assemble, read it's simple enough and run. If you need a working git example just comment.
githublink (as from comments)
Here is a little prototype web app on GitHub to test how to handle error messages in HTML form submissions with AJAX, Python and Google App Engine. It will give a starting point to see how these three pieces of technology mesh together. You can test this "app" on https://ajax-prototype.appspot.com/
Here is how it works on the client-side:
This htlm form submission is used:
<form method="post" action="javascript:ajaxScript();">
<label>Please pick a name</label>
<input id="input" type="text">
<input type="submit">
<div id="error" style="color:red"></div>
It will trigger the JavaScript function ajaxScript:
function ajaxScript() {
var input = $("#input").val();
$.ajax({
type: "POST",
url: "/",
data: JSON.stringify({
"name": input
}),
dataType: "json"
})
.done(function(jsonResponse) {
$("#error").html(jsonResponse.message);
});
}
The jQuery .ajax() method handles the request while the .done() method will eventually handle the response that it gets from the server.
On the server-side:
The main.py file handles the server side of the business using our handler class AjaxHandler, which inherits from the GAE builtin class webapp2.RequestHandler
Within this class, the post method handles the AJAX request:
def post(self):
data = json.loads(self.request.body)
username = data["name"]
if not re.match(r"^[a-zA-Z0-9_-]{3,20}$", username):
if len(username) < 3:
message = "Your name must be at least 3 characters long."
else:
message = "Allowed characters are \
a-z, A-Z, 0-9, underscores \
and hyphens."
else:
message = "Congrats!"
self.response.write(json.dumps({"message": message}))
Basically, the handy json module provides the two key Python ingredients
json.loads handles the data that the browser sends to the server.
json.dumps handles the data sent by the server in response to the browser's request.
The self.request.body argument of json.loads is the only less common piece of GAE that is used in the process, as it is specific to the task. As its name suggests, it gets the body from the Ajax request sent by the client.

Why is the URL 404 not found with Django?

I have written a Django script that runs a Python parser to web s*e. I am sending the request to the Django script via AJAX. However, when the Ajax runs, it comes back as 404 not found for the URL. Why is this happening?
My code is below:
Ajax (with jQuery):
//send a `post` request up to AWS, and then
//insert the data into the paths
$.post('/ca', function(data){
//evaluate the JSON
data = eval ("(" + data + ")");
//insert the vars into the DOM
var contentOne;
contentOne = data.bridge_time;
contentOne += 'min delay';
$('#timeone').html(contentOne);
var contentTwo;
contentTwo = data.tunnel_time;
contentTwo += 'min delay';
$('#timetwo').html(contentTwo);
//if this falls through, push an error.
var tunnel_time = data.tunnel_time;
var bridge_time = data.bridge_time;
var tunnel = document.getElementById('tunnel');
var bridge = document.getElementById('bridge');
var tunnelText = document.getElementById('timeone');
var bridgeText = document.getElementById('timetwo');
//algo for the changing icons. Kudos to Vito
if(tunnel_time<bridge_time){
tunnel.src="tunnel3.png";
bridge.src="bridge2r.png";
}else if( bridge_time<tunnel_time){
bridge.src="bridge21.png";
tunnel.src="tunnel2r.png";
}else{
bridge.src="bridge2n.png";
tunnel.src="tunnel2g.png";
}
$.fail(function() {
alert("We're sorry. We are having an error. Check back later.");
});
});
My urls.py:
from django.conf.urls.defaults import *
from views import views
urlpatterns = patterns('',
(r'^/us', views.american_time),
(r'^/ca', views.canadian_time),
)
My urls.py and my views.py are in the same folder, if that makes any difference. They are just titled views.py and urls.py. Thank you!
Try
from django.conf.urls.defaults import *
from views import views
urlpatterns = patterns('',
(r'^/us/$', views.american_time),
(r'^/ca/$', views.canadian_time),
)
Also you have to add the trailing slash in your JavaScript.
I just resolved this: there was an error in my urls.py. My system was having trouble with the .defaults that is was supposed to import from. Also, I didn't have a Django project set up, so It wouldn't import the views.

Categories

Resources