├── .gitignore ├── convo.png ├── django-ajax.png ├── images ├── drf-cbv.png └── drf.png ├── overview.png ├── part1 ├── main.js ├── post_ajax │ ├── requirements.txt │ └── talk_project │ │ ├── manage.py │ │ ├── static │ │ └── scripts │ │ │ └── main.js │ │ ├── talk │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── forms.py │ │ ├── middleware.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ │ ├── talk_project │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ ├── views.py │ │ └── wsgi.py │ │ └── templates │ │ ├── registration │ │ ├── base.html │ │ └── login.html │ │ └── talk │ │ ├── base.html │ │ └── index.html ├── pre-ajax.zip └── pre-ajax │ ├── requirements.txt │ └── talk_project │ ├── manage.py │ ├── static │ └── scripts │ │ └── main.js │ ├── talk │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── middleware.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── talk_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py │ └── templates │ ├── registration │ ├── base.html │ └── login.html │ └── talk │ ├── base.html │ └── index.html ├── part2 ├── requirements.txt └── talk_project │ ├── manage.py │ ├── static │ └── scripts │ │ └── main.js │ ├── talk │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── middleware.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── talk_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py │ └── templates │ ├── registration │ ├── base.html │ └── login.html │ └── talk │ ├── base.html │ └── index.html ├── part3 ├── requirements.txt └── talk_project │ ├── manage.py │ ├── static │ └── scripts │ │ └── main.js │ ├── talk │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── middleware.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── talk_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py │ └── templates │ ├── registration │ ├── base.html │ └── login.html │ └── talk │ ├── base.html │ └── index.html ├── part4 ├── requirements.txt └── talk_project │ ├── manage.py │ ├── static │ └── scripts │ │ └── main.js │ ├── talk │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── middleware.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── talk_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py │ └── templates │ ├── registration │ ├── base.html │ └── login.html │ └── talk │ ├── base.html │ └── index.html └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | env-talk 5 | db.sqlite3 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | bin/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # Installer logs 28 | pip-log.txt 29 | pip-delete-this-directory.txt 30 | 31 | # Unit test / coverage reports 32 | htmlcov/ 33 | .tox/ 34 | .coverage 35 | .cache 36 | nosetests.xml 37 | coverage.xml 38 | 39 | # Translations 40 | *.mo 41 | 42 | # Mr Developer 43 | .mr.developer.cfg 44 | .project 45 | .pydevproject 46 | 47 | # Rope 48 | .ropeproject 49 | 50 | # Django stuff: 51 | *.log 52 | *.pot 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | .pyc 58 | .DS_Store 59 | env 60 | 61 | talk-env 62 | 63 | -------------------------------------------------------------------------------- /convo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/convo.png -------------------------------------------------------------------------------- /django-ajax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/django-ajax.png -------------------------------------------------------------------------------- /images/drf-cbv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/images/drf-cbv.png -------------------------------------------------------------------------------- /images/drf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/images/drf.png -------------------------------------------------------------------------------- /overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/overview.png -------------------------------------------------------------------------------- /part1/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | 4 | // This function gets cookie with a given name 5 | function getCookie(name) { 6 | var cookieValue = null; 7 | if (document.cookie && document.cookie != '') { 8 | var cookies = document.cookie.split(';'); 9 | for (var i = 0; i < cookies.length; i++) { 10 | var cookie = jQuery.trim(cookies[i]); 11 | // Does this cookie string begin with the name we want? 12 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 13 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 14 | break; 15 | } 16 | } 17 | } 18 | return cookieValue; 19 | } 20 | var csrftoken = getCookie('csrftoken'); 21 | 22 | /* 23 | The functions below will create a header with csrftoken 24 | */ 25 | 26 | function csrfSafeMethod(method) { 27 | // these HTTP methods do not require CSRF protection 28 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 29 | } 30 | function sameOrigin(url) { 31 | // test that a given url is a same-origin URL 32 | // url could be relative or scheme relative or absolute 33 | var host = document.location.host; // host + port 34 | var protocol = document.location.protocol; 35 | var sr_origin = '//' + host; 36 | var origin = protocol + sr_origin; 37 | // Allow absolute or scheme relative URLs to same origin 38 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 39 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 40 | // or any other URL that isn't scheme relative or absolute i.e relative. 41 | !(/^(\/\/|http:|https:).*/.test(url)); 42 | } 43 | 44 | $.ajaxSetup({ 45 | beforeSend: function(xhr, settings) { 46 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { 47 | // Send the token to same-origin, relative URLs only. 48 | // Send the token only if the method warrants CSRF protection 49 | // Using the CSRFToken value acquired earlier 50 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 51 | } 52 | } 53 | }); 54 | 55 | }); -------------------------------------------------------------------------------- /part1/post_ajax/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.6 2 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/static/scripts/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | 4 | // Submit post on submit 5 | $('#post-form').on('submit', function(event){ 6 | event.preventDefault(); 7 | console.log("form submitted!") // sanity check 8 | create_post(); 9 | }); 10 | 11 | // AJAX for posting 12 | function create_post() { 13 | console.log("create post is working!") // sanity check 14 | $.ajax({ 15 | url : "create_post/", // the endpoint 16 | type : "POST", // http method 17 | data : { the_post : $('#post-text').val() }, // data sent with the post request 18 | // handle a successful response 19 | success : function(json) { 20 | $('#post-text').val(''); // remove the value from the input 21 | console.log(json); // log the returned json to the console 22 | $("#talk").prepend("
  • "+json.text+" - "+json.author+" - "+json.created+ 23 | " - delete me
  • "); 24 | console.log("success"); // another sanity check 25 | }, 26 | // handle a non-successful response 27 | error : function(xhr,errmsg,err) { 28 | $('#results').html("
    Oops! We have encountered an error: "+errmsg+ 29 | " ×
    "); // add the error to the dom 30 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 31 | } 32 | }); 33 | }; 34 | 35 | 36 | // This function gets cookie with a given name 37 | function getCookie(name) { 38 | var cookieValue = null; 39 | if (document.cookie && document.cookie != '') { 40 | var cookies = document.cookie.split(';'); 41 | for (var i = 0; i < cookies.length; i++) { 42 | var cookie = jQuery.trim(cookies[i]); 43 | // Does this cookie string begin with the name we want? 44 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 45 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 46 | break; 47 | } 48 | } 49 | } 50 | return cookieValue; 51 | } 52 | var csrftoken = getCookie('csrftoken'); 53 | 54 | /* 55 | The functions below will create a header with csrftoken 56 | */ 57 | 58 | function csrfSafeMethod(method) { 59 | // these HTTP methods do not require CSRF protection 60 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 61 | } 62 | function sameOrigin(url) { 63 | // test that a given url is a same-origin URL 64 | // url could be relative or scheme relative or absolute 65 | var host = document.location.host; // host + port 66 | var protocol = document.location.protocol; 67 | var sr_origin = '//' + host; 68 | var origin = protocol + sr_origin; 69 | // Allow absolute or scheme relative URLs to same origin 70 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 71 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 72 | // or any other URL that isn't scheme relative or absolute i.e relative. 73 | !(/^(\/\/|http:|https:).*/.test(url)); 74 | } 75 | 76 | $.ajaxSetup({ 77 | beforeSend: function(xhr, settings) { 78 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { 79 | // Send the token to same-origin, relative URLs only. 80 | // Send the token only if the method warrants CSRF protection 81 | // Using the CSRFToken value acquired earlier 82 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 83 | } 84 | } 85 | }); 86 | 87 | }); -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part1/post_ajax/talk_project/talk/__init__.py -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | # Register your models here. 5 | admin.site.register(Post) 6 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from talk.models import Post 3 | 4 | 5 | class PostForm(forms.ModelForm): 6 | class Meta: 7 | model = Post 8 | # exclude = ['author', 'updated', 'created', ] 9 | fields = ['text'] 10 | widgets = { 11 | 'text': forms.TextInput( 12 | attrs={'id': 'post-text', 'required': True, 'placeholder': 'Say something...'} 13 | ), 14 | } 15 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | 7 | class RequireLoginMiddleware(object): 8 | """ 9 | Middleware component that wraps the login_required decorator around 10 | matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and 11 | define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your 12 | settings.py. For example: 13 | ------ 14 | LOGIN_REQUIRED_URLS = ( 15 | r'/topsecret/(.*)$', 16 | ) 17 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 18 | r'/topsecret/login(.*)$', 19 | r'/topsecret/logout(.*)$', 20 | ) 21 | ------ 22 | LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must 23 | be a valid regex. 24 | 25 | LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly 26 | define any exceptions (like login and logout URLs). 27 | """ 28 | def __init__(self): 29 | self.required = tuple( 30 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS 31 | ) 32 | self.exceptions = tuple( 33 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS 34 | ) 35 | 36 | def process_view(self, request, view_func, view_args, view_kwargs): 37 | # No need to process URLs if user already logged in 38 | if request.user.is_authenticated(): 39 | return None 40 | 41 | # An exception match should immediately return None 42 | for url in self.exceptions: 43 | if url.match(request.path): 44 | return None 45 | 46 | # Requests matching a restricted URL pattern are returned 47 | # wrapped with the login_required decorator 48 | for url in self.required: 49 | if url.match(request.path): 50 | return login_required(view_func)(request, *view_args, **view_kwargs) 51 | 52 | # Explicitly return None for all non-matching requests 53 | return None 54 | 55 | 56 | class MaintenanceMiddleware(object): 57 | """Serve a temporary redirect to a maintenance url in maintenance mode""" 58 | def process_request(self, request): 59 | if request.method == 'POST': 60 | if getattr(settings, 'MAINTENANCE_MODE', False) is True \ 61 | and hasattr(settings, 'MAINTENANCE_URL'): 62 | # http? where is that defined? 63 | return http.HttpResponseRedirect(settings.MAINTENANCE_URL) 64 | return None 65 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | # Create your models here. 5 | 6 | 7 | class Post(models.Model): 8 | author = models.ForeignKey(User) 9 | text = models.TextField() 10 | 11 | # Time is a rhinocerous 12 | updated = models.DateTimeField(auto_now=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | 15 | class Meta: 16 | ordering = ['created'] 17 | 18 | def __unicode__(self): 19 | return self.text+' - '+self.author.username 20 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import resolve 3 | 4 | 5 | class HomeViewTest(TestCase): 6 | 7 | def test_index(self): 8 | """Ensure main url is connected to the talk.views.home view""" 9 | resolver = resolve('/') 10 | self.assertEqual(resolver.view_name, 'talk.views.home') 11 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/urls.py: -------------------------------------------------------------------------------- 1 | # Talk urls 2 | from django.conf.urls import patterns, url 3 | 4 | 5 | urlpatterns = patterns( 6 | 'talk.views', 7 | url(r'^$', 'home'), 8 | url(r'^create_post/$', 'create_post'), 9 | ) 10 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse 3 | from talk.models import Post 4 | from talk.forms import PostForm 5 | 6 | import json 7 | 8 | 9 | def home(req): 10 | 11 | tmpl_vars = { 12 | 'all_posts': Post.objects.reverse(), 13 | 'form': PostForm() 14 | } 15 | return render(req, 'talk/index.html', tmpl_vars) 16 | 17 | 18 | # def create_post(request): 19 | # if request.method == 'POST': 20 | # form = PostForm(request.POST) 21 | # if form.is_valid(): 22 | # post = form.save(commit=False) 23 | # post.author = request.user 24 | # post.save() 25 | # return HttpResponseRedirect('/') 26 | # else: 27 | # form = PostForm() 28 | # return render(request, 'post.html', {'form': form}) 29 | 30 | def create_post(request): 31 | if request.method == 'POST': 32 | post_text = request.POST.get('the_post') 33 | response_data = {} 34 | 35 | post = Post(text=post_text, author=request.user) 36 | post.save() 37 | 38 | response_data['result'] = 'Create post successful!' 39 | response_data['postpk'] = post.pk 40 | response_data['text'] = post.text 41 | response_data['created'] = post.created.strftime('%B %d, %Y %I:%M %p') 42 | response_data['author'] = post.author.username 43 | 44 | return HttpResponse( 45 | json.dumps(response_data), 46 | content_type="application/json" 47 | ) 48 | else: 49 | return HttpResponse( 50 | json.dumps({"nothing to see": "this isn't happening"}), 51 | content_type="application/json" 52 | ) 53 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part1/post_ajax/talk_project/talk_project/__init__.py -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for talk_project project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | 14 | SETTINGS_DIR = os.path.dirname(__file__) 15 | PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir) 16 | PROJECT_ROOT = os.path.abspath(PROJECT_PATH) 17 | 18 | TEMPLATE_DIRS = ( 19 | os.path.join(PROJECT_ROOT, 'templates'), 20 | ) 21 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 22 | 23 | LOGIN_URL = '/login/' 24 | 25 | LOGOUT_URL = '/logout/' 26 | 27 | # LOGIN_REDIRECT_URL = '/accounts/profile/' 28 | 29 | # Quick-start development settings - unsuitable for production 30 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 31 | 32 | # SECURITY WARNING: keep the secret key used in production secret! 33 | SECRET_KEY = 'u)vhj6nj*)(i(8zg2f0!j=xwg+309om2v@o$-sn0l9a5u0=%+7' 34 | 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | DEBUG = True 37 | 38 | TEMPLATE_DEBUG = True 39 | 40 | ALLOWED_HOSTS = [] 41 | 42 | 43 | # Application definition 44 | 45 | INSTALLED_APPS = ( 46 | 'django.contrib.admin', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.sessions', 50 | 'django.contrib.messages', 51 | 'django.contrib.staticfiles', 52 | 'talk' 53 | ) 54 | 55 | MIDDLEWARE_CLASSES = ( 56 | 'django.contrib.sessions.middleware.SessionMiddleware', 57 | 'django.middleware.common.CommonMiddleware', 58 | 'django.middleware.csrf.CsrfViewMiddleware', 59 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 60 | 'django.contrib.messages.middleware.MessageMiddleware', 61 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 62 | 'talk.middleware.RequireLoginMiddleware', 63 | ) 64 | 65 | LOGIN_REQUIRED_URLS = ( 66 | r'/(.*)$', # TODO interpret this regex. 67 | ) 68 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 69 | r'/login(.*)$', 70 | r'/logout(.*)$', 71 | r'/staff(.*)$', 72 | ) 73 | 74 | TEMPLATE_CONTEXT_PROCESSORS = ( 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.core.context_processors.request', 77 | ) 78 | 79 | ROOT_URLCONF = 'talk_project.urls' 80 | 81 | WSGI_APPLICATION = 'talk_project.wsgi.application' 82 | 83 | 84 | # Database 85 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 86 | 87 | DATABASES = { 88 | 'default': { 89 | 'ENGINE': 'django.db.backends.sqlite3', 90 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 91 | } 92 | } 93 | 94 | # Internationalization 95 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 96 | 97 | LANGUAGE_CODE = 'en-us' 98 | 99 | TIME_ZONE = 'UTC' 100 | 101 | USE_I18N = True 102 | 103 | USE_L10N = True 104 | 105 | USE_TZ = True 106 | 107 | 108 | # Static files (CSS, JavaScript, Images) 109 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 110 | 111 | STATIC_URL = '/static/' 112 | 113 | STATICFILES_DIRS = ( 114 | os.path.join(BASE_DIR, 'static'), 115 | ) 116 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from talk_project.views import logout_page 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | 8 | urlpatterns = patterns( 9 | '', 10 | url(r'^admin/', include(admin.site.urls)), 11 | url(r'^login/', 'django.contrib.auth.views.login'), 12 | (r'^logout/$', logout_page), 13 | url(r'^', include('talk.urls')), 14 | ) 15 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk_project/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | def logout_page(request): 6 | """ 7 | Log users out and re-direct them to the main page. 8 | """ 9 | logout(request) 10 | return HttpResponseRedirect('/') 11 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/talk_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for talk_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %} 9 | 10 | {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 |
    17 | {% block content %} 18 | {% endblock %} 19 |
    20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | Login 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
    10 |

    11 |
    12 | 13 |
    14 | {% if form.errors %} 15 |
    Oh fiddlesticks! Please try again.
    16 | {% endif %} 17 | 18 |
    19 |
    20 | {% csrf_token %} 21 |
    22 | Login 23 |

    24 | 25 | {{ form.username }} 26 |

    27 |

    28 | 29 | {{ form.password }} 30 |

    31 | {% if next %} 32 | 33 | {% else %} 34 | 35 | {% endif %} 36 | 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/templates/talk/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | talk to me 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 51 | 52 | {% block head %} 53 | {% endblock %} 54 | 55 | 56 | 57 | 58 | {% block content %} 59 | {% endblock %} 60 | 61 | -------------------------------------------------------------------------------- /part1/post_ajax/talk_project/templates/talk/index.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% block content %} 3 |

    4 |
    5 |
    6 | Logout 7 |

    Hi, {{request.user.username}}

    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | {% csrf_token %} 15 |
    16 | {{ form.text }} 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 | 24 | 25 |
    26 |
    27 | 37 |
    38 |
    39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /part1/pre-ajax.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part1/pre-ajax.zip -------------------------------------------------------------------------------- /part1/pre-ajax/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.6 2 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/static/scripts/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // nothing 4 | 5 | console.log("nothing") 6 | 7 | }); -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part1/pre-ajax/talk_project/talk/__init__.py -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | # Register your models here. 5 | admin.site.register(Post) 6 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from talk.models import Post 3 | 4 | 5 | class PostForm(forms.ModelForm): 6 | class Meta: 7 | model = Post 8 | # exclude = ['author', 'updated', 'created', ] 9 | fields = ['text'] 10 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | 7 | class RequireLoginMiddleware(object): 8 | """ 9 | Middleware component that wraps the login_required decorator around 10 | matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and 11 | define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your 12 | settings.py. For example: 13 | ------ 14 | LOGIN_REQUIRED_URLS = ( 15 | r'/topsecret/(.*)$', 16 | ) 17 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 18 | r'/topsecret/login(.*)$', 19 | r'/topsecret/logout(.*)$', 20 | ) 21 | ------ 22 | LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must 23 | be a valid regex. 24 | 25 | LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly 26 | define any exceptions (like login and logout URLs). 27 | """ 28 | def __init__(self): 29 | self.required = tuple( 30 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS 31 | ) 32 | self.exceptions = tuple( 33 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS 34 | ) 35 | 36 | def process_view(self, request, view_func, view_args, view_kwargs): 37 | # No need to process URLs if user already logged in 38 | if request.user.is_authenticated(): 39 | return None 40 | 41 | # An exception match should immediately return None 42 | for url in self.exceptions: 43 | if url.match(request.path): 44 | return None 45 | 46 | # Requests matching a restricted URL pattern are returned 47 | # wrapped with the login_required decorator 48 | for url in self.required: 49 | if url.match(request.path): 50 | return login_required(view_func)(request, *view_args, **view_kwargs) 51 | 52 | # Explicitly return None for all non-matching requests 53 | return None 54 | 55 | 56 | class MaintenanceMiddleware(object): 57 | """Serve a temporary redirect to a maintenance url in maintenance mode""" 58 | def process_request(self, request): 59 | if request.method == 'POST': 60 | if getattr(settings, 'MAINTENANCE_MODE', False) is True \ 61 | and hasattr(settings, 'MAINTENANCE_URL'): 62 | # http? where is that defined? 63 | return http.HttpResponseRedirect(settings.MAINTENANCE_URL) 64 | return None 65 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | # Create your models here. 5 | 6 | 7 | class Post(models.Model): 8 | author = models.ForeignKey(User) 9 | text = models.TextField() 10 | 11 | # Time is a rhinocerous 12 | updated = models.DateTimeField(auto_now=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | 15 | class Meta: 16 | ordering = ['created'] 17 | 18 | def __unicode__(self): 19 | return self.text+' - '+self.author.username 20 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import resolve 3 | 4 | 5 | class HomeViewTest(TestCase): 6 | 7 | def test_index(self): 8 | """Ensure main url is connected to the talk.views.home view""" 9 | resolver = resolve('/') 10 | self.assertEqual(resolver.view_name, 'talk.views.home') 11 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/urls.py: -------------------------------------------------------------------------------- 1 | # Talk urls 2 | from django.conf.urls import patterns, url 3 | 4 | 5 | urlpatterns = patterns( 6 | 'talk.views', 7 | url(r'^$', 'home'), 8 | url(r'^create_post/$', 'create_post'), 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponseRedirect 3 | from talk.models import Post 4 | from talk.forms import PostForm 5 | 6 | 7 | def home(req): 8 | 9 | tmpl_vars = { 10 | 'all_posts': Post.objects.reverse(), 11 | 'form': PostForm() 12 | } 13 | return render(req, 'talk/index.html', tmpl_vars) 14 | 15 | 16 | def create_post(request): 17 | if request.method == 'POST': 18 | form = PostForm(request.POST) 19 | if form.is_valid(): 20 | post = form.save(commit=False) 21 | post.author = request.user 22 | post.save() 23 | return HttpResponseRedirect('/') 24 | else: 25 | form = PostForm() 26 | return render(request, 'post.html', {'form': form}) 27 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part1/pre-ajax/talk_project/talk_project/__init__.py -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for talk_project project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | 14 | SETTINGS_DIR = os.path.dirname(__file__) 15 | PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir) 16 | PROJECT_ROOT = os.path.abspath(PROJECT_PATH) 17 | 18 | TEMPLATE_DIRS = ( 19 | os.path.join(PROJECT_ROOT, 'templates'), 20 | ) 21 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 22 | 23 | LOGIN_URL = '/login/' 24 | 25 | LOGOUT_URL = '/logout/' 26 | 27 | # LOGIN_REDIRECT_URL = '/accounts/profile/' 28 | 29 | # Quick-start development settings - unsuitable for production 30 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 31 | 32 | # SECURITY WARNING: keep the secret key used in production secret! 33 | SECRET_KEY = 'u)vhj6nj*)(i(8zg2f0!j=xwg+309om2v@o$-sn0l9a5u0=%+7' 34 | 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | DEBUG = True 37 | 38 | TEMPLATE_DEBUG = True 39 | 40 | ALLOWED_HOSTS = [] 41 | 42 | 43 | # Application definition 44 | 45 | INSTALLED_APPS = ( 46 | 'django.contrib.admin', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.sessions', 50 | 'django.contrib.messages', 51 | 'django.contrib.staticfiles', 52 | 'talk' 53 | ) 54 | 55 | MIDDLEWARE_CLASSES = ( 56 | 'django.contrib.sessions.middleware.SessionMiddleware', 57 | 'django.middleware.common.CommonMiddleware', 58 | 'django.middleware.csrf.CsrfViewMiddleware', 59 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 60 | 'django.contrib.messages.middleware.MessageMiddleware', 61 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 62 | 'talk.middleware.RequireLoginMiddleware', 63 | ) 64 | 65 | LOGIN_REQUIRED_URLS = ( 66 | r'/(.*)$', # TODO interpret this regex. 67 | ) 68 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 69 | r'/login(.*)$', 70 | r'/logout(.*)$', 71 | r'/staff(.*)$', 72 | ) 73 | 74 | TEMPLATE_CONTEXT_PROCESSORS = ( 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.core.context_processors.request', 77 | ) 78 | 79 | ROOT_URLCONF = 'talk_project.urls' 80 | 81 | WSGI_APPLICATION = 'talk_project.wsgi.application' 82 | 83 | 84 | # Database 85 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 86 | 87 | DATABASES = { 88 | 'default': { 89 | 'ENGINE': 'django.db.backends.sqlite3', 90 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 91 | } 92 | } 93 | 94 | # Internationalization 95 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 96 | 97 | LANGUAGE_CODE = 'en-us' 98 | 99 | TIME_ZONE = 'UTC' 100 | 101 | USE_I18N = True 102 | 103 | USE_L10N = True 104 | 105 | USE_TZ = True 106 | 107 | 108 | # Static files (CSS, JavaScript, Images) 109 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 110 | 111 | STATIC_URL = '/static/' 112 | 113 | STATICFILES_DIRS = ( 114 | os.path.join(BASE_DIR, 'static'), 115 | ) 116 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from talk_project.views import logout_page 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | 8 | urlpatterns = patterns( 9 | '', 10 | url(r'^admin/', include(admin.site.urls)), 11 | url(r'^login/', 'django.contrib.auth.views.login'), 12 | (r'^logout/$', logout_page), 13 | url(r'^', include('talk.urls')), 14 | ) 15 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk_project/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | def logout_page(request): 6 | """ 7 | Log users out and re-direct them to the main page. 8 | """ 9 | logout(request) 10 | return HttpResponseRedirect('/') 11 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/talk_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for talk_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %} 9 | 10 | {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 |
    17 | {% block content %} 18 | {% endblock %} 19 |
    20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | Login 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
    10 |

    11 |
    12 | 13 |
    14 | {% if form.errors %} 15 |
    Oh fiddlesticks! Please try again.
    16 | {% endif %} 17 | 18 |
    19 |
    20 | {% csrf_token %} 21 |
    22 | Login 23 |

    24 | 25 | {{ form.username }} 26 |

    27 |

    28 | 29 | {{ form.password }} 30 |

    31 | {% if next %} 32 | 33 | {% else %} 34 | 35 | {% endif %} 36 | 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/templates/talk/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | talk to me 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 51 | 52 | {% block head %} 53 | {% endblock %} 54 | 55 | 56 | 57 | 58 | {% block content %} 59 | {% endblock %} 60 | 61 | -------------------------------------------------------------------------------- /part1/pre-ajax/talk_project/templates/talk/index.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% block content %} 3 |

    4 |
    5 |
    6 | Logout 7 |

    Hi, {{request.user.username}}

    8 |
    9 |
    10 |
    11 |
    12 |
    13 | {% csrf_token %} 14 |
    15 | {{ form.text }} 16 |
    17 | 18 |
    19 |
    20 |
    21 | 22 | 23 |
    24 |
    25 | 34 |
    35 |
    36 | 37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /part2/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.6 2 | -------------------------------------------------------------------------------- /part2/talk_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /part2/talk_project/static/scripts/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | 4 | // Submit post on submit 5 | $('#post-form').on('submit', function(event){ 6 | event.preventDefault(); 7 | console.log("form submitted!") // sanity check 8 | create_post(); 9 | }); 10 | 11 | // Delete post on click 12 | $("#talk").on('click', 'a[id^=delete-post-]', function(){ 13 | var post_primary_key = $(this).attr('id').split('-')[2]; 14 | console.log(post_primary_key) // sanity check 15 | delete_post(post_primary_key); 16 | }); 17 | 18 | // AJAX for posting 19 | function create_post() { 20 | console.log("create post is working!") // sanity check 21 | $.ajax({ 22 | url : "create_post/", // the endpoint 23 | type : "POST", // http method 24 | data : { the_post : $('#post-text').val() }, // data sent with the post request 25 | // handle a successful response 26 | success : function(json) { 27 | $('#post-text').val(''); // remove the value from the input 28 | console.log(json); // log the returned json to the console 29 | $("#talk").prepend("
  • "+json.text+" - "+json.author+" - "+json.created+ 30 | " - delete me
  • "); 31 | console.log("success"); // another sanity check 32 | }, 33 | // handle a non-successful response 34 | error : function(xhr,errmsg,err) { 35 | $('#results').html("
    Oops! We have encountered an error: "+errmsg+ 36 | " ×
    "); // add the error to the dom 37 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 38 | } 39 | }); 40 | }; 41 | 42 | // AJAX for deleting 43 | function delete_post(post_primary_key){ 44 | if (confirm('are you sure you want to remove this post?')==true){ 45 | $.ajax({ 46 | url : "delete_post/", // the endpoint 47 | type : "DELETE", // http method 48 | data : { postpk : post_primary_key }, // data sent with the delete request 49 | success : function(json) { 50 | // hide the post 51 | $('#post-'+post_primary_key).hide(); // hide the post on success 52 | console.log("post deletion successful"); 53 | }, 54 | 55 | error : function(xhr,errmsg,err) { 56 | // Show an error 57 | $('#results').html("
    "+ 58 | "Oops! We have encountered an error. ×
    "); // add error to the dom 59 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 60 | } 61 | }); 62 | } else { 63 | return false; 64 | } 65 | }; 66 | 67 | 68 | // This function gets cookie with a given name 69 | function getCookie(name) { 70 | var cookieValue = null; 71 | if (document.cookie && document.cookie != '') { 72 | var cookies = document.cookie.split(';'); 73 | for (var i = 0; i < cookies.length; i++) { 74 | var cookie = jQuery.trim(cookies[i]); 75 | // Does this cookie string begin with the name we want? 76 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 77 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 78 | break; 79 | } 80 | } 81 | } 82 | return cookieValue; 83 | } 84 | var csrftoken = getCookie('csrftoken'); 85 | 86 | /* 87 | The functions below will create a header with csrftoken 88 | */ 89 | 90 | function csrfSafeMethod(method) { 91 | // these HTTP methods do not require CSRF protection 92 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 93 | } 94 | function sameOrigin(url) { 95 | // test that a given url is a same-origin URL 96 | // url could be relative or scheme relative or absolute 97 | var host = document.location.host; // host + port 98 | var protocol = document.location.protocol; 99 | var sr_origin = '//' + host; 100 | var origin = protocol + sr_origin; 101 | // Allow absolute or scheme relative URLs to same origin 102 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 103 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 104 | // or any other URL that isn't scheme relative or absolute i.e relative. 105 | !(/^(\/\/|http:|https:).*/.test(url)); 106 | } 107 | 108 | $.ajaxSetup({ 109 | beforeSend: function(xhr, settings) { 110 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { 111 | // Send the token to same-origin, relative URLs only. 112 | // Send the token only if the method warrants CSRF protection 113 | // Using the CSRFToken value acquired earlier 114 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 115 | } 116 | } 117 | }); 118 | 119 | }); -------------------------------------------------------------------------------- /part2/talk_project/talk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part2/talk_project/talk/__init__.py -------------------------------------------------------------------------------- /part2/talk_project/talk/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | # Register your models here. 5 | admin.site.register(Post) 6 | -------------------------------------------------------------------------------- /part2/talk_project/talk/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from talk.models import Post 3 | 4 | 5 | class PostForm(forms.ModelForm): 6 | class Meta: 7 | model = Post 8 | # exclude = ['author', 'updated', 'created', ] 9 | fields = ['text'] 10 | widgets = { 11 | 'text': forms.TextInput( 12 | attrs={'id': 'post-text', 'required': True, 'placeholder': 'Say something...'} 13 | ), 14 | } 15 | -------------------------------------------------------------------------------- /part2/talk_project/talk/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | 7 | class RequireLoginMiddleware(object): 8 | """ 9 | Middleware component that wraps the login_required decorator around 10 | matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and 11 | define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your 12 | settings.py. For example: 13 | ------ 14 | LOGIN_REQUIRED_URLS = ( 15 | r'/topsecret/(.*)$', 16 | ) 17 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 18 | r'/topsecret/login(.*)$', 19 | r'/topsecret/logout(.*)$', 20 | ) 21 | ------ 22 | LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must 23 | be a valid regex. 24 | 25 | LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly 26 | define any exceptions (like login and logout URLs). 27 | """ 28 | def __init__(self): 29 | self.required = tuple( 30 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS 31 | ) 32 | self.exceptions = tuple( 33 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS 34 | ) 35 | 36 | def process_view(self, request, view_func, view_args, view_kwargs): 37 | # No need to process URLs if user already logged in 38 | if request.user.is_authenticated(): 39 | return None 40 | 41 | # An exception match should immediately return None 42 | for url in self.exceptions: 43 | if url.match(request.path): 44 | return None 45 | 46 | # Requests matching a restricted URL pattern are returned 47 | # wrapped with the login_required decorator 48 | for url in self.required: 49 | if url.match(request.path): 50 | return login_required(view_func)(request, *view_args, **view_kwargs) 51 | 52 | # Explicitly return None for all non-matching requests 53 | return None 54 | 55 | 56 | class MaintenanceMiddleware(object): 57 | """Serve a temporary redirect to a maintenance url in maintenance mode""" 58 | def process_request(self, request): 59 | if request.method == 'POST': 60 | if getattr(settings, 'MAINTENANCE_MODE', False) is True \ 61 | and hasattr(settings, 'MAINTENANCE_URL'): 62 | # http? where is that defined? 63 | return http.HttpResponseRedirect(settings.MAINTENANCE_URL) 64 | return None 65 | -------------------------------------------------------------------------------- /part2/talk_project/talk/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | # Create your models here. 5 | 6 | 7 | class Post(models.Model): 8 | author = models.ForeignKey(User) 9 | text = models.TextField() 10 | 11 | # Time is a rhinocerous 12 | updated = models.DateTimeField(auto_now=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | 15 | class Meta: 16 | ordering = ['created'] 17 | 18 | def __unicode__(self): 19 | return self.text+' - '+self.author.username 20 | -------------------------------------------------------------------------------- /part2/talk_project/talk/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import resolve 3 | 4 | 5 | class HomeViewTest(TestCase): 6 | 7 | def test_index(self): 8 | """Ensure main url is connected to the talk.views.home view""" 9 | resolver = resolve('/') 10 | self.assertEqual(resolver.view_name, 'talk.views.home') 11 | -------------------------------------------------------------------------------- /part2/talk_project/talk/urls.py: -------------------------------------------------------------------------------- 1 | # Talk urls 2 | from django.conf.urls import patterns, url 3 | 4 | 5 | urlpatterns = patterns( 6 | 'talk.views', 7 | url(r'^$', 'home'), 8 | url(r'^create_post/$', 'create_post'), 9 | url(r'^delete_post/$', 'delete_post'), 10 | ) 11 | -------------------------------------------------------------------------------- /part2/talk_project/talk/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse 3 | from django.http import QueryDict 4 | from talk.models import Post 5 | from talk.forms import PostForm 6 | 7 | import json 8 | 9 | 10 | def home(req): 11 | 12 | tmpl_vars = { 13 | 'all_posts': Post.objects.reverse(), 14 | 'form': PostForm() 15 | } 16 | return render(req, 'talk/index.html', tmpl_vars) 17 | 18 | 19 | # def create_post(request): 20 | # if request.method == 'POST': 21 | # form = PostForm(request.POST) 22 | # if form.is_valid(): 23 | # post = form.save(commit=False) 24 | # post.author = request.user 25 | # post.save() 26 | # return HttpResponseRedirect('/') 27 | # else: 28 | # form = PostForm() 29 | # return render(request, 'post.html', {'form': form}) 30 | 31 | def create_post(request): 32 | if request.method == 'POST': 33 | post_text = request.POST.get('the_post') 34 | response_data = {} 35 | 36 | post = Post(text=post_text, author=request.user) 37 | post.save() 38 | 39 | response_data['result'] = 'Create post successful!' 40 | response_data['postpk'] = post.pk 41 | response_data['text'] = post.text 42 | response_data['created'] = post.created.strftime('%B %d, %Y %I:%M %p') 43 | response_data['author'] = post.author.username 44 | 45 | return HttpResponse( 46 | json.dumps(response_data), 47 | content_type="application/json" 48 | ) 49 | else: 50 | return HttpResponse( 51 | json.dumps({"nothing to see": "this isn't happening"}), 52 | content_type="application/json" 53 | ) 54 | 55 | 56 | def delete_post(request): 57 | 58 | if request.method == 'DELETE': 59 | 60 | post = Post.objects.get(pk=int(QueryDict(request.body).get('postpk'))) 61 | 62 | post.delete() 63 | 64 | response_data = {} 65 | response_data['msg'] = 'Post was deleted.' 66 | 67 | return HttpResponse( 68 | json.dumps(response_data), 69 | content_type="application/json" 70 | ) 71 | else: 72 | return HttpResponse( 73 | json.dumps({"nothing to see": "this isn't happening"}), 74 | content_type="application/json" 75 | ) 76 | -------------------------------------------------------------------------------- /part2/talk_project/talk_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part2/talk_project/talk_project/__init__.py -------------------------------------------------------------------------------- /part2/talk_project/talk_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for talk_project project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | 14 | SETTINGS_DIR = os.path.dirname(__file__) 15 | PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir) 16 | PROJECT_ROOT = os.path.abspath(PROJECT_PATH) 17 | 18 | TEMPLATE_DIRS = ( 19 | os.path.join(PROJECT_ROOT, 'templates'), 20 | ) 21 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 22 | 23 | LOGIN_URL = '/login/' 24 | 25 | LOGOUT_URL = '/logout/' 26 | 27 | # LOGIN_REDIRECT_URL = '/accounts/profile/' 28 | 29 | # Quick-start development settings - unsuitable for production 30 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 31 | 32 | # SECURITY WARNING: keep the secret key used in production secret! 33 | SECRET_KEY = 'u)vhj6nj*)(i(8zg2f0!j=xwg+309om2v@o$-sn0l9a5u0=%+7' 34 | 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | DEBUG = True 37 | 38 | TEMPLATE_DEBUG = True 39 | 40 | ALLOWED_HOSTS = [] 41 | 42 | 43 | # Application definition 44 | 45 | INSTALLED_APPS = ( 46 | 'django.contrib.admin', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.sessions', 50 | 'django.contrib.messages', 51 | 'django.contrib.staticfiles', 52 | 'talk' 53 | ) 54 | 55 | MIDDLEWARE_CLASSES = ( 56 | 'django.contrib.sessions.middleware.SessionMiddleware', 57 | 'django.middleware.common.CommonMiddleware', 58 | 'django.middleware.csrf.CsrfViewMiddleware', 59 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 60 | 'django.contrib.messages.middleware.MessageMiddleware', 61 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 62 | 'talk.middleware.RequireLoginMiddleware', 63 | ) 64 | 65 | LOGIN_REQUIRED_URLS = ( 66 | r'/(.*)$', # TODO interpret this regex. 67 | ) 68 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 69 | r'/login(.*)$', 70 | r'/logout(.*)$', 71 | r'/staff(.*)$', 72 | ) 73 | 74 | TEMPLATE_CONTEXT_PROCESSORS = ( 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.core.context_processors.request', 77 | ) 78 | 79 | ROOT_URLCONF = 'talk_project.urls' 80 | 81 | WSGI_APPLICATION = 'talk_project.wsgi.application' 82 | 83 | 84 | # Database 85 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 86 | 87 | DATABASES = { 88 | 'default': { 89 | 'ENGINE': 'django.db.backends.sqlite3', 90 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 91 | } 92 | } 93 | 94 | # Internationalization 95 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 96 | 97 | LANGUAGE_CODE = 'en-us' 98 | 99 | TIME_ZONE = 'UTC' 100 | 101 | USE_I18N = True 102 | 103 | USE_L10N = True 104 | 105 | USE_TZ = True 106 | 107 | 108 | # Static files (CSS, JavaScript, Images) 109 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 110 | 111 | STATIC_URL = '/static/' 112 | 113 | STATICFILES_DIRS = ( 114 | os.path.join(BASE_DIR, 'static'), 115 | ) 116 | -------------------------------------------------------------------------------- /part2/talk_project/talk_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from talk_project.views import logout_page 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | 8 | urlpatterns = patterns( 9 | '', 10 | url(r'^admin/', include(admin.site.urls)), 11 | url(r'^login/', 'django.contrib.auth.views.login'), 12 | (r'^logout/$', logout_page), 13 | url(r'^', include('talk.urls')), 14 | ) 15 | -------------------------------------------------------------------------------- /part2/talk_project/talk_project/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | def logout_page(request): 6 | """ 7 | Log users out and re-direct them to the main page. 8 | """ 9 | logout(request) 10 | return HttpResponseRedirect('/') 11 | -------------------------------------------------------------------------------- /part2/talk_project/talk_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for talk_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /part2/talk_project/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %} 9 | 10 | {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 |
    17 | {% block content %} 18 | {% endblock %} 19 |
    20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /part2/talk_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | Login 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
    10 |

    11 |
    12 | 13 |
    14 | {% if form.errors %} 15 |
    Oh fiddlesticks! Please try again.
    16 | {% endif %} 17 | 18 |
    19 |
    20 | {% csrf_token %} 21 |
    22 | Login 23 |

    24 | 25 | {{ form.username }} 26 |

    27 |

    28 | 29 | {{ form.password }} 30 |

    31 | {% if next %} 32 | 33 | {% else %} 34 | 35 | {% endif %} 36 | 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /part2/talk_project/templates/talk/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | talk to me 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 51 | 52 | {% block head %} 53 | {% endblock %} 54 | 55 | 56 | 57 | 58 | {% block content %} 59 | {% endblock %} 60 | 61 | -------------------------------------------------------------------------------- /part2/talk_project/templates/talk/index.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% block content %} 3 |

    4 |
    5 |
    6 | Logout 7 |

    Hi, {{request.user.username}}

    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | {% csrf_token %} 15 |
    16 | {{ form.text }} 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 | 24 | 25 |
    26 |
    27 | 37 |
    38 |
    39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /part3/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.6 2 | djangorestframework==2.4.2 3 | wsgiref==0.1.2 4 | -------------------------------------------------------------------------------- /part3/talk_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /part3/talk_project/static/scripts/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | load_posts() 4 | 5 | // Load all posts on page load 6 | function load_posts() { 7 | $.ajax({ 8 | url : "api/v1/posts/", // the endpoint 9 | type : "GET", // http method 10 | // handle a successful response 11 | success : function(json) { 12 | for (var i = 0; i < json.length; i++) { 13 | dateString = convert_to_readable_date(json[i].created) 14 | $("#talk").prepend("
  • "+json[i].text+ 15 | " - "+json[i].author+" - "+dateString+ 16 | " - delete me
  • "); 17 | } 18 | }, 19 | // handle a non-successful response 20 | error : function(xhr,errmsg,err) { 21 | $('#results').html("
    Oops! We have encountered an error: "+errmsg+ 22 | " ×
    "); // add the error to the dom 23 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 24 | } 25 | }); 26 | }; 27 | 28 | // convert ugly date to human readable date 29 | function convert_to_readable_date(date_time_string) { 30 | var newDate = moment(date_time_string).format('MM/DD/YYYY, h:mm:ss a') 31 | return newDate 32 | } 33 | 34 | // Submit post on submit 35 | $('#post-form').on('submit', function(event){ 36 | event.preventDefault(); 37 | console.log("form submitted!") // sanity check 38 | create_post(); 39 | }); 40 | 41 | // Delete post on click 42 | $("#talk").on('click', 'a[id^=delete-post-]', function(){ 43 | var post_primary_key = $(this).attr('id').split('-')[2]; 44 | console.log(post_primary_key) // sanity check 45 | delete_post(post_primary_key); 46 | }); 47 | 48 | // AJAX for posting 49 | function create_post() { 50 | console.log("create post is working!") // sanity check 51 | $.ajax({ 52 | url : "api/v1/posts/", // the endpoint 53 | type : "POST", // http method 54 | data : { the_post : $('#post-text').val() }, // data sent with the post request 55 | // handle a successful response 56 | success : function(json) { 57 | $('#post-text').val(''); // remove the value from the input 58 | console.log(json); // log the returned json to the console 59 | dateString = convert_to_readable_date(json.created) 60 | $("#talk").prepend("
  • "+json.text+" - "+ 61 | json.author+" - "+dateString+ 62 | " - delete me
  • "); 63 | console.log("success"); // another sanity check 64 | }, 65 | // handle a non-successful response 66 | error : function(xhr,errmsg,err) { 67 | $('#results').html("
    Oops! We have encountered an error: "+errmsg+ 68 | " ×
    "); // add the error to the dom 69 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 70 | } 71 | }); 72 | }; 73 | 74 | // AJAX for deleting 75 | function delete_post(post_primary_key){ 76 | if (confirm('are you sure you want to remove this post?')==true){ 77 | $.ajax({ 78 | url : "api/v1/posts/"+post_primary_key, // the endpoint 79 | type : "DELETE", // http method 80 | data : { postpk : post_primary_key }, // data sent with the delete request 81 | success : function(json) { 82 | // hide the post 83 | $('#post-'+post_primary_key).hide(); // hide the post on success 84 | console.log("post deletion successful"); 85 | }, 86 | 87 | error : function(xhr,errmsg,err) { 88 | // Show an error 89 | $('#results').html("
    "+ 90 | "Oops! We have encountered an error. ×
    "); // add error to the dom 91 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 92 | } 93 | }); 94 | } else { 95 | return false; 96 | } 97 | }; 98 | 99 | 100 | // This function gets cookie with a given name 101 | function getCookie(name) { 102 | var cookieValue = null; 103 | if (document.cookie && document.cookie != '') { 104 | var cookies = document.cookie.split(';'); 105 | for (var i = 0; i < cookies.length; i++) { 106 | var cookie = jQuery.trim(cookies[i]); 107 | // Does this cookie string begin with the name we want? 108 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 109 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 110 | break; 111 | } 112 | } 113 | } 114 | return cookieValue; 115 | } 116 | var csrftoken = getCookie('csrftoken'); 117 | 118 | /* 119 | The functions below will create a header with csrftoken 120 | */ 121 | 122 | function csrfSafeMethod(method) { 123 | // these HTTP methods do not require CSRF protection 124 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 125 | } 126 | function sameOrigin(url) { 127 | // test that a given url is a same-origin URL 128 | // url could be relative or scheme relative or absolute 129 | var host = document.location.host; // host + port 130 | var protocol = document.location.protocol; 131 | var sr_origin = '//' + host; 132 | var origin = protocol + sr_origin; 133 | // Allow absolute or scheme relative URLs to same origin 134 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 135 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 136 | // or any other URL that isn't scheme relative or absolute i.e relative. 137 | !(/^(\/\/|http:|https:).*/.test(url)); 138 | } 139 | 140 | $.ajaxSetup({ 141 | beforeSend: function(xhr, settings) { 142 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { 143 | // Send the token to same-origin, relative URLs only. 144 | // Send the token only if the method warrants CSRF protection 145 | // Using the CSRFToken value acquired earlier 146 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 147 | } 148 | } 149 | }); 150 | 151 | }); -------------------------------------------------------------------------------- /part3/talk_project/talk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part3/talk_project/talk/__init__.py -------------------------------------------------------------------------------- /part3/talk_project/talk/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | # Register your models here. 5 | admin.site.register(Post) 6 | -------------------------------------------------------------------------------- /part3/talk_project/talk/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from talk.models import Post 3 | 4 | 5 | class PostForm(forms.ModelForm): 6 | class Meta: 7 | model = Post 8 | # exclude = ['author', 'updated', 'created', ] 9 | fields = ['text'] 10 | widgets = { 11 | 'text': forms.TextInput( 12 | attrs={'id': 'post-text', 'required': True, 'placeholder': 'Say something...'} 13 | ), 14 | } 15 | -------------------------------------------------------------------------------- /part3/talk_project/talk/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | 7 | class RequireLoginMiddleware(object): 8 | """ 9 | Middleware component that wraps the login_required decorator around 10 | matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and 11 | define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your 12 | settings.py. For example: 13 | ------ 14 | LOGIN_REQUIRED_URLS = ( 15 | r'/topsecret/(.*)$', 16 | ) 17 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 18 | r'/topsecret/login(.*)$', 19 | r'/topsecret/logout(.*)$', 20 | ) 21 | ------ 22 | LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must 23 | be a valid regex. 24 | 25 | LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly 26 | define any exceptions (like login and logout URLs). 27 | """ 28 | def __init__(self): 29 | self.required = tuple( 30 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS 31 | ) 32 | self.exceptions = tuple( 33 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS 34 | ) 35 | 36 | def process_view(self, request, view_func, view_args, view_kwargs): 37 | # No need to process URLs if user already logged in 38 | if request.user.is_authenticated(): 39 | return None 40 | 41 | # An exception match should immediately return None 42 | for url in self.exceptions: 43 | if url.match(request.path): 44 | return None 45 | 46 | # Requests matching a restricted URL pattern are returned 47 | # wrapped with the login_required decorator 48 | for url in self.required: 49 | if url.match(request.path): 50 | return login_required(view_func)(request, *view_args, **view_kwargs) 51 | 52 | # Explicitly return None for all non-matching requests 53 | return None 54 | 55 | 56 | class MaintenanceMiddleware(object): 57 | """Serve a temporary redirect to a maintenance url in maintenance mode""" 58 | def process_request(self, request): 59 | if request.method == 'POST': 60 | if getattr(settings, 'MAINTENANCE_MODE', False) is True \ 61 | and hasattr(settings, 'MAINTENANCE_URL'): 62 | # http? where is that defined? 63 | return http.HttpResponseRedirect(settings.MAINTENANCE_URL) 64 | return None 65 | -------------------------------------------------------------------------------- /part3/talk_project/talk/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | # Create your models here. 5 | 6 | 7 | class Post(models.Model): 8 | author = models.ForeignKey(User) 9 | text = models.TextField() 10 | 11 | # Time is a rhinocerous 12 | updated = models.DateTimeField(auto_now=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | 15 | class Meta: 16 | ordering = ['created'] 17 | 18 | def __unicode__(self): 19 | return self.text+' - '+self.author.username 20 | -------------------------------------------------------------------------------- /part3/talk_project/talk/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework import serializers 3 | from talk.models import Post 4 | 5 | 6 | class PostSerializer(serializers.ModelSerializer): 7 | author = serializers.SlugRelatedField( 8 | queryset=User.objects.all(), slug_field='username' 9 | ) 10 | 11 | class Meta: 12 | model = Post 13 | fields = ('id', 'author', 'text', 'created', 'updated') 14 | -------------------------------------------------------------------------------- /part3/talk_project/talk/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import resolve 3 | 4 | 5 | class HomeViewTest(TestCase): 6 | 7 | def test_index(self): 8 | """Ensure main url is connected to the talk.views.home view""" 9 | resolver = resolve('/') 10 | self.assertEqual(resolver.view_name, 'talk.views.home') 11 | -------------------------------------------------------------------------------- /part3/talk_project/talk/urls.py: -------------------------------------------------------------------------------- 1 | # Talk urls 2 | from django.conf.urls import patterns, url 3 | 4 | 5 | urlpatterns = patterns( 6 | 'talk.views', 7 | url(r'^$', 'home'), 8 | 9 | # api 10 | url(r'^api/v1/posts/$', 'post_collection'), 11 | url(r'^api/v1/posts/(?P[0-9]+)$', 'post_element') 12 | ) 13 | -------------------------------------------------------------------------------- /part3/talk_project/talk/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse 3 | from rest_framework.decorators import api_view 4 | from rest_framework.response import Response 5 | from rest_framework import status 6 | from talk.models import Post 7 | from talk.serializers import PostSerializer 8 | from talk.forms import PostForm 9 | 10 | 11 | def home(request): 12 | tmpl_vars = {'form': PostForm()} 13 | return render(request, 'talk/index.html', tmpl_vars) 14 | 15 | 16 | @api_view(['GET', 'POST']) 17 | def post_collection(request): 18 | if request.method == 'GET': 19 | posts = Post.objects.all() 20 | serializer = PostSerializer(posts, many=True) 21 | return Response(serializer.data) 22 | elif request.method == 'POST': 23 | data = {'text': request.DATA.get('the_post'), 'author': request.user} 24 | serializer = PostSerializer(data=data) 25 | if serializer.is_valid(): 26 | serializer.save() 27 | return Response(serializer.data, status=status.HTTP_201_CREATED) 28 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 29 | 30 | 31 | @api_view(['GET', 'DELETE']) 32 | def post_element(request, pk): 33 | try: 34 | post = Post.objects.get(pk=pk) 35 | except Post.DoesNotExist: 36 | return HttpResponse(status=404) 37 | 38 | if request.method == 'GET': 39 | serializer = PostSerializer(post) 40 | return Response(serializer.data) 41 | 42 | elif request.method == 'DELETE': 43 | post.delete() 44 | return Response(status=status.HTTP_204_NO_CONTENT) 45 | -------------------------------------------------------------------------------- /part3/talk_project/talk_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part3/talk_project/talk_project/__init__.py -------------------------------------------------------------------------------- /part3/talk_project/talk_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for talk_project project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | 14 | SETTINGS_DIR = os.path.dirname(__file__) 15 | PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir) 16 | PROJECT_ROOT = os.path.abspath(PROJECT_PATH) 17 | 18 | TEMPLATE_DIRS = ( 19 | os.path.join(PROJECT_ROOT, 'templates'), 20 | ) 21 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 22 | 23 | LOGIN_URL = '/login/' 24 | 25 | LOGOUT_URL = '/logout/' 26 | 27 | # LOGIN_REDIRECT_URL = '/accounts/profile/' 28 | 29 | # Quick-start development settings - unsuitable for production 30 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 31 | 32 | # SECURITY WARNING: keep the secret key used in production secret! 33 | SECRET_KEY = 'u)vhj6nj*)(i(8zg2f0!j=xwg+309om2v@o$-sn0l9a5u0=%+7' 34 | 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | DEBUG = True 37 | 38 | TEMPLATE_DEBUG = True 39 | 40 | ALLOWED_HOSTS = [] 41 | 42 | 43 | # Application definition 44 | 45 | INSTALLED_APPS = ( 46 | 'django.contrib.admin', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.sessions', 50 | 'django.contrib.messages', 51 | 'django.contrib.staticfiles', 52 | 'talk', 53 | 'rest_framework' 54 | ) 55 | 56 | MIDDLEWARE_CLASSES = ( 57 | 'django.contrib.sessions.middleware.SessionMiddleware', 58 | 'django.middleware.common.CommonMiddleware', 59 | 'django.middleware.csrf.CsrfViewMiddleware', 60 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 61 | 'django.contrib.messages.middleware.MessageMiddleware', 62 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 63 | 'talk.middleware.RequireLoginMiddleware', 64 | ) 65 | 66 | LOGIN_REQUIRED_URLS = ( 67 | r'/(.*)$', # TODO interpret this regex. 68 | ) 69 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 70 | r'/login(.*)$', 71 | r'/logout(.*)$', 72 | r'/staff(.*)$', 73 | ) 74 | 75 | TEMPLATE_CONTEXT_PROCESSORS = ( 76 | 'django.contrib.auth.context_processors.auth', 77 | 'django.core.context_processors.request', 78 | ) 79 | 80 | ROOT_URLCONF = 'talk_project.urls' 81 | 82 | WSGI_APPLICATION = 'talk_project.wsgi.application' 83 | 84 | 85 | # Database 86 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 87 | 88 | DATABASES = { 89 | 'default': { 90 | 'ENGINE': 'django.db.backends.sqlite3', 91 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 92 | } 93 | } 94 | 95 | # Internationalization 96 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 97 | 98 | LANGUAGE_CODE = 'en-us' 99 | 100 | TIME_ZONE = 'UTC' 101 | 102 | USE_I18N = True 103 | 104 | USE_L10N = True 105 | 106 | USE_TZ = True 107 | 108 | 109 | # Static files (CSS, JavaScript, Images) 110 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 111 | 112 | STATIC_URL = '/static/' 113 | 114 | STATICFILES_DIRS = ( 115 | os.path.join(BASE_DIR, 'static'), 116 | ) 117 | -------------------------------------------------------------------------------- /part3/talk_project/talk_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from talk_project.views import logout_page 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | 8 | urlpatterns = patterns( 9 | '', 10 | url(r'^admin/', include(admin.site.urls)), 11 | url(r'^login/', 'django.contrib.auth.views.login'), 12 | (r'^logout/$', logout_page), 13 | url(r'^', include('talk.urls')), 14 | ) 15 | -------------------------------------------------------------------------------- /part3/talk_project/talk_project/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | def logout_page(request): 6 | """ 7 | Log users out and re-direct them to the main page. 8 | """ 9 | logout(request) 10 | return HttpResponseRedirect('/') 11 | -------------------------------------------------------------------------------- /part3/talk_project/talk_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for talk_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /part3/talk_project/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %} 9 | 10 | {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 |
    17 | {% block content %} 18 | {% endblock %} 19 |
    20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /part3/talk_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | Login 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
    10 |

    11 |
    12 | 13 |
    14 | {% if form.errors %} 15 |
    Oh fiddlesticks! Please try again.
    16 | {% endif %} 17 | 18 |
    19 |
    20 | {% csrf_token %} 21 |
    22 | Login 23 |

    24 | 25 | {{ form.username }} 26 |

    27 |

    28 | 29 | {{ form.password }} 30 |

    31 | {% if next %} 32 | 33 | {% else %} 34 | 35 | {% endif %} 36 | 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /part3/talk_project/templates/talk/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | talk to me 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 51 | 52 | {% block head %} 53 | {% endblock %} 54 | 55 | 56 | 57 | 58 | {% block content %} 59 | {% endblock %} 60 | 61 | -------------------------------------------------------------------------------- /part3/talk_project/templates/talk/index.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% block content %} 3 |

    4 |
    5 |
    6 | Logout 7 |

    Hi, {{request.user.username}}

    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | {% csrf_token %} 15 |
    16 | {{ form.text }} 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 | 24 | 25 |
    26 |
    27 |
      28 | {% for post in all_posts %} 29 |
    • 30 | {{ post.text }} - 31 | {{ post.author }} - 32 | {{ post.created }} - 33 | delete me 34 |
    • 35 | {% endfor %} 36 |
    37 |
    38 |
    39 | 40 | 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /part4/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.6 2 | djangorestframework==2.4.2 3 | wsgiref==0.1.2 4 | -------------------------------------------------------------------------------- /part4/talk_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /part4/talk_project/static/scripts/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | load_posts() 4 | 5 | // Load all posts on page load 6 | function load_posts() { 7 | $.ajax({ 8 | url : "api/v1/posts/", // the endpoint 9 | type : "GET", // http method 10 | // handle a successful response 11 | success : function(json) { 12 | for (var i = 0; i < json.length; i++) { 13 | dateString = convert_to_readable_date(json[i].created) 14 | $("#talk").prepend("
  • "+json[i].text+ 15 | " - "+json[i].author+" - "+dateString+ 16 | " - delete me
  • "); 17 | } 18 | }, 19 | // handle a non-successful response 20 | error : function(xhr,errmsg,err) { 21 | $('#results').html("
    Oops! We have encountered an error: "+errmsg+ 22 | " ×
    "); // add the error to the dom 23 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 24 | } 25 | }); 26 | }; 27 | 28 | // convert ugly date to human readable date 29 | function convert_to_readable_date(date_time_string) { 30 | var newDate = moment(date_time_string).format('MM/DD/YYYY, h:mm:ss a') 31 | return newDate 32 | } 33 | 34 | // Submit post on submit 35 | $('#post-form').on('submit', function(event){ 36 | event.preventDefault(); 37 | console.log("form submitted!") // sanity check 38 | create_post(); 39 | }); 40 | 41 | // Delete post on click 42 | $("#talk").on('click', 'a[id^=delete-post-]', function(){ 43 | var post_primary_key = $(this).attr('id').split('-')[2]; 44 | console.log(post_primary_key) // sanity check 45 | delete_post(post_primary_key); 46 | }); 47 | 48 | // AJAX for posting 49 | function create_post() { 50 | console.log("create post is working!") // sanity check 51 | $.ajax({ 52 | url : "api/v1/posts/", // the endpoint 53 | type : "POST", // http method 54 | data : { text : $('#post-text').val(), author: $('#user').text()}, // data sent with the post request 55 | // handle a successful response 56 | success : function(json) { 57 | $('#post-text').val(''); // remove the value from the input 58 | console.log(json); // log the returned json to the console 59 | dateString = convert_to_readable_date(json.created) 60 | $("#talk").prepend("
  • "+json.text+" - "+ 61 | json.author+" - "+dateString+ 62 | " - delete me
  • "); 63 | console.log("success"); // another sanity check 64 | }, 65 | // handle a non-successful response 66 | error : function(xhr,errmsg,err) { 67 | $('#results').html("
    Oops! We have encountered an error: "+errmsg+ 68 | " ×
    "); // add the error to the dom 69 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 70 | } 71 | }); 72 | }; 73 | 74 | // AJAX for deleting 75 | function delete_post(post_primary_key){ 76 | if (confirm('are you sure you want to remove this post?')==true){ 77 | $.ajax({ 78 | url : "api/v1/posts/"+post_primary_key, // the endpoint 79 | type : "DELETE", // http method 80 | data : { postpk : post_primary_key }, // data sent with the delete request 81 | success : function(json) { 82 | // hide the post 83 | $('#post-'+post_primary_key).hide(); // hide the post on success 84 | console.log("post deletion successful"); 85 | }, 86 | 87 | error : function(xhr,errmsg,err) { 88 | // Show an error 89 | $('#results').html("
    "+ 90 | "Oops! We have encountered an error. ×
    "); // add error to the dom 91 | console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console 92 | } 93 | }); 94 | } else { 95 | return false; 96 | } 97 | }; 98 | 99 | 100 | // This function gets cookie with a given name 101 | function getCookie(name) { 102 | var cookieValue = null; 103 | if (document.cookie && document.cookie != '') { 104 | var cookies = document.cookie.split(';'); 105 | for (var i = 0; i < cookies.length; i++) { 106 | var cookie = jQuery.trim(cookies[i]); 107 | // Does this cookie string begin with the name we want? 108 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 109 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 110 | break; 111 | } 112 | } 113 | } 114 | return cookieValue; 115 | } 116 | var csrftoken = getCookie('csrftoken'); 117 | 118 | /* 119 | The functions below will create a header with csrftoken 120 | */ 121 | 122 | function csrfSafeMethod(method) { 123 | // these HTTP methods do not require CSRF protection 124 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 125 | } 126 | function sameOrigin(url) { 127 | // test that a given url is a same-origin URL 128 | // url could be relative or scheme relative or absolute 129 | var host = document.location.host; // host + port 130 | var protocol = document.location.protocol; 131 | var sr_origin = '//' + host; 132 | var origin = protocol + sr_origin; 133 | // Allow absolute or scheme relative URLs to same origin 134 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 135 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 136 | // or any other URL that isn't scheme relative or absolute i.e relative. 137 | !(/^(\/\/|http:|https:).*/.test(url)); 138 | } 139 | 140 | $.ajaxSetup({ 141 | beforeSend: function(xhr, settings) { 142 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { 143 | // Send the token to same-origin, relative URLs only. 144 | // Send the token only if the method warrants CSRF protection 145 | // Using the CSRFToken value acquired earlier 146 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 147 | } 148 | } 149 | }); 150 | 151 | }); -------------------------------------------------------------------------------- /part4/talk_project/talk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part4/talk_project/talk/__init__.py -------------------------------------------------------------------------------- /part4/talk_project/talk/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | # Register your models here. 5 | admin.site.register(Post) 6 | -------------------------------------------------------------------------------- /part4/talk_project/talk/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from talk.models import Post 3 | 4 | 5 | class PostForm(forms.ModelForm): 6 | class Meta: 7 | model = Post 8 | # exclude = ['author', 'updated', 'created', ] 9 | fields = ['text'] 10 | widgets = { 11 | 'text': forms.TextInput( 12 | attrs={'id': 'post-text', 'required': True, 'placeholder': 'Say something...'} 13 | ), 14 | } 15 | -------------------------------------------------------------------------------- /part4/talk_project/talk/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | 7 | class RequireLoginMiddleware(object): 8 | """ 9 | Middleware component that wraps the login_required decorator around 10 | matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and 11 | define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your 12 | settings.py. For example: 13 | ------ 14 | LOGIN_REQUIRED_URLS = ( 15 | r'/topsecret/(.*)$', 16 | ) 17 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 18 | r'/topsecret/login(.*)$', 19 | r'/topsecret/logout(.*)$', 20 | ) 21 | ------ 22 | LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must 23 | be a valid regex. 24 | 25 | LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly 26 | define any exceptions (like login and logout URLs). 27 | """ 28 | def __init__(self): 29 | self.required = tuple( 30 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS 31 | ) 32 | self.exceptions = tuple( 33 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS 34 | ) 35 | 36 | def process_view(self, request, view_func, view_args, view_kwargs): 37 | # No need to process URLs if user already logged in 38 | if request.user.is_authenticated(): 39 | return None 40 | 41 | # An exception match should immediately return None 42 | for url in self.exceptions: 43 | if url.match(request.path): 44 | return None 45 | 46 | # Requests matching a restricted URL pattern are returned 47 | # wrapped with the login_required decorator 48 | for url in self.required: 49 | if url.match(request.path): 50 | return login_required(view_func)(request, *view_args, **view_kwargs) 51 | 52 | # Explicitly return None for all non-matching requests 53 | return None 54 | 55 | 56 | class MaintenanceMiddleware(object): 57 | """Serve a temporary redirect to a maintenance url in maintenance mode""" 58 | def process_request(self, request): 59 | if request.method == 'POST': 60 | if getattr(settings, 'MAINTENANCE_MODE', False) is True \ 61 | and hasattr(settings, 'MAINTENANCE_URL'): 62 | # http? where is that defined? 63 | return http.HttpResponseRedirect(settings.MAINTENANCE_URL) 64 | return None 65 | -------------------------------------------------------------------------------- /part4/talk_project/talk/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | # Create your models here. 5 | 6 | 7 | class Post(models.Model): 8 | author = models.ForeignKey(User) 9 | text = models.TextField() 10 | 11 | # Time is a rhinocerous 12 | updated = models.DateTimeField(auto_now=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | 15 | class Meta: 16 | ordering = ['created'] 17 | 18 | def __unicode__(self): 19 | return self.text+' - '+self.author.username 20 | -------------------------------------------------------------------------------- /part4/talk_project/talk/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework import serializers 3 | from talk.models import Post 4 | 5 | 6 | class PostSerializer(serializers.ModelSerializer): 7 | 8 | author = serializers.SlugRelatedField( 9 | queryset=User.objects.filter(), slug_field='username' 10 | ) 11 | 12 | class Meta: 13 | model = Post 14 | fields = ('id', 'author', 'text', 'created', 'updated') 15 | -------------------------------------------------------------------------------- /part4/talk_project/talk/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import resolve 3 | 4 | 5 | class HomeViewTest(TestCase): 6 | 7 | def test_index(self): 8 | """Ensure main url is connected to the talk.views.home view""" 9 | resolver = resolve('/') 10 | self.assertEqual(resolver.view_name, 'talk.views.home') 11 | -------------------------------------------------------------------------------- /part4/talk_project/talk/urls.py: -------------------------------------------------------------------------------- 1 | # Talk urls 2 | from django.conf.urls import patterns, url 3 | from talk import views 4 | 5 | 6 | urlpatterns = patterns( 7 | 'talk.views', 8 | url(r'^$', 'home'), 9 | 10 | # api 11 | url(r'^api/v1/posts/$', views.PostCollection.as_view()), 12 | url(r'^api/v1/posts/(?P[0-9]+)/$', views.PostMember.as_view()) 13 | ) 14 | -------------------------------------------------------------------------------- /part4/talk_project/talk/views.py: -------------------------------------------------------------------------------- 1 | from talk.models import Post 2 | from talk.forms import PostForm 3 | from talk.serializers import PostSerializer 4 | from rest_framework import generics 5 | from django.shortcuts import render 6 | 7 | 8 | def home(request): 9 | tmpl_vars = {'form': PostForm()} 10 | return render(request, 'talk/index.html', tmpl_vars) 11 | 12 | 13 | ######################### 14 | ### class based views ### 15 | ######################### 16 | 17 | class PostCollection(generics.ListCreateAPIView): 18 | queryset = Post.objects.all() 19 | serializer_class = PostSerializer 20 | 21 | 22 | class PostMember(generics.RetrieveDestroyAPIView): 23 | queryset = Post.objects.all() 24 | serializer_class = PostSerializer 25 | 26 | # class PostCollection(mixins.ListModelMixin, 27 | # mixins.CreateModelMixin, 28 | # generics.ListAPIView): 29 | 30 | # queryset = Post.objects.all() 31 | # serializer_class = PostSerializer 32 | 33 | # def get(self, request, *args, **kwargs): 34 | # return self.list(request, *args, **kwargs) 35 | 36 | # def post(self, request, *args, **kwargs): 37 | # return self.create(request, *args, **kwargs) 38 | 39 | 40 | # class PostMember(mixins.RetrieveModelMixin, 41 | # mixins.DestroyModelMixin, 42 | # generics.GenericAPIView): 43 | 44 | # queryset = Post.objects.all() 45 | # serializer_class = PostSerializer 46 | 47 | # def get(self, request, *args, **kwargs): 48 | # return self.retrieve(request, *args, **kwargs) 49 | 50 | # def delete(self, request, *args, **kwargs): 51 | # return self.destroy(request, *args, **kwargs) 52 | 53 | ############################ 54 | ### function based views ### 55 | ############################ 56 | 57 | # from django.shortcuts import get_object_or_404 58 | # from rest_framework.decorators import api_view 59 | # from rest_framework.response import Response 60 | # from rest_framework import status 61 | # from talk.models import Post 62 | # from talk.serializers import PostSerializer 63 | 64 | # @api_view(['GET', 'POST']) 65 | # def post_collection(request): 66 | # if request.method == 'GET': 67 | # posts = Post.objects.all() 68 | # serializer = PostSerializer(posts, many=True) 69 | # return Response(serializer.data) 70 | # elif request.method == 'POST': 71 | # data = {'text': request.DATA.get('the_post'), 'author': request.user} 72 | # serializer = PostSerializer(data=data) 73 | # if serializer.is_valid(): 74 | # serializer.save() 75 | # return Response(serializer.data, status=status.HTTP_201_CREATED) 76 | # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 77 | 78 | 79 | # @api_view(['GET', 'DELETE']) 80 | # def post_element(request, pk): 81 | 82 | # post = get_object_or_404(Post, id=pk) 83 | 84 | # # try: 85 | # # post = Post.objects.get(pk=pk) 86 | # # except Post.DoesNotExist: 87 | # # return HttpResponse(status=404) 88 | 89 | # if request.method == 'GET': 90 | # serializer = PostSerializer(post) 91 | # return Response(serializer.data) 92 | 93 | # elif request.method == 'DELETE': 94 | # post.delete() 95 | # return Response(status=status.HTTP_204_NO_CONTENT) 96 | -------------------------------------------------------------------------------- /part4/talk_project/talk_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realpython/django-form-fun/3a8e98e6c9060effba8df9222d22a337f95b48eb/part4/talk_project/talk_project/__init__.py -------------------------------------------------------------------------------- /part4/talk_project/talk_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for talk_project project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | 14 | SETTINGS_DIR = os.path.dirname(__file__) 15 | PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir) 16 | PROJECT_ROOT = os.path.abspath(PROJECT_PATH) 17 | 18 | TEMPLATE_DIRS = ( 19 | os.path.join(PROJECT_ROOT, 'templates'), 20 | ) 21 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 22 | 23 | LOGIN_URL = '/login/' 24 | 25 | LOGOUT_URL = '/logout/' 26 | 27 | # LOGIN_REDIRECT_URL = '/accounts/profile/' 28 | 29 | # Quick-start development settings - unsuitable for production 30 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 31 | 32 | # SECURITY WARNING: keep the secret key used in production secret! 33 | SECRET_KEY = 'u)vhj6nj*)(i(8zg2f0!j=xwg+309om2v@o$-sn0l9a5u0=%+7' 34 | 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | DEBUG = True 37 | 38 | TEMPLATE_DEBUG = True 39 | 40 | ALLOWED_HOSTS = [] 41 | 42 | 43 | # Application definition 44 | 45 | INSTALLED_APPS = ( 46 | 'django.contrib.admin', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.sessions', 50 | 'django.contrib.messages', 51 | 'django.contrib.staticfiles', 52 | 'talk', 53 | 'rest_framework' 54 | ) 55 | 56 | MIDDLEWARE_CLASSES = ( 57 | 'django.contrib.sessions.middleware.SessionMiddleware', 58 | 'django.middleware.common.CommonMiddleware', 59 | 'django.middleware.csrf.CsrfViewMiddleware', 60 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 61 | 'django.contrib.messages.middleware.MessageMiddleware', 62 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 63 | 'talk.middleware.RequireLoginMiddleware', 64 | ) 65 | 66 | LOGIN_REQUIRED_URLS = ( 67 | r'/(.*)$', # TODO interpret this regex. 68 | ) 69 | LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 70 | r'/login(.*)$', 71 | r'/logout(.*)$', 72 | r'/staff(.*)$', 73 | ) 74 | 75 | TEMPLATE_CONTEXT_PROCESSORS = ( 76 | 'django.contrib.auth.context_processors.auth', 77 | 'django.core.context_processors.request', 78 | ) 79 | 80 | ROOT_URLCONF = 'talk_project.urls' 81 | 82 | WSGI_APPLICATION = 'talk_project.wsgi.application' 83 | 84 | 85 | # Database 86 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 87 | 88 | DATABASES = { 89 | 'default': { 90 | 'ENGINE': 'django.db.backends.sqlite3', 91 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 92 | } 93 | } 94 | 95 | # Internationalization 96 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 97 | 98 | LANGUAGE_CODE = 'en-us' 99 | 100 | TIME_ZONE = 'UTC' 101 | 102 | USE_I18N = True 103 | 104 | USE_L10N = True 105 | 106 | USE_TZ = True 107 | 108 | 109 | # Static files (CSS, JavaScript, Images) 110 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 111 | 112 | STATIC_URL = '/static/' 113 | 114 | STATICFILES_DIRS = ( 115 | os.path.join(BASE_DIR, 'static'), 116 | ) 117 | -------------------------------------------------------------------------------- /part4/talk_project/talk_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from talk_project.views import logout_page 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | 8 | urlpatterns = patterns( 9 | '', 10 | url(r'^admin/', include(admin.site.urls)), 11 | url(r'^login/', 'django.contrib.auth.views.login'), 12 | (r'^logout/$', logout_page), 13 | url(r'^', include('talk.urls')), 14 | ) 15 | -------------------------------------------------------------------------------- /part4/talk_project/talk_project/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | def logout_page(request): 6 | """ 7 | Log users out and re-direct them to the main page. 8 | """ 9 | logout(request) 10 | return HttpResponseRedirect('/') 11 | -------------------------------------------------------------------------------- /part4/talk_project/talk_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for talk_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk_project.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /part4/talk_project/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %} 9 | 10 | {% endblock %} 11 | 12 | 13 | 14 | 15 | 16 |
    17 | {% block content %} 18 | {% endblock %} 19 |
    20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /part4/talk_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | Login 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
    10 |

    11 |
    12 | 13 |
    14 | {% if form.errors %} 15 |
    Oh fiddlesticks! Please try again.
    16 | {% endif %} 17 | 18 |
    19 |
    20 | {% csrf_token %} 21 |
    22 | Login 23 |

    24 | 25 | {{ form.username }} 26 |

    27 |

    28 | 29 | {{ form.password }} 30 |

    31 | {% if next %} 32 | 33 | {% else %} 34 | 35 | {% endif %} 36 | 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /part4/talk_project/templates/talk/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | talk to me 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 51 | 52 | {% block head %} 53 | {% endblock %} 54 | 55 | 56 | 57 | 58 | {% block content %} 59 | {% endblock %} 60 | 61 | -------------------------------------------------------------------------------- /part4/talk_project/templates/talk/index.html: -------------------------------------------------------------------------------- 1 | {% extends "talk/base.html" %} 2 | {% block content %} 3 |

    4 |
    5 |
    6 | Logout 7 |

    Hi, {{request.user.username}}

    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | {% csrf_token %} 15 |
    16 | {{ form.text }} 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 | 24 | 25 |
    26 |
    27 |
      28 | {% for post in all_posts %} 29 |
    • 30 | {{ post.text }} - 31 | {{ post.author }} - 32 | {{ post.created }} - 33 | delete me 34 |
    • 35 | {% endfor %} 36 |
    37 | 38 |
    39 |
    40 | 41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Django and AJAX Form Submissions - say 'goodbye' to the page refresh 2 | 3 | - check out the blog post: [https://realpython.com/blog/python/django-and-ajax-form-submissions/](https://realpython.com/blog/python/django-and-ajax-form-submissions/) --------------------------------------------------------------------------------