├── mysite ├── mysite │ ├── __init__.py │ ├── urls.py │ ├── wsgi.py │ └── settings.py ├── polls │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── static │ │ └── polls │ │ │ ├── images │ │ │ └── background.jpg │ │ │ └── style.css │ ├── templates │ │ └── polls │ │ │ ├── results.html │ │ │ ├── index.html │ │ │ └── detail.html │ ├── urls.py │ ├── admin.py │ ├── models.py │ ├── views.py │ └── tests.py ├── manage.py └── templates │ └── admin │ └── base_site.html ├── rebuild_docker.sh ├── static └── admin │ ├── img │ ├── icon-no.gif │ ├── nav-bg.gif │ ├── default-bg.gif │ ├── icon-yes.gif │ ├── icon_alert.gif │ ├── icon_clock.gif │ ├── icon_error.gif │ ├── icon-unknown.gif │ ├── icon_addlink.gif │ ├── icon_success.gif │ ├── tooltag-add.png │ ├── changelist-bg.gif │ ├── deleted-overlay.gif │ ├── icon_calendar.gif │ ├── icon_changelink.gif │ ├── icon_deletelink.gif │ ├── icon_searchbox.png │ ├── inline-delete.png │ ├── inline-restore.png │ ├── nav-bg-grabber.gif │ ├── nav-bg-reverse.gif │ ├── nav-bg-selected.gif │ ├── selector-icons.gif │ ├── selector-search.gif │ ├── sorting-icons.gif │ ├── changelist-bg_rtl.gif │ ├── default-bg-reverse.gif │ ├── gis │ │ ├── move_vertex_off.png │ │ └── move_vertex_on.png │ ├── inline-delete-8bit.png │ ├── inline-restore-8bit.png │ ├── inline-splitter-bg.gif │ └── tooltag-arrowright.png │ ├── js │ ├── jquery.init.js │ ├── prepopulate.min.js │ ├── collapse.min.js │ ├── related-widget-wrapper.js │ ├── collapse.js │ ├── LICENSE-JQUERY.txt │ ├── prepopulate.js │ ├── timeparse.js │ ├── actions.min.js │ ├── inlines.min.js │ ├── SelectBox.js │ ├── admin │ │ ├── RelatedObjectLookups.js │ │ └── DateTimeShortcuts.js │ ├── actions.js │ ├── urlify.js │ ├── calendar.js │ ├── core.js │ ├── SelectFilter2.js │ └── inlines.js │ └── css │ ├── dashboard.css │ ├── login.css │ ├── ie.css │ ├── rtl.css │ ├── changelists.css │ ├── forms.css │ ├── widgets.css │ └── base.css ├── gunicorn_conf.py ├── nginx ├── Dockerfile └── nginx.conf ├── Dockerfile ├── LICENSE ├── docker-compose.yml ├── .gitignore ├── requirements.txt └── README.md /mysite/mysite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mysite/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mysite/polls/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rebuild_docker.sh: -------------------------------------------------------------------------------- 1 | docker-compose build 2 | docker-compose up -d 3 | docker-compose ps 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon-no.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /static/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/default-bg.gif -------------------------------------------------------------------------------- /static/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /static/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /static/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /static/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_error.gif -------------------------------------------------------------------------------- /gunicorn_conf.py: -------------------------------------------------------------------------------- 1 | # Gunicorn configuration file 2 | 3 | bind = '0.0.0.0:8000' 4 | 5 | loglevel = 'info' 6 | errorlog = '-' 7 | accesslog = '-' 8 | -------------------------------------------------------------------------------- /static/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /static/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_success.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/tooltag-add.png -------------------------------------------------------------------------------- /static/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /static/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /static/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /static/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /static/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/inline-delete.png -------------------------------------------------------------------------------- /static/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/inline-restore.png -------------------------------------------------------------------------------- /static/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /static/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /static/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/selector-search.gif -------------------------------------------------------------------------------- /static/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /static/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /static/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /static/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /static/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /static/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-arrowright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/static/admin/img/tooltag-arrowright.png -------------------------------------------------------------------------------- /mysite/polls/static/polls/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrecp/django-tutorial-docker-nginx-postgres/HEAD/mysite/polls/static/polls/images/background.jpg -------------------------------------------------------------------------------- /mysite/polls/static/polls/style.css: -------------------------------------------------------------------------------- 1 | li a { 2 | color: green; 3 | } 4 | 5 | body { 6 | background: white url("images/background.jpg") no-repeat right bottom; 7 | } 8 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Set nginx base image 2 | FROM nginx 3 | 4 | # File Author / Maintainer 5 | MAINTAINER Andre Prado 6 | 7 | # Copy custom configuration file from the current directory 8 | COPY nginx.conf /etc/nginx/nginx.conf 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for sample Django application 2 | 3 | FROM python:2.7-onbuild 4 | 5 | ENV DJANGO_CONFIGURATION Docker 6 | 7 | CMD ["gunicorn", "-c", "gunicorn_conf.py", "--chdir", "mysite", "mysite.wsgi:application", "--reload"] 8 | -------------------------------------------------------------------------------- /mysite/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", "mysite.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /mysite/polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 |
No polls are available.
13 | {% endif %} 14 | -------------------------------------------------------------------------------- /mysite/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite 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.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Andre 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /mysite/polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |{{ error_message }}
{% endif %} 4 | 5 | 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker-compose.yml file for atbaker/docker-django project 2 | 3 | # Nginx 4 | nginx: 5 | build: ./nginx 6 | volumes: 7 | - .:/usr/src/app 8 | links: 9 | - django 10 | ports: 11 | - "80:80" 12 | 13 | # This defines a service for the Django app 14 | django: 15 | build: . 16 | volumes: 17 | - .:/usr/src/app 18 | expose: 19 | - "8000" 20 | links: 21 | - postgres 22 | 23 | 24 | # This defines a service for the Postgres database 25 | postgres: 26 | image: postgres:9.3 27 | 28 | 29 | -------------------------------------------------------------------------------- /static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } 31 | -------------------------------------------------------------------------------- /static/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return false})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /mysite/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Choice, Question 5 | 6 | 7 | class ChoiceInline(admin.TabularInline): 8 | model = Choice 9 | extra = 3 10 | 11 | 12 | class QuestionAdmin(admin.ModelAdmin): 13 | fieldsets = [ 14 | (None, {'fields': ['question_text']}), 15 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 16 | ] 17 | inlines = [ChoiceInline] 18 | list_display = ('question_text', 'pub_date', 'was_published_recently') 19 | list_filter = ['pub_date'] 20 | search_fields = ['question_text'] 21 | 22 | admin.site.register(Question, QuestionAdmin) 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | -------------------------------------------------------------------------------- /mysite/polls/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | import datetime 4 | 5 | 6 | class Question(models.Model): 7 | question_text = models.CharField(max_length=200) 8 | pub_date = models.DateTimeField('date published') 9 | 10 | def __str__(self): 11 | return self.question_text 12 | 13 | def was_published_recently(self): 14 | now = timezone.now() 15 | return now - datetime.timedelta(days=1) <= self.pub_date <= now 16 | 17 | # Admin view attributes. 18 | was_published_recently.admin_order_field = 'pub_date' 19 | was_published_recently.boolean = True 20 | was_published_recently.short_description = 'Published recently?' 21 | 22 | 23 | 24 | class Choice(models.Model): 25 | question = models.ForeignKey(Question) 26 | choice_text = models.CharField(max_length=200) 27 | votes = models.IntegerField(default=0) 28 | 29 | def __str__(self): 30 | return self.choice_text 31 | -------------------------------------------------------------------------------- /static/admin/js/related-widget-wrapper.js: -------------------------------------------------------------------------------- 1 | django.jQuery(function($){ 2 | function updateLinks() { 3 | var $this = $(this); 4 | var siblings = $this.nextAll('.change-related, .delete-related'); 5 | if (!siblings.length) return; 6 | var value = $this.val(); 7 | if (value) { 8 | siblings.each(function(){ 9 | var elm = $(this); 10 | elm.attr('href', elm.attr('data-href-template').replace('__fk__', value)); 11 | }); 12 | } else siblings.removeAttr('href'); 13 | } 14 | var container = $(document); 15 | container.on('change', '.related-widget-wrapper select', updateLinks); 16 | container.find('.related-widget-wrapper select').each(updateLinks); 17 | container.on('click', '.related-widget-wrapper-link', function(event){ 18 | if (this.href) { 19 | showRelatedObjectPopup(this); 20 | } 21 | event.preventDefault(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 14 | if ($(this).closest("fieldset").hasClass("collapsed")) { 15 | // Show 16 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 17 | } else { 18 | // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | } 21 | return false; 22 | }); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | 5 | worker_connections 1024; 6 | 7 | } 8 | 9 | http { 10 | 11 | server { 12 | listen 80; 13 | server_name example.org; 14 | 15 | access_log /dev/stdout; 16 | error_log /dev/stdout info; 17 | 18 | location /static { 19 | alias /usr/src/app/static; 20 | } 21 | 22 | location /static/polls { 23 | alias /usr/src/app/mysite/polls/static/polls; 24 | } 25 | 26 | location /static/admin/js/ { 27 | default_type text/javascript; 28 | alias /usr/src/app/static/admin/js/; 29 | } 30 | 31 | location /static/admin/css/ { 32 | default_type text/css; 33 | alias /usr/src/app/static/admin/css/; 34 | } 35 | 36 | location / { 37 | proxy_pass http://django:8000; 38 | proxy_set_header Host $host; 39 | proxy_set_header X-Real-IP $remote_addr; 40 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 41 | proxy_set_header X-Forwarded-Host $server_name; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /static/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is here because many Platforms as a Service look for 2 | # requirements.txt in the root directory of a project. 3 | # pylibmc==1.2.3 4 | # django-heroku-memcacheify==0.5 5 | # -r requirements/production.txt 6 | 7 | # Bleeding edge Django 8 | django==1.8 9 | 10 | # Configuration 11 | django-configurations==0.8 12 | django-secure==1.0 13 | django-cache-url==0.8.0 14 | dj-database-url==0.3.0 15 | 16 | # Forms 17 | django-braces==1.4.0 18 | django-crispy-forms==1.4.0 19 | django-floppyforms==1.2.0 20 | 21 | # Models 22 | django-model-utils==2.2 23 | 24 | # images 25 | Pillow==2.5.3 26 | 27 | # For user registration, either via email or social 28 | # Well-built with regular release cycles! 29 | django-allauth==0.18.0 30 | 31 | # For the persistance stores 32 | psycopg2==2.5.4 33 | 34 | # Unicode slugification 35 | unicode-slugify==0.1.1 36 | django-autoslug==1.7.2 37 | 38 | # Useful things 39 | django-avatar==2.0 40 | 41 | coverage==3.7.1 42 | Sphinx 43 | 44 | # django-debug-toolbar that works with Django 1.5+ 45 | django-debug-toolbar==1.2.1 46 | 47 | python-memcached==1.53 48 | 49 | gunicorn==19.1.1 50 | 51 | 52 | # Your custom requirements go here 53 | -------------------------------------------------------------------------------- /static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | padding-right: 0.5em; 33 | line-height: 2em; 34 | font-size: 1em; 35 | clear: both; 36 | color: #333; 37 | } 38 | 39 | .login .form-row #id_username, .login .form-row #id_password { 40 | clear: both; 41 | padding: 6px; 42 | width: 100%; 43 | -webkit-box-sizing: border-box; 44 | -moz-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | 48 | .login span.help { 49 | font-size: 10px; 50 | display: block; 51 | } 52 | 53 | .login .submit-row { 54 | clear: both; 55 | padding: 1em 0 0 9.4em; 56 | } 57 | 58 | .login .password-reset-link { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /mysite/polls/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Choice', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('choice_text', models.CharField(max_length=200)), 18 | ('votes', models.IntegerField(default=0)), 19 | ], 20 | ), 21 | migrations.CreateModel( 22 | name='Question', 23 | fields=[ 24 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 25 | ('question_text', models.CharField(max_length=200)), 26 | ('pub_date', models.DateTimeField(verbose_name=b'date published')), 27 | ], 28 | ), 29 | migrations.AddField( 30 | model_name='choice', 31 | name='question', 32 | field=models.ForeignKey(to='polls.Question'), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /static/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } 64 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.prepopulate = function(dependencies, maxLength) { 3 | /* 4 | Depends on urlify.js 5 | Populates a selected field with the values of the dependent fields, 6 | URLifies and shortens the string. 7 | dependencies - array of dependent fields ids 8 | maxLength - maximum length of the URLify'd string 9 | */ 10 | return this.each(function() { 11 | var prepopulatedField = $(this); 12 | 13 | var populate = function () { 14 | // Bail if the field's value has been changed by the user 15 | if (prepopulatedField.data('_changed')) { 16 | return; 17 | } 18 | 19 | var values = []; 20 | $.each(dependencies, function(i, field) { 21 | field = $(field); 22 | if (field.val().length > 0) { 23 | values.push(field.val()); 24 | } 25 | }); 26 | prepopulatedField.val(URLify(values.join(' '), maxLength)); 27 | }; 28 | 29 | prepopulatedField.data('_changed', false); 30 | prepopulatedField.change(function() { 31 | prepopulatedField.data('_changed', true); 32 | }); 33 | 34 | if (!prepopulatedField.val()) { 35 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 36 | } 37 | }); 38 | }; 39 | })(django.jQuery); 40 | -------------------------------------------------------------------------------- /mysite/polls/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404, render 2 | from django.http import HttpResponseRedirect 3 | from django.core.urlresolvers import reverse 4 | from django.views import generic 5 | from django.utils import timezone 6 | 7 | from .models import Choice, Question 8 | 9 | 10 | class IndexView(generic.ListView): 11 | template_name = 'polls/index.html' 12 | context_object_name = 'latest_question_list' 13 | 14 | def get_queryset(self): 15 | """ 16 | Return the last five published questions (not including those set to be 17 | published in the future). 18 | """ 19 | return Question.objects.filter( 20 | pub_date__lte=timezone.now() 21 | ).order_by('-pub_date')[:5] 22 | 23 | 24 | class DetailView(generic.DetailView): 25 | model = Question 26 | template_name = 'polls/detail.html' 27 | 28 | def get_queryset(self): 29 | """ 30 | Return the last five published questions (not including those set to be 31 | published in the future). 32 | """ 33 | return Question.objects.filter( 34 | pub_date__lte=timezone.now() 35 | ) 36 | 37 | 38 | class ResultsView(generic.DetailView): 39 | model = Question 40 | template_name = 'polls/results.html' 41 | 42 | 43 | def vote(request, question_id): 44 | p = get_object_or_404(Question, pk=question_id) 45 | try: 46 | selected_choice = p.choice_set.get(pk=request.POST['choice']) 47 | except (KeyError, Choice.DoesNotExist): 48 | # Redisplay the question voting form. 49 | return render(request, 'polls/detail.html', { 50 | 'question': p, 51 | 'error_message': "You didn't select a choice.", 52 | }) 53 | else: 54 | selected_choice.votes += 1 55 | selected_choice.save() 56 | # Always return an HttpResponseRedirect after successfully dealing 57 | # with POST data. This prevents data from being posted twice if a 58 | # user hits the Back button. 59 | return HttpResponseRedirect(reverse('polls:results', args=(p.id,))) 60 | -------------------------------------------------------------------------------- /static/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | var timeParsePatterns = [ 2 | // 9 3 | { re: /^\d{1,2}$/i, 4 | handler: function(bits) { 5 | if (bits[0].length == 1) { 6 | return '0' + bits[0] + ':00'; 7 | } else { 8 | return bits[0] + ':00'; 9 | } 10 | } 11 | }, 12 | // 13:00 13 | { re: /^\d{2}[:.]\d{2}$/i, 14 | handler: function(bits) { 15 | return bits[0].replace('.', ':'); 16 | } 17 | }, 18 | // 9:00 19 | { re: /^\d[:.]\d{2}$/i, 20 | handler: function(bits) { 21 | return '0' + bits[0].replace('.', ':'); 22 | } 23 | }, 24 | // 3 am / 3 a.m. / 3am 25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 26 | handler: function(bits) { 27 | var hour = parseInt(bits[1]); 28 | if (hour == 12) { 29 | hour = 0; 30 | } 31 | if (bits[2].toLowerCase() == 'p') { 32 | if (hour == 12) { 33 | hour = 0; 34 | } 35 | return (hour + 12) + ':00'; 36 | } else { 37 | if (hour < 10) { 38 | return '0' + hour + ':00'; 39 | } else { 40 | return hour + ':00'; 41 | } 42 | } 43 | } 44 | }, 45 | // 3.30 am / 3:15 a.m. / 3.00am 46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 47 | handler: function(bits) { 48 | var hour = parseInt(bits[1]); 49 | var mins = parseInt(bits[2]); 50 | if (mins < 10) { 51 | mins = '0' + mins; 52 | } 53 | if (hour == 12) { 54 | hour = 0; 55 | } 56 | if (bits[3].toLowerCase() == 'p') { 57 | if (hour == 12) { 58 | hour = 0; 59 | } 60 | return (hour + 12) + ':' + mins; 61 | } else { 62 | if (hour < 10) { 63 | return '0' + hour + ':' + mins; 64 | } else { 65 | return hour + ':' + mins; 66 | } 67 | } 68 | } 69 | }, 70 | // noon 71 | { re: /^no/i, 72 | handler: function(bits) { 73 | return '12:00'; 74 | } 75 | }, 76 | // midnight 77 | { re: /^mid/i, 78 | handler: function(bits) { 79 | return '00:00'; 80 | } 81 | } 82 | ]; 83 | 84 | function parseTimeString(s) { 85 | for (var i = 0; i < timeParsePatterns.length; i++) { 86 | var re = timeParsePatterns[i].re; 87 | var handler = timeParsePatterns[i].handler; 88 | var bits = re.exec(s); 89 | if (bits) { 90 | return handler(bits); 91 | } 92 | } 93 | return s; 94 | } 95 | -------------------------------------------------------------------------------- /static/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var f;a.fn.actions=function(q){var b=a.extend({},a.fn.actions.defaults,q),g=a(this),e=!1,m=function(c){c?k():l();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).prop("checked",function(){var a;c==g.length?(a=!0,k()):(a=!1,n());return a})},k=function(){a(b.acrossClears).hide(); 2 | a(b.acrossQuestions).show();a(b.allContainer).hide()},p=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},n=function(){l();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass); 3 | h();1==a(b.acrossInput).val()&&p()});a(b.allToggle).show().click(function(){m(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);p()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);n();m(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&a.data(f)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass, 4 | d.checked);a(g).each(function(){if(a.data(this)==a.data(f)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(a){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); 5 | a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})}; 6 | a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 7 | -------------------------------------------------------------------------------- /mysite/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '07s#p@ol!&7$et@@yh=q^r42qr74pjes!sosahjszt#g1yaqyz' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'polls', 41 | ) 42 | 43 | MIDDLEWARE_CLASSES = ( 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | 53 | ) 54 | 55 | ROOT_URLCONF = 'mysite.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'mysite.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 82 | 'NAME': 'postgres', 83 | 'USER': 'postgres', 84 | 'HOST': os.environ['POSTGRES_PORT_5432_TCP_ADDR'], 85 | 'PASSWORD': '', 86 | 'PORT': '' 87 | } 88 | } 89 | 90 | 91 | # Internationalization 92 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 93 | 94 | LANGUAGE_CODE = 'en-us' 95 | 96 | TIME_ZONE = 'Europe/London' 97 | 98 | USE_I18N = True 99 | 100 | USE_L10N = True 101 | 102 | USE_TZ = True 103 | 104 | 105 | # Static files (CSS, JavaScript, Images) 106 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 107 | 108 | STATIC_URL = '/static/' 109 | 110 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Django tutorial using Docker, Nginx, Gunicorn and PostgreSQL. 2 | 3 | This is Django tutorial in steroids, I wanted to go through the tutorial using a configuration that is more close to production. 4 | 5 | I am running the django app with gunicorn, using postgres as the database, the django app is behind nginx and everything is running out of Docker containers orchastrated with docker-compose. 6 | 7 | #### Commits by tutorial parts 8 | 9 | I've just finished going throught Django tutorial so the adventure on this repository is over, however have fun picking a start point: 10 | 11 | [Commit at beggining of Part 2](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/commit/24def5c2e962e74fd41132fb8caef5ef5d9a92f5) 12 | 13 | [Commit at beggining of Part 3](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/commit/3a9bb28648f7fe84675fba6e50c36968d5e22cf5) 14 | 15 | [Commit at beggining of Part 4](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/commit/fda3c34191574867cd07b8c5006f7d1ff6188f52) 16 | 17 | [Commit at beggining of Part 5](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/commit/186309a739908cd2b498aad540c95acf41230f93) 18 | 19 | [Commit at beggining of Part 6](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/commit/d8888f803a36ff862fc84faae87b34b726610f06) 20 | 21 | [Branch with reusuable-apps advanced tutorial](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/tree/reusable-apps) 22 | 23 | #### Setup 24 | I am running this on a Macbook air running Yosemite with boot2docker. 25 | 26 | #### Running 27 | 1. git clone this repo 28 | 2. boot2docker up 29 | 3. sh rebuild_docker.sh 30 | 4. run migrations in your Django instance: ```docker-compose run django /bin/sh -c 'cd mysite;python manage.py migrate'``` 31 | 5. access it in your browser! http://192.168.59.103/ for me, run boot2docker ip to know where it is running. 32 | 33 | #### [docker-compose.yml](https://github.com/andrecp/django-tutorial-docker-nginx-postgres/blob/master/docker-compose.yml) 34 | Compose is a tool for defining and running multi-container applications with Docker. With Compose, you define a multi-container application in a single file, then spin your application up in a single command which does everything that needs to be done to get it running. 35 | 36 | https://docs.docker.com/compose/ 37 | 38 | We have three containers: Nginx, Postgres and a Django App. 39 | 40 | * django 41 | 42 | - Built from ./Dockerfile, running gunicorn to serve our django app. 43 | 44 | * postgres 45 | - Just the basic image from DockerRegistry. 46 | 47 | * nginx 48 | - We are building it from nginx/Dockerfile. In our nginx/nginx.conf we are basically proxying django to gunicorn and serving static files which gunicorn doesn't do for us. 49 | 50 | #### rebuild_docker.sh 51 | Created to make life easier: 52 | 53 | 1. Build your docker-compose file. 54 | - docker-compose build 55 | 56 | 2. Runs your containers on detached mode. 57 | - docker-compose up -d 58 | 59 | 3. Check if they are all running. 60 | - docker-compose ps 61 | 62 | #### Running commands inside containers 63 | use ```docker-compose run CONTAINER_NAME command``` for example: 64 | 65 | * Testing polls app 66 | - docker-compose run django /bin/sh -c 'cd mysite;python manage.py test polls' 67 | 68 | * Running ls 69 | - docker-compose run django /bin/sh -c 'ls' 70 | 71 | * Running Python directly 72 | - docker-compose run django python 73 | 74 | #### Checking logs 75 | use ```docker-compose logs CONTAINER_NAME``` 76 | 77 | #### Important 78 | This project first started as me following the awesome introduction from Andrew T. Baker at the PyCon US 2015 [link](http://docker.atbaker.me/) 79 | 80 | 81 | -------------------------------------------------------------------------------- /static/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.formset=function(g){var b=a.extend({},a.fn.formset.defaults,g),i=a(this);g=i.parent();var m=function(e,k,h){var j=RegExp("("+k+"-(\\d+|__prefix__))");k=k+"-"+h;a(e).prop("for")&&a(e).prop("for",a(e).prop("for").replace(j,k));if(e.id)e.id=e.id.replace(j,k);if(e.name)e.name=e.name.replace(j,k)},l=a("#id_"+b.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),d=parseInt(l.val(),10),c=a("#id_"+b.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off");l=c.val()===""||c.val()-l.val()>0;i.each(function(){a(this).not("."+ 2 | b.emptyCssClass).addClass(b.formCssClass)});if(i.length&&l){var f;if(i.prop("tagName")=="TR"){i=this.eq(-1).children().length;g.append('