├── __init__.py ├── items ├── __init__.py ├── static │ ├── img │ │ └── destroy.png │ ├── js │ │ ├── todos.js │ │ ├── json2.js │ │ ├── underscore.js │ │ └── backbone.js │ └── css │ │ └── todos.css ├── models.py ├── tests.py └── views.py ├── .gitignore ├── README.md ├── urls.py ├── index.html └── settings.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /items/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | secret_key.py 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Backbone.js + Django (v 1.1.1) 3 | -------------------------------------------------------------------------------- /items/static/img/destroy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccarpenterg/djangotodos/HEAD/items/static/img/destroy.png -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | from django.conf import settings 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns('', 7 | (r'^$', 'items.views.index'), 8 | (r'^todos\/?(?P\d*)$', 'items.views.restful'), 9 | (r'^static/(?P.*)$', 'django.views.static.serve', {'document_root': '/home/django/todos/items/static/'}), 10 | (r'^admin/', include(admin.site.urls)), 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /items/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | class TodoList(models.Model): 6 | createdon = models.DateTimeField(auto_now_add=True) 7 | 8 | class Todo(models.Model): 9 | todolist = models.ForeignKey(TodoList) 10 | order = models.IntegerField() 11 | content = models.CharField(max_length=256) 12 | done = models.BooleanField() 13 | 14 | def toDict(self): 15 | todo = { 16 | 'id': self.id, 17 | 'order': self.order, 18 | 'content': self.content, 19 | 'done': self.done 20 | } 21 | return todo 22 | -------------------------------------------------------------------------------- /items/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | -------------------------------------------------------------------------------- /items/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | 3 | from items.models import TodoList, Todo 4 | from django.http import HttpResponse, QueryDict, HttpResponseForbidden 5 | from django.shortcuts import render_to_response 6 | from django.template import loader, Context 7 | import json, Cookie 8 | 9 | from datetime import datetime 10 | 11 | def get(request, item_id): 12 | id = request.COOKIES['todos'] 13 | todos = [] 14 | for todo in Todo.objects.filter(todolist=id): 15 | todos.append(todo.toDict()) 16 | todos = json.dumps(todos) 17 | response = HttpResponse() 18 | #response['Content-Type'] = 'application/json' 19 | response.write(todos) 20 | return response 21 | 22 | def post(request, item_id): 23 | id = request.COOKIES['todos'] 24 | todolist = TodoList.objects.get(id=int(id)) 25 | todo = json.loads(request.raw_post_data) 26 | todo = Todo(todolist = todolist, 27 | order = int(todo['order']), 28 | content = todo['content'], 29 | done = todo['done']) 30 | todo.save() 31 | todo = json.dumps(todo.toDict()) 32 | response = HttpResponse() 33 | response.write(todo) 34 | return response 35 | 36 | def put(request, item_id): 37 | id = request.COOKIES['todos'] 38 | todolist = TodoList.objects.get(id=id) 39 | todo = Todo.objects.get(id=item_id) 40 | if todo.todolist == todolist: 41 | tmp = json.loads(request.raw_post_data) 42 | todo.content = tmp['content'] 43 | todo.done = tmp['done'] 44 | todo.save() 45 | todo = json.dumps(todo.toDict()) 46 | response = HttpResponse() 47 | response.write(todo) 48 | return response 49 | else: 50 | return HttpResponseForbidden() 51 | 52 | def delete(request, item_id): 53 | id = request.COOKIES['todos'] 54 | todolist = TodoList.objects.get(id=id) 55 | todo = Todo.objects.get(id=item_id) 56 | if todo.todolist == todolist: 57 | todo.delete() 58 | return HttpResponse() 59 | else: 60 | return HttpResponseForbidden() 61 | 62 | methods = { 63 | 'GET': get, 64 | 'POST': post, 65 | 'PUT': put, 66 | 'DELETE': delete 67 | } 68 | 69 | def restful(request, item_id): 70 | return methods[request.method](request, item_id) 71 | 72 | def index(request): 73 | response = HttpResponse() 74 | if 'todos' not in request.COOKIES: 75 | todolist = TodoList() 76 | todolist.save() 77 | cookie = Cookie.SimpleCookie() 78 | cookie['todos'] = todolist.id 79 | cookie['todos']['expires'] = datetime(2014, 1, 1).strftime('%a, %d %b %Y %H:%M:%S') 80 | cookie['todos']['path'] ='/' 81 | response['Set-Cookie'] = cookie['todos'].OutputString() 82 | template = loader.get_template('index.html') 83 | context = Context({'x': 'x'}) 84 | response.write(template.render(context)) 85 | return response 86 | #return render_to_response('index.html') 87 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone.js + Django | Todos example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 |

Todos

22 |
23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 |
32 |
    33 |
    34 | 35 |
    36 | 37 |
    38 | 39 |
    40 | 41 | 45 | 46 |
    47 | UI (Backbone.js app and html/css) created by 48 |
    49 | Jérôme Gravel-Niquet 50 |
    51 | 52 |
    53 | Django app created by 54 |
    55 | Cristian Carpenter 56 |
    57 | 58 | 59 | 60 | 61 | 73 | 74 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for todos project. 2 | 3 | from secret_key import * 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | ADMINS = ( 9 | # ('Your Name', 'your_email@domain.com'), 10 | ) 11 | 12 | MANAGERS = ADMINS 13 | 14 | DATABASE_ENGINE = 'django.db.backends.postgresql_psycopg2' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 15 | DATABASE_NAME = 'todos' # Or path to database file if using sqlite3. 16 | DATABASE_USER = 'postgres' # Not used with sqlite3. 17 | DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 18 | DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 19 | 20 | # Local time zone for this installation. Choices can be found here: 21 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 22 | # although not all choices may be available on all operating systems. 23 | # If running in a Windows environment this must be set to the same as your 24 | # system time zone. 25 | TIME_ZONE = 'America/Chicago' 26 | 27 | # Language code for this installation. All choices can be found here: 28 | # http://www.i18nguy.com/unicode/language-identifiers.html 29 | LANGUAGE_CODE = 'en-us' 30 | 31 | SITE_ID = 1 32 | 33 | # If you set this to False, Django will make some optimizations so as not 34 | # to load the internationalization machinery. 35 | USE_I18N = True 36 | 37 | # Absolute path to the directory that holds media. 38 | # Example: "/home/media/media.lawrence.com/" 39 | MEDIA_ROOT = '' 40 | 41 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 42 | # trailing slash if there is a path component (optional in other cases). 43 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 44 | MEDIA_URL = '' 45 | 46 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 47 | # trailing slash. 48 | # Examples: "http://foo.com/media/", "/media/". 49 | ADMIN_MEDIA_PREFIX = '/media/' 50 | 51 | # List of callables that know how to import templates from various sources. 52 | TEMPLATE_LOADERS = ( 53 | 'django.template.loaders.filesystem.load_template_source', 54 | 'django.template.loaders.app_directories.load_template_source', 55 | # 'django.template.loaders.eggs.load_template_source', 56 | ) 57 | 58 | MIDDLEWARE_CLASSES = ( 59 | 'django.middleware.common.CommonMiddleware', 60 | 'django.contrib.sessions.middleware.SessionMiddleware', 61 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 62 | ) 63 | 64 | ROOT_URLCONF = 'todos.urls' 65 | 66 | TEMPLATE_DIRS = ( 67 | '/home/django/todos/' 68 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 69 | # Always use forward slashes, even on Windows. 70 | # Don't forget to use absolute paths, not relative paths. 71 | ) 72 | 73 | INSTALLED_APPS = ( 74 | 'django.contrib.auth', 75 | 'django.contrib.contenttypes', 76 | 'django.contrib.sessions', 77 | 'django.contrib.sites', 78 | 'django.contrib.admin', 79 | 'items', 80 | ) 81 | -------------------------------------------------------------------------------- /items/static/js/todos.js: -------------------------------------------------------------------------------- 1 | // An example Backbone application contributed by 2 | // [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple 3 | // [LocalStorage adapter](backbone-localstorage.html) 4 | // to persist Backbone models within your browser. 5 | 6 | // Load the application once the DOM is ready, using `jQuery.ready`: 7 | $(function(){ 8 | 9 | // Todo Model 10 | // ---------- 11 | 12 | // Our basic **Todo** model has `content`, `order`, and `done` attributes. 13 | window.Todo = Backbone.Model.extend({ 14 | 15 | // If you don't provide a todo, one will be provided for you. 16 | EMPTY: "empty todo...", 17 | 18 | // Ensure that each todo created has `content`. 19 | initialize: function() { 20 | if (!this.get("content")) { 21 | this.set({"content": this.EMPTY}); 22 | } 23 | }, 24 | 25 | // Toggle the `done` state of this todo item. 26 | toggle: function() { 27 | this.save({done: !this.get("done")}); 28 | }, 29 | 30 | // Remove this Todo from *localStorage* and delete its view. 31 | clear: function() { 32 | this.destroy(); 33 | this.view.remove(); 34 | } 35 | 36 | }); 37 | 38 | // Todo Collection 39 | // --------------- 40 | 41 | // The collection of todos is backed by *localStorage* instead of a remote 42 | // server. 43 | window.TodoList = Backbone.Collection.extend({ 44 | 45 | // Reference to this collection's model. 46 | model: Todo, 47 | 48 | // Save all of the todo items under the `"todos"` namespace. 49 | //localStorage: new Store("todos"), 50 | 51 | //url: 'http://localhost:8080/todos', 52 | url: '/todos', 53 | 54 | // Filter down the list of all todo items that are finished. 55 | done: function() { 56 | return this.filter(function(todo){ return todo.get('done'); }); 57 | }, 58 | 59 | // Filter down the list to only todo items that are still not finished. 60 | remaining: function() { 61 | return this.without.apply(this, this.done()); 62 | }, 63 | 64 | // We keep the Todos in sequential order, despite being saved by unordered 65 | // GUID in the database. This generates the next order number for new items. 66 | nextOrder: function() { 67 | if (!this.length) return 1; 68 | return this.last().get('order') + 1; 69 | }, 70 | 71 | // Todos are sorted by their original insertion order. 72 | comparator: function(todo) { 73 | return todo.get('order'); 74 | } 75 | 76 | }); 77 | 78 | // Create our global collection of **Todos**. 79 | window.Todos = new TodoList; 80 | 81 | // Todo Item View 82 | // -------------- 83 | 84 | // The DOM element for a todo item... 85 | window.TodoView = Backbone.View.extend({ 86 | 87 | //... is a list tag. 88 | tagName: "li", 89 | 90 | // Cache the template function for a single item. 91 | template: _.template($('#item-template').html()), 92 | 93 | // The DOM events specific to an item. 94 | events: { 95 | "click .check" : "toggleDone", 96 | "dblclick div.todo-content" : "edit", 97 | "click span.todo-destroy" : "clear", 98 | "keypress .todo-input" : "updateOnEnter" 99 | }, 100 | 101 | // The TodoView listens for changes to its model, re-rendering. Since there's 102 | // a one-to-one correspondence between a **Todo** and a **TodoView** in this 103 | // app, we set a direct reference on the model for convenience. 104 | initialize: function() { 105 | _.bindAll(this, 'render', 'close'); 106 | this.model.bind('change', this.render); 107 | this.model.view = this; 108 | }, 109 | 110 | // Re-render the contents of the todo item. 111 | render: function() { 112 | $(this.el).html(this.template(this.model.toJSON())); 113 | this.setContent(); 114 | return this; 115 | }, 116 | 117 | // To avoid XSS (not that it would be harmful in this particular app), 118 | // we use `jQuery.text` to set the contents of the todo item. 119 | setContent: function() { 120 | var content = this.model.get('content'); 121 | this.$('.todo-content').text(content); 122 | this.input = this.$('.todo-input'); 123 | this.input.bind('blur', this.close); 124 | this.input.val(content); 125 | }, 126 | 127 | // Toggle the `"done"` state of the model. 128 | toggleDone: function() { 129 | this.model.toggle(); 130 | }, 131 | 132 | // Switch this view into `"editing"` mode, displaying the input field. 133 | edit: function() { 134 | $(this.el).addClass("editing"); 135 | this.input.focus(); 136 | }, 137 | 138 | // Close the `"editing"` mode, saving changes to the todo. 139 | close: function() { 140 | this.model.save({content: this.input.val()}); 141 | $(this.el).removeClass("editing"); 142 | }, 143 | 144 | // If you hit `enter`, we're through editing the item. 145 | updateOnEnter: function(e) { 146 | if (e.keyCode == 13) this.close(); 147 | }, 148 | 149 | // Remove this view from the DOM. 150 | remove: function() { 151 | $(this.el).remove(); 152 | }, 153 | 154 | // Remove the item, destroy the model. 155 | clear: function() { 156 | this.model.clear(); 157 | } 158 | 159 | }); 160 | 161 | // The Application 162 | // --------------- 163 | 164 | // Our overall **AppView** is the top-level piece of UI. 165 | window.AppView = Backbone.View.extend({ 166 | 167 | // Instead of generating a new element, bind to the existing skeleton of 168 | // the App already present in the HTML. 169 | el: $("#todoapp"), 170 | 171 | // Our template for the line of statistics at the bottom of the app. 172 | statsTemplate: _.template($('#stats-template').html()), 173 | 174 | // Delegated events for creating new items, and clearing completed ones. 175 | events: { 176 | "keypress #new-todo": "createOnEnter", 177 | "keyup #new-todo": "showTooltip", 178 | "click .todo-clear a": "clearCompleted" 179 | }, 180 | 181 | // At initialization we bind to the relevant events on the `Todos` 182 | // collection, when items are added or changed. Kick things off by 183 | // loading any preexisting todos that might be saved in *localStorage*. 184 | initialize: function() { 185 | _.bindAll(this, 'addOne', 'addAll', 'render'); 186 | 187 | this.input = this.$("#new-todo"); 188 | 189 | Todos.bind('add', this.addOne); 190 | Todos.bind('refresh', this.addAll); 191 | Todos.bind('all', this.render); 192 | 193 | Todos.fetch(); 194 | }, 195 | 196 | // Re-rendering the App just means refreshing the statistics -- the rest 197 | // of the app doesn't change. 198 | render: function() { 199 | var done = Todos.done().length; 200 | this.$('#todo-stats').html(this.statsTemplate({ 201 | total: Todos.length, 202 | done: Todos.done().length, 203 | remaining: Todos.remaining().length 204 | })); 205 | }, 206 | 207 | // Add a single todo item to the list by creating a view for it, and 208 | // appending its element to the `