├── .dockerignore
├── .gitattributes
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── deploy
├── nginx
│ └── nginx.json-logging.conf
└── uwsgi
│ └── uwsgi.ini
├── docker-compose.yml
├── project_name
├── __init__.py
├── accounts
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── management
│ │ ├── __init__.py
│ │ └── commands
│ │ │ ├── __init__.py
│ │ │ └── createsuperuser.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── templatetags
│ │ ├── __init__.py
│ │ └── accounts.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── factories.py
│ │ ├── models_tests.py
│ │ ├── utils_tests.py
│ │ └── views_tests.py
│ ├── urls.py
│ ├── utils.py
│ └── views.py
├── api
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── mixins.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── core
│ ├── __init__.py
│ ├── models.py
│ └── tests
│ │ └── __init__.py
├── lib
│ ├── __init__.py
│ ├── audit
│ │ ├── LICENSE.txt
│ │ ├── README.txt
│ │ ├── __init__.py
│ │ ├── middleware.py
│ │ └── models.py
│ ├── cache
│ │ ├── __init__.py
│ │ └── decorators.py
│ ├── common
│ │ ├── __init__.py
│ │ ├── templatetags
│ │ │ ├── __init__.py
│ │ │ ├── dateutils.py
│ │ │ ├── dicthelpers.py
│ │ │ ├── formhelpers.py
│ │ │ ├── list_helpers.py
│ │ │ ├── numberhelpers.py
│ │ │ ├── sitemaphelpers.py
│ │ │ ├── statichelpers.py
│ │ │ ├── stringhelpers.py
│ │ │ ├── templatehelpers.py
│ │ │ └── urlhelpers.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── datetime
│ │ ├── __init__.py
│ │ └── utils.py
│ ├── forms
│ │ ├── __init__.py
│ │ └── generic.py
│ ├── html
│ │ ├── __init__.py
│ │ ├── markdown_extensions.py
│ │ └── utils.py
│ ├── storage.py
│ └── viewslibrary
│ │ ├── __init__.py
│ │ ├── tests.py
│ │ └── views.py
├── manage.py
├── project_name
│ ├── __init__.py
│ ├── celery.py
│ ├── databases.py
│ ├── development
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── general
│ │ ├── __init__.py
│ │ ├── redis_rabbitmq.py
│ │ └── settings.py
│ ├── settings.py
│ ├── staging
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── testing
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── third_parties
│ │ ├── __init__.py
│ │ └── blacklist_domains.py
│ ├── urls.py
│ └── wsgi.py
├── static
│ ├── css
│ │ ├── foundation
│ │ │ ├── foundation.css
│ │ │ └── foundation.min.css
│ │ └── project_name.css
│ └── js
│ │ └── vendor
│ │ ├── foundation
│ │ ├── foundation.js
│ │ └── foundation.min.js
│ │ ├── jquery
│ │ ├── jquery.js
│ │ └── jquery.min.js
│ │ └── jqueryui
│ │ ├── jquery-ui-1.7.2.min.js
│ │ └── jquery-ui.min.js
└── templates
│ └── project_name
│ ├── application
│ ├── core.html
│ └── homepage
│ │ └── homepage.html
│ ├── auth
│ ├── core.html
│ └── login.html
│ └── shared
│ ├── forms
│ ├── foundation_field.html
│ └── foundation_field_basic.html
│ └── partials
│ └── messages.html
├── requirements
├── common.txt
├── deployment.txt
├── development.txt
├── live.txt
└── windows
│ ├── common.txt
│ ├── deployment.txt
│ └── development.txt
└── roles
└── tasks.py
/.dockerignore:
--------------------------------------------------------------------------------
1 | *.pgdump
2 | .git
3 | *.swp
4 | *.swo
5 | *.pyc
6 | config.ini
7 | supervisord.log
8 | supervisord.pid
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.rb linguist-language=Python
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | LINUX-ENV
2 | ENV
3 | DEV
4 | EVENT
5 | popup
6 | syntax: glob
7 | *.pyc
8 | ._*
9 | __pycache__/
10 | .#*
11 | #*#
12 | .DS_Store
13 | media
14 | local_settings.py
15 | *.mo
16 | *.codekit
17 | *.map
18 | .sass-cache/
19 | .coverage
20 | wag-labels
21 | useful
22 | renders
23 | *~
24 | .*.sw*
25 | .Python
26 | .vagrant/
27 | renders
28 | .pip-download-cache
29 | *-auto-bak-*.pgdump
30 | .installed
31 | # built CSS is not kept in source control
32 | config.codekit
33 | *.map.css
34 | *.tar
35 | *.Rhistory
36 | *.tsv
37 | keys
38 | *.sql
39 | dump.rdb
40 | build
41 |
42 | # Eclipse metadata
43 | .project
44 | .pydevproject
45 | .settings
46 | dbbaks
47 |
48 | selenium-server-standalone*jar
49 | ingest
50 |
51 | # PyCharm metadata
52 | .idea
53 |
54 | # VS Code metadata
55 | .vscode
56 | .noseids
57 |
58 | celerybeat-schedule
59 | *.pgdump
60 | db.sqlite3
61 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ############################################################
2 | # Dockerfile to run a Django-based web application
3 | # Based on an Ubuntu Image
4 | ############################################################
5 | FROM ubuntu:18.04
6 | MAINTAINER tiago.arasilva@outlook.com
7 |
8 | ENV DEBIAN_FRONTEND noninteractive
9 | ENV LANG en_GB.UTF-8
10 | ENV LC_ALL en_GB.UTF-8
11 |
12 | #
13 | # Install Apt Packages
14 | #
15 | RUN apt-get update && \
16 | apt-get upgrade -y && \
17 | apt-get install curl -y && \
18 | apt-get clean && apt-get autoclean && \
19 | find /var/lib/apt/lists/ -type f -delete && \
20 | echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
21 | curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
22 |
23 | RUN apt-get update && \
24 | apt-get install -y bind9-host \
25 | build-essential \
26 | curl \
27 | geoip-bin \
28 | gettext \
29 | git-core \
30 | gawk \
31 | imagemagick \
32 | iputils-ping \
33 | language-pack-en \
34 | less \
35 | libcurl4-openssl-dev \
36 | libevent-dev \
37 | libffi-dev \
38 | libgeos-c1v5 \
39 | libmagickwand-dev \
40 | libmemcached-tools \
41 | libxml2-dev \
42 | libxslt-dev \
43 | memcached \
44 | net-tools \
45 | nginx-extras \
46 | perl \
47 | pgbouncer \
48 | postgresql-client-9.6 \
49 | postgresql-server-dev-9.6 \
50 | python-pil \
51 | python-urllib3 \
52 | python3-pip \
53 | python-pip \
54 | python-dev \
55 | python3-dev \
56 | rsyslog \
57 | socat \
58 | software-properties-common \
59 | sudo \
60 | supervisor \
61 | unattended-upgrades \
62 | unzip \
63 | vim \
64 | wget && \
65 | apt-get clean && apt-get autoclean && \
66 | find /var/lib/apt/lists/ -type f -delete
67 |
68 |
69 | RUN apt-get -y upgrade
70 |
71 | #
72 | # Install Pip Requirements
73 | # upgrade the setuptools from 27.1.2 to 32.3.1.
74 | #
75 | RUN pip3 install pip --upgrade
76 | RUN pip3 install wheel
77 | RUN pip3 install -U setuptools
78 |
79 | #
80 | # Install requirements
81 | #
82 | ADD requirements /var/www/requirements
83 | RUN pip3 install -r /var/www/requirements/common.txt
84 |
85 | RUN echo 'alias python=python3' >> ~/.bashrc && echo 'alias pip=pip3' >> ~/.bashrc
86 |
87 | # Patch Nginx Config to Disable Security Tokens
88 | RUN sed -i -e 's/# server_tokens off;/server_tokens off;/g' /etc/nginx/nginx.conf
89 |
90 | # Add JSON logging formatter for nginx
91 | ADD deploy/nginx/nginx.json-logging.conf /etc/nginx/conf.d/json-logging.conf
92 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017, Tiago Silva
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | clean: clean_pyc
2 | clean_pyc:
3 | find . -type f -name "*.pyc" -delete || true
4 |
5 | migrate:
6 | python3 $(PROJECT_NAME)/manage.py migrate --noinput
7 |
8 | migrations:
9 | python3 $(PROJECT_NAME)/manage.py makemigrations
10 |
11 | reusedb_unittests:
12 | DJANGO_SETTINGS_MODULE=$(PROJECT_NAME).testing.settings REUSE_DB=1 DJANGOENV=testing python3 $(PROJECT_NAME)/manage.py test $(TESTONLY) --with-specplugin --keepdb
13 |
14 | unittests:
15 | DJANGO_SETTINGS_MODULE=$(PROJECT_NAME).testing.settings DJANGOENV=testing python3 $(PROJECT_NAME)/manage.py test $(TESTONLY) --with-specplugin
16 |
17 | run:
18 | python3 $(PROJECT_NAME)/manage.py runserver_plus 0.0.0.0:8000 --settings=$(PROJECT_NAME).$(ENVIRONMENT).settings
19 |
20 | shell:
21 | python3 $(PROJECT_NAME)/manage.py shell_plus --settings=$(PROJECT_NAME).$(ENVIRONMENT).settings
22 |
23 | celery_django:
24 | python3 $(PROJECT_NAME)/manage.py celery -A $(PROJECT_NAME) worker --logleve=INFO --settings=$(PROJECT_NAME).$(ENVIRONMENT).settings
25 |
26 | celery:
27 | python3 $(PROJECT_NAME)/manage.py celery worker -A $(PROJECT_NAME).celery --logleve=INFO --settings=$(PROJECT_NAME).$(ENVIRONMENT).settings
28 |
29 | show_urls:
30 | python3 $(PROJECT_NAME)/manage.py show_urls --settings=$(PROJECT_NAME).$(ENVIRONMENT).settings
31 |
32 | validate_templates:
33 | python3 $(PROJECT_NAME)/manage.py validate_templates --settings=$(PROJECT_NAME).$(ENVIRONMENT).settings
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](LICENSE.txt)
2 |
3 | Django Docker Boilerplate - Django Rest Framework with Redis, RabbitMQ and Celery
4 | ===============================================================================
5 |
6 | # Notice
7 | - This boilerplate is no longer supported. Thank you very much for the contribution and it's now only supported the second version [Here](https://github.com/tiagoarasilva/django2-boilerplate)
8 |
9 | ## Note
10 |
11 | - The template is using foundation as a framework but this can be changed by your personal preference, you only need to update the references in the `static` and `templates` folder.
12 | - Inside the views located in accounts, there are some that you need to create your own files, such as the registration or change password, the code is there, you only need to place the HTML inside the proper directory. The only HTML placed and working is the login and the homepage to allow you to do a first run.
13 | - Comes with a bunch of plugins for many kinds of projects. Not all of them are needed, feel free to remove what you don't need
14 | - This boilerplate is now supporting [Django Channels](https://channels.readthedocs.io/en/stable/index.html).
15 | - Read more about this [here](https://channels.readthedocs.io/en/stable/index.html)
16 |
17 | # Installing from the template base
18 |
19 | - `django-admin startproject --template=https://github.com/tiagoarasilva/django-boilerplate/archive/master.zip --extension=py,md,html,txt,scss,sass project_name`
20 | - Make sure you change the "project_name" to the name you desire for you project.
21 | - The tests for the views won't work until you implement the solution to make the tests passing, that means, once you implement the views!
22 |
23 | # Docker
24 |
25 | - Change the {{ project_name }} in your docker file to the desired name gave to the project when running the previous command.
26 |
27 | # {{ project_name }} Docker
28 |
29 | - Run `docker volume create --name={{ project_name }}_db_data`
30 | - Run `docker-compose up`. It will download all the resources needed to build your docker containers
31 | - Run `docker-compose exec {{ project_name }} bash` to go inside the container
32 | - Run `make run` to start the server (inside docker container)
33 | - Run `make shell` to start the shell_plus
34 |
35 | If you desire, you can create somes aliases in your local machine, inside the bash_profile/bashrc/profile to do automatically some previous instructions for you
36 |
37 | E.g.:
38 |
39 | ```shell
40 | alias shell_plus='docker-compose exec {{ project_name }} bash && make run'
41 | alias run_server='docker-compose exec {{ project_name }} bash && make shell'
42 | ```
43 |
44 | # First run with the project
45 |
46 | - Inside docker container:
47 | - Run `make migrate`. This is a special command inside the Makefile to run the first migration or if you are on windows or you don't want to run the Makefile, just run `python {{ project_name }}/manage.py migrate`
48 | - Run `python {{ project_name }}/manage.py createsuperuser` to create a super user for yourself
49 | - It will create a "User Admin" by default as first and last name respectively. This can be changed in `accounts/management/commands/createsuperuser.py`
50 |
51 |
52 | # Run Tests (If you ran migrations before and need to reconstruct the DB schema)
53 |
54 | `make unittests TESTONLY='profiles.tests.models_tests'`
55 | - OR
56 | `make unittests TESTONLY='profiles.tests.models_tests:ProfileUserTest.test_create_user'` for a specific test
57 |
58 | # If you only need to run the tests and the models weren't changed before
59 |
60 | `make reusedb_unittests TESTONLY='profiles.tests.models_tests`
61 |
62 | ### Apps
63 |
64 | All of your Django "apps" go in this {{ project_name}} directory. These have models, views, forms, urls,
65 | templates or all of the above. These should be Python packages you would add to
66 | your project's `INSTALLED_APPS` list.
67 |
68 |
69 | ### Requirements for MacOS and Windows
70 |
71 | Install Homebrew (MacOS Users)
72 | `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
73 |
74 | Install OpenSSL or Upgrade (MacOS Users)
75 | `brew install openssl`
76 |
77 | ### Requirements for Linux (Ubuntu >= 16.10)
78 | Install OpenSSL or Upgrade
79 | `sudo apt update`
80 | `sudo apt install openssl-server`
81 |
82 | ### Requirements for Linux (Ubuntu <= 16.10)
83 | Install OpenSSL or Upgrade
84 | `sudo apt-get update`
85 | `sudo apt-get install openssl-server`
86 |
87 |
88 | Install VirtualenvWrapper
89 | `https://virtualenvwrapper.readthedocs.io/en/latest/install.html`
90 |
91 | Upgrade pip
92 | `pip install --upgrade pip`
93 |
94 | ### Templates
95 |
96 | Project-wide templates are located in templates/
97 |
98 | ### Celery
99 | This projects integrates Redis with RabbitMQ and Celery
100 |
--------------------------------------------------------------------------------
/deploy/nginx/nginx.json-logging.conf:
--------------------------------------------------------------------------------
1 | log_format json_format '{ "@timestamp": "$time_iso8601", '
2 | '"@fields": { '
3 | '"remote_addr": "$remote_addr", '
4 | '"remote_user": "$remote_user", '
5 | '"body_bytes_sent": "$body_bytes_sent", '
6 | '"request_time": "$request_time", '
7 | '"status": "$status", '
8 | '"request": "$request", '
9 | '"request_method": "$request_method", '
10 | '"http_referrer": "$http_referer", '
11 | '"http_user_agent": "$http_user_agent" } }';
12 |
--------------------------------------------------------------------------------
/deploy/uwsgi/uwsgi.ini:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | enable-threads = true
3 | single-interpreter=true
4 | wsgi-file = # location of the uwsgi file
5 | touch-reload = # location of the uwsgi file
6 | touch-logreopen = # location of the uwsgi file
7 | chown-socket = www-data:www-data
8 | master = true
9 | workers=4
10 | ;processes = 10
11 | socket = /var/www/uwsgi.sock
12 | chmod-socket = 664
13 | vacuum = true
14 | harakiri = 300
15 | harakiri-verbose
16 | max-requests = 300
17 | log-zero
18 | log-slow
19 | log-500
20 | log-x-forwarded-for
21 | drop-after-apps
22 | buffer-size = 32768
23 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | db:
4 | restart: always
5 | image: postgres:11.6
6 | expose:
7 | - "5432"
8 | volumes:
9 | - "{{ project_name }}_db_data:/var/lib/postgresql/data"
10 | ports:
11 | - "5432:5432"
12 |
13 | redis:
14 | restart: always
15 | image: redis:latest
16 | expose:
17 | - "6379"
18 |
19 | rabbit:
20 | restart: always
21 | image: rabbitmq:3.6.12
22 | expose:
23 | - "5672"
24 | environment:
25 | - RABBITMQ_DEFAULT_USER=rabbit_user
26 | - RABBITMQ_DEFAULT_PASS=rabbit_user_default_pass
27 |
28 | {{ project_name }}:
29 | restart: always
30 | image: oratio/libra:latest
31 | depends_on:
32 | - redis
33 | - rabbit
34 | environment:
35 | DJANGOENV: development
36 | ENVIRONMENT: development
37 | PYTHON: python
38 | PROJECT_NAME: {{ project_name }}
39 | TERM: xterm
40 | ROLE: development
41 | links:
42 | - db:postgres
43 | - redis
44 | - rabbit
45 | ports:
46 | - "80:80"
47 | - "443:443"
48 | - "8000:8000"
49 | expose:
50 | - "80"
51 | - "443"
52 | - "8000"
53 | volumes:
54 | - .:/var/www
55 | - ~/.aws/config:/root/.aws/config
56 | working_dir: /var/www
57 | command: bash -lc "pip install invoke jinja2 && invoke -r roles $${ROLE}"
58 |
59 | volumes:
60 | {{ project_name }}_db_data:
61 | external: true
62 |
--------------------------------------------------------------------------------
/project_name/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/accounts/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from accounts.models import User, Profile, ProfileType
4 |
5 | admin.site.register(User)
6 | admin.site.register(Profile)
7 | admin.site.register(ProfileType)
8 |
--------------------------------------------------------------------------------
/project_name/accounts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AccountsConfig(AppConfig):
5 | name = 'accounts'
6 |
7 |
--------------------------------------------------------------------------------
/project_name/accounts/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib.auth import get_user_model
3 | from django.contrib.auth import authenticate
4 | from django.conf import settings
5 | from django.utils.translation import ugettext_lazy as _
6 | from django.forms.models import modelformset_factory, inlineformset_factory
7 | from slugify import slugify
8 |
9 | import accounts.models
10 | import accounts.utils
11 |
12 |
13 | def check_email_domain_is_valid(email):
14 | if email.split("@")[-1].strip() in settings.BLACKLISTED_DOMAINS:
15 | raise forms.ValidationError(_("This email address is invalid."))
16 | return True
17 |
18 |
19 | class LoginForm(forms.Form):
20 |
21 | email = forms.EmailField(
22 | label=_("Email"),
23 | widget=forms.EmailInput(attrs={'autofocus': 'autofocus', 'id': 'login_email', 'Placeholder': 'Email'}))
24 | password = forms.CharField(
25 | label=_("Password"),
26 | widget=forms.PasswordInput(attrs={'id': 'login_password', 'placeholder': 'Password'}))
27 |
28 | def clean(self):
29 | try:
30 | email = self.cleaned_data['email']
31 | password = self.cleaned_data['password']
32 |
33 | user = get_user_model().objects.get(email__iexact=email, is_active=True)
34 | try:
35 | self.authed_user = authenticate(
36 | username=user.username,
37 | password=password,
38 | )
39 | except ValueError:
40 | self.authed_user = None
41 |
42 | if self.authed_user:
43 | return self.cleaned_data
44 |
45 | except (get_user_model().DoesNotExist, KeyError):
46 | pass
47 |
48 | raise forms.ValidationError("Your login details were incorrect. Please try again.")
49 |
50 | def get_user(self):
51 | return self.authed_user
52 |
53 |
54 | class RegistrationForm(forms.Form):
55 |
56 | first_name = forms.CharField(label=_('First name'), required=True, widget=forms.TextInput(
57 | attrs={
58 | 'id': 'register_first_name',
59 | 'class': 'validate black-text'
60 | }))
61 |
62 | last_name = forms.CharField(label=_('Last name'), required=True, widget=forms.TextInput(
63 | attrs={
64 | 'id': 'register_first_name',
65 | 'class': 'validate black-text'
66 | }))
67 |
68 | email = forms.EmailField(label=_("Email"), required=True, widget=forms.EmailInput(
69 | attrs={
70 | 'class': 'validate black-text',
71 | 'id': 'register_email',
72 | }))
73 |
74 | password = forms.CharField(label=_("Password"), required=True, widget=forms.PasswordInput(
75 | attrs={
76 | 'id': 'register_password',
77 | 'class': 'validate black-text',
78 | }))
79 |
80 | retype_password = forms.CharField(label=_("Repeat Password"), required=True, widget=forms.PasswordInput(
81 | attrs={
82 | 'id': 'repeat_password',
83 | 'class': 'validate black-text',
84 | }))
85 |
86 | profile_type = forms.ChoiceField(choices=accounts.models.Choices.Profiles.PROFILE_CHOICES)
87 |
88 | def clean(self):
89 | cleaned_data = super(RegistrationForm, self).clean()
90 | self._validate_password()
91 | return cleaned_data
92 |
93 | def clean_first_name(self):
94 | first_name = self.cleaned_data['first_name']
95 |
96 | if first_name.strip() == "" or first_name.strip() is None:
97 | raise forms.ValidationError(_("First name cannot be empty."))
98 | return first_name
99 |
100 | def clean_last_name(self):
101 | first_name = self.cleaned_data['last_name']
102 |
103 | if first_name.strip() == "" or first_name.strip() is None:
104 | raise forms.ValidationError(_("Last name cannot be empty."))
105 | return first_name
106 |
107 | def clean_email(self, raise_on_duplicate=True):
108 | email = self.cleaned_data['email'].lower().strip().rstrip(".")
109 | try:
110 | self.user = get_user_model().objects.get(email__iexact=email)
111 | except get_user_model().DoesNotExist:
112 | pass
113 | except get_user_model().MultipleObjectsReturned:
114 | raise forms.ValidationError(_("There is already an account with that email address."))
115 | else:
116 | if raise_on_duplicate or self.user.has_usable_password():
117 | raise forms.ValidationError(_("There is already an account with that email address."))
118 |
119 | check_email_domain_is_valid(email)
120 | return email
121 |
122 | def clean_password(self):
123 | password = self.cleaned_data['password'].strip()
124 |
125 | if not password:
126 | self.add_error('password', _("The password cannot be empty"))
127 | return password
128 |
129 | def clean_retype_password(self):
130 | retype_password = self.cleaned_data['retype_password'].strip()
131 |
132 | if not retype_password:
133 | self.add_error('retype_password', _("The password cannot be empty"))
134 |
135 | self._validate_password()
136 | return retype_password
137 |
138 | def _validate_password(self):
139 | password = self.cleaned_data.get('password')
140 | retype_password = self.cleaned_data.get('retype_password')
141 |
142 | if password != retype_password:
143 | self.add_error('password', _("The passwords do not match"))
144 |
145 |
--------------------------------------------------------------------------------
/project_name/accounts/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/accounts/management/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/accounts/management/commands/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/management/commands/createsuperuser.py:
--------------------------------------------------------------------------------
1 | """
2 | Management utility to create superusers.
3 | """
4 | import getpass
5 | import sys
6 |
7 | from django.contrib.auth import get_user_model
8 | from django.contrib.auth.management import get_default_username
9 | from django.contrib.auth.password_validation import validate_password
10 | from django.core import exceptions
11 | from django.core.management.base import BaseCommand, CommandError
12 | from django.db import DEFAULT_DB_ALIAS
13 | from django.utils.text import capfirst
14 |
15 | from accounts.models import Profile, ProfileType
16 | from accounts.utils import generate_username, generate_profile_type
17 |
18 |
19 | class NotRunningInTTYException(Exception):
20 | pass
21 |
22 |
23 | class Command(BaseCommand):
24 | help = 'Used to create a superuser.'
25 | requires_migrations_checks = True
26 | stealth_options = ('stdin',)
27 |
28 | def __init__(self, *args, **kwargs):
29 | super().__init__(*args, **kwargs)
30 | self.UserModel = get_user_model()
31 | self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)
32 |
33 | def add_arguments(self, parser):
34 | parser.add_argument(
35 | '--%s' % self.UserModel.USERNAME_FIELD,
36 | dest=self.UserModel.USERNAME_FIELD, default=None,
37 | help='Specifies the login for the superuser.',
38 | )
39 | parser.add_argument(
40 | '--noinput', '--no-input', action='store_false', dest='interactive',
41 | help=(
42 | 'Tells Django to NOT prompt the user for input of any kind. '
43 | 'You must use --%s with --noinput, along with an option for '
44 | 'any other required field. Superusers created with --noinput will '
45 | 'not be able to log in until they\'re given a valid password.' %
46 | self.UserModel.USERNAME_FIELD
47 | ),
48 | )
49 | parser.add_argument(
50 | '--database', action='store', dest='database',
51 | default=DEFAULT_DB_ALIAS,
52 | help='Specifies the database to use. Default is "default".',
53 | )
54 | for field in self.UserModel.REQUIRED_FIELDS:
55 | parser.add_argument(
56 | '--%s' % field, dest=field, default=None,
57 | help='Specifies the %s for the superuser.' % field,
58 | )
59 |
60 | def execute(self, *args, **options):
61 | self.stdin = options.get('stdin', sys.stdin) # Used for testing
62 | return super().execute(*args, **options)
63 |
64 | def handle(self, *args, **options):
65 | username = options[self.UserModel.USERNAME_FIELD]
66 | database = options['database']
67 |
68 | # If not provided, create the user with an unusable password
69 | password = None
70 | user_data = {}
71 | # Same as user_data but with foreign keys as fake model instances
72 | # instead of raw IDs.
73 | fake_user_data = {}
74 |
75 | # Do quick and dirty validation if --noinput
76 | if not options['interactive']:
77 | try:
78 | if not username:
79 | raise CommandError("You must use --%s with --noinput." % self.UserModel.USERNAME_FIELD)
80 | username = self.username_field.clean(username, None)
81 |
82 | for field_name in self.UserModel.REQUIRED_FIELDS:
83 | if options[field_name]:
84 | field = self.UserModel._meta.get_field(field_name)
85 | user_data[field_name] = field.clean(options[field_name], None)
86 | else:
87 | raise CommandError("You must use --%s with --noinput." % field_name)
88 | except exceptions.ValidationError as e:
89 | raise CommandError('; '.join(e.messages))
90 |
91 | else:
92 | # Prompt for username/password, and any other required fields.
93 | # Enclose this whole thing in a try/except to catch
94 | # KeyboardInterrupt and exit gracefully.
95 | default_username = get_default_username()
96 | try:
97 |
98 | if hasattr(self.stdin, 'isatty') and not self.stdin.isatty():
99 | raise NotRunningInTTYException("Not running in a TTY")
100 |
101 | # Get a username
102 | verbose_field_name = self.username_field.verbose_name
103 | while username is None:
104 | input_msg = capfirst(verbose_field_name)
105 | if default_username:
106 | input_msg += " (leave blank to use '%s')" % default_username
107 | username_rel = self.username_field.remote_field
108 | input_msg = '%s%s: ' % (
109 | input_msg,
110 | ' (%s.%s)' % (
111 | username_rel.model._meta.object_name,
112 | username_rel.field_name
113 | ) if username_rel else ''
114 | )
115 | username = self.get_input_data(self.username_field, input_msg, default_username)
116 | if not username:
117 | continue
118 | if self.username_field.unique:
119 | try:
120 | self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
121 | except self.UserModel.DoesNotExist:
122 | pass
123 | else:
124 | self.stderr.write("Error: That %s is already taken." % verbose_field_name)
125 | username = None
126 |
127 | for field_name in self.UserModel.REQUIRED_FIELDS:
128 | field = self.UserModel._meta.get_field(field_name)
129 | user_data[field_name] = options[field_name]
130 | while user_data[field_name] is None:
131 | message = '%s%s: ' % (
132 | capfirst(field.verbose_name),
133 | ' (%s.%s)' % (
134 | field.remote_field.model._meta.object_name,
135 | field.remote_field.field_name,
136 | ) if field.remote_field else '',
137 | )
138 | input_value = self.get_input_data(field, message)
139 | user_data[field_name] = input_value
140 | fake_user_data[field_name] = input_value
141 |
142 | # Wrap any foreign keys in fake model instances
143 | if field.remote_field:
144 | fake_user_data[field_name] = field.remote_field.model(input_value)
145 |
146 | # Get a password
147 | while password is None:
148 | password = getpass.getpass()
149 | password2 = getpass.getpass('Password (again): ')
150 | if password != password2:
151 | self.stderr.write("Error: Your passwords didn't match.")
152 | password = None
153 | # Don't validate passwords that don't match.
154 | continue
155 |
156 | if password.strip() == '':
157 | self.stderr.write("Error: Blank passwords aren't allowed.")
158 | password = None
159 | # Don't validate blank passwords.
160 | continue
161 |
162 | try:
163 | validate_password(password2, self.UserModel(**fake_user_data))
164 | except exceptions.ValidationError as err:
165 | self.stderr.write('\n'.join(err.messages))
166 | password = None
167 |
168 | except KeyboardInterrupt:
169 | self.stderr.write("\nOperation cancelled.")
170 | sys.exit(1)
171 |
172 | except NotRunningInTTYException:
173 | self.stdout.write(
174 | "Superuser creation skipped due to not running in a TTY. "
175 | "You can run `manage.py createsuperuser` in your project "
176 | "to create one manually."
177 | )
178 |
179 | if username:
180 | user_data[self.UserModel.USERNAME_FIELD] = username
181 | user_data['password'] = password
182 | self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
183 | if options['verbosity'] >= 1:
184 | self.stdout.write("Superuser created successfully.")
185 |
186 | self.generate_profile_from_user(**user_data)
187 |
188 | def get_input_data(self, field, message, default=None):
189 | """
190 | Override this method if you want to customize data inputs or
191 | validation exceptions.
192 | """
193 | raw_value = input(message)
194 | if default and raw_value == '':
195 | raw_value = default
196 | try:
197 | val = field.clean(raw_value, None)
198 | except exceptions.ValidationError as e:
199 | self.stderr.write("Error: %s" % '; '.join(e.messages))
200 | val = None
201 |
202 | return val
203 |
204 | def generate_profile_from_user(self, **user_data):
205 | from slugify import slugify
206 | user = get_user_model().objects.get(email=user_data.get('email'))
207 | user.first_name = u'User'
208 | user.last_name = u'Admin'
209 |
210 | username = generate_username(user.username)
211 | profile = Profile.objects.create(
212 | user=user, slug=slugify(username),
213 | )
214 | generate_profile_type(profile)
215 | user.save()
216 | profile.save()
217 |
--------------------------------------------------------------------------------
/project_name/accounts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.4 on 2017-09-04 17:41
3 | from __future__ import unicode_literals
4 |
5 | from django.conf import settings
6 | import django.contrib.auth.models
7 | import django.contrib.auth.validators
8 | from django.db import migrations, models
9 | import django.db.models.deletion
10 | import django.utils.timezone
11 |
12 |
13 | class Migration(migrations.Migration):
14 |
15 | initial = True
16 |
17 | dependencies = [
18 | ('auth', '0008_alter_user_username_max_length'),
19 | ]
20 |
21 | operations = [
22 | migrations.CreateModel(
23 | name='User',
24 | fields=[
25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26 | ('password', models.CharField(max_length=128, verbose_name='password')),
27 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
28 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
29 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
30 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
31 | ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
32 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
33 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
34 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
35 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
36 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
37 | ],
38 | options={
39 | 'db_table': 'auth_user',
40 | 'permissions': (('can_view_dashboard', 'Can view all dashboards'), ('can_view_store_profiles', 'Can store profiles')),
41 | },
42 | managers=[
43 | ('objects', django.contrib.auth.models.UserManager()),
44 | ],
45 | ),
46 | migrations.CreateModel(
47 | name='Profile',
48 | fields=[
49 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
50 | ('slug', models.SlugField(help_text='Slug', max_length=255, unique=True)),
51 | ('created_at', models.DateTimeField(auto_now_add=True)),
52 | ('modified_at', models.DateTimeField(auto_now=True)),
53 | ('is_hidden', models.BooleanField(default=False)),
54 | ('is_disabled', models.BooleanField(default=False)),
55 | ('is_password_changed', models.BooleanField(default=False)),
56 | ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
57 | ],
58 | ),
59 | migrations.CreateModel(
60 | name='ProfileType',
61 | fields=[
62 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63 | ('profile_type', models.CharField(choices=[('admin', 'Admin'), ('user', 'User'), ('manager', 'Manager'), ('other', 'Other')], default='admin', max_length=255)),
64 | ('profile', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='profile_types', to='accounts.Profile')),
65 | ],
66 | ),
67 | migrations.CreateModel(
68 | name='Permission',
69 | fields=[
70 | ],
71 | options={
72 | 'permissions': (('shorten_urls', 'Can shorten urls in the dashboard'),),
73 | 'proxy': True,
74 | 'indexes': [],
75 | },
76 | bases=('auth.permission',),
77 | managers=[
78 | ('objects', django.contrib.auth.models.PermissionManager()),
79 | ],
80 | ),
81 | migrations.AddField(
82 | model_name='user',
83 | name='user_permissions',
84 | field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
85 | ),
86 | ]
87 |
--------------------------------------------------------------------------------
/project_name/accounts/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/accounts/migrations/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | import bleach
3 |
4 | from django.conf import settings
5 | from django.utils.translation import ugettext_lazy as _
6 | from django.core.urlresolvers import reverse
7 | from django.contrib.auth.models import Permission as DjangoPermission
8 | from django.contrib.auth.models import AbstractUser
9 | from django.db.models.signals import post_init, post_save
10 | from django.db import models
11 |
12 | from lib.cache.decorators import memoize_invalidate
13 |
14 |
15 | class Choices(object):
16 |
17 | class Profiles:
18 | ADMIN = u'admin'
19 | USER = u'user'
20 | MANAGER = u'manager'
21 | OTHER = u'other'
22 |
23 | PROFILE_CHOICES = (
24 | (ADMIN, 'Admin'),
25 | (USER, 'User'),
26 | (MANAGER, 'Manager'),
27 | (OTHER, 'Other'),
28 | )
29 |
30 |
31 | class ProfileType(models.Model):
32 | profile = models.ForeignKey('accounts.Profile', null=False, blank=True, related_name='profile_types')
33 | profile_type = models.CharField(max_length=255, choices=Choices.Profiles.PROFILE_CHOICES,
34 | default=Choices.Profiles.USER, null=False, blank=False)
35 |
36 | def __str__(self):
37 | return self.get_profile_type_display()
38 |
39 |
40 | class Profile(models.Model):
41 | """
42 | For every user, there is a profile associated. This model is a representation of that same profile
43 | """
44 |
45 | user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='profile')
46 | slug = models.SlugField(max_length=255, help_text=_('Slug'), blank=False, null=False, unique=True)
47 | created_at = models.DateTimeField(null=False, blank=False, auto_now_add=True)
48 | modified_at = models.DateTimeField(null=False, blank=False, auto_now=True)
49 | is_hidden = models.BooleanField(default=False, blank=False, null=False)
50 | is_disabled = models.BooleanField(default=False, blank=False, null=False)
51 | is_password_changed = models.BooleanField(default=False, blank=False, null=False)
52 |
53 | def __str__(self):
54 | return self.slug
55 |
56 |
57 | class User(AbstractUser):
58 | """
59 | Model responsible for the user maintenance for the platform
60 | """
61 | class Meta:
62 | db_table = 'auth_user'
63 | permissions = (('can_view_dashboard', 'Can view all dashboards'),
64 | ('can_view_store_profiles', 'Can store profiles'),)
65 |
66 | @memoize_invalidate
67 | def get_or_create_profile(self, first_name=None, last_name=None):
68 | try:
69 | return Profile.objects.get(user=self)
70 | except Profile.DoesNotExist:
71 | profile = Profile.objects.create(
72 | user=self, slug=self.username
73 | )
74 | profile.save()
75 | return profile
76 |
77 | def to_json_dict(self, context=None):
78 | data = {
79 | 'id': self.pk,
80 | 'username': bleach.clean(self.username),
81 | 'email_url': reverse('postman_write', args=(self.pk,))
82 | }
83 |
84 | profile = self.profile
85 | if profile is not None:
86 | data['profile'] = profile.to_json_dict(context)
87 |
88 | return data
89 |
90 | def get_primary_email(self):
91 | return self.email
92 |
93 | @staticmethod
94 | def post_init(sender, **kwargs):
95 | instance = kwargs["instance"]
96 | instance.first_name = bleach.clean(instance.first_name)
97 | instance.last_name = bleach.clean(instance.last_name)
98 | instance.old_email = instance.email
99 | instance.old_is_active = instance.is_active
100 | instance.old_first_name = instance.first_name
101 | instance.old_last_name = instance.last_name
102 |
103 | @staticmethod
104 | def post_save(sender, **kwargs):
105 | instance = kwargs["instance"]
106 | try:
107 | profile = instance.profile
108 | except Profile.DoesNotExist:
109 | pass
110 | instance.old_email = instance.email
111 | instance.old_first_name = instance.first_name
112 | instance.old_last_name = instance.last_name
113 |
114 | @property
115 | def display_name(self):
116 | return self.first_name + u' ' + self.last_name
117 |
118 |
119 | post_init.connect(User.post_init, sender=User)
120 | post_save.connect(User.post_save, sender=User)
121 |
122 |
123 | class Permission(DjangoPermission):
124 |
125 | class Meta:
126 | proxy = True
127 | permissions = (
128 | ('shorten_urls', 'Can shorten urls anywhere'),
129 | )
130 |
--------------------------------------------------------------------------------
/project_name/accounts/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/accounts/templatetags/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/templatetags/accounts.py:
--------------------------------------------------------------------------------
1 | from django.template import Library
2 | from accounts.models import ProfileType
3 |
4 | register = Library()
5 |
6 |
7 | @register.simple_tag
8 | def profiles(user):
9 | profile_type = ProfileType.objects.filter( profile=user.profile)
10 | return profile_type
11 |
--------------------------------------------------------------------------------
/project_name/accounts/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/accounts/tests/__init__.py
--------------------------------------------------------------------------------
/project_name/accounts/tests/factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | import uuid
3 | import factory.django
4 |
5 | from django.contrib.auth import get_user_model
6 | import accounts.models
7 |
8 |
9 | class ProfileFactory(factory.django.DjangoModelFactory):
10 | slug = factory.LazyAttribute(lambda u: "testes-%s" % str(uuid.uuid4())[0:12])
11 |
12 | class Meta:
13 | model = accounts.models.Profile
14 |
15 |
16 | class ProfileTypeFactory(factory.django.DjangoModelFactory):
17 | profile = factory.SubFactory(ProfileFactory)
18 | profile_type = accounts.models.Choices.Profiles.USER
19 |
20 | class Meta:
21 | model = accounts.models.ProfileType
22 |
23 |
24 | class UserFactory(factory.django.DjangoModelFactory):
25 | username = factory.Sequence(lambda n: "testes-%s" % n)
26 | password = "testes"
27 | first_name = "Test"
28 | last_name = 'User'
29 | email = factory.LazyAttribute(lambda u: "%s@testes.example.com" % u.username)
30 | profile = factory.RelatedFactory(ProfileFactory, factory_related_name='user')
31 |
32 | class Meta:
33 | model = get_user_model()
34 |
35 | @classmethod
36 | def _prepare(cls, create, **kwargs):
37 | user = super()._prepare(create, **kwargs)
38 | password = kwargs.pop('password', None)
39 | if password:
40 | user.set_password(password)
41 | if create:
42 | user.save()
43 | return user
44 |
--------------------------------------------------------------------------------
/project_name/accounts/tests/models_tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | import accounts.tests.factories
4 | import accounts.models
5 |
6 |
7 | class UserAccountModelTest(TestCase):
8 |
9 | def setUp(self):
10 | self.user = accounts.tests.factories.UserFactory(username='stuff')
11 | self.user_2 = accounts.tests.factories.UserFactory(username='{{ project_name }}_2')
12 |
13 | def test_create_user(self):
14 | self.assertEqual('stuff', self.user.username)
15 |
16 | def test_create_user_with_specific_email(self):
17 | self.user.email = '{{ project_name }}@test.com'
18 | self.user.save()
19 |
20 | self.assertEqual(self.user.email, '{{ project_name }}@test.com')
21 |
22 | def test_create_user_with_specific_password(self):
23 | self.user.password = '123password'
24 | self.user.save()
25 |
26 | self.assertEqual(self.user.password, '123password')
27 |
28 | def test_create_user_with_staff_access(self):
29 | self.user.is_staff = True
30 | self.user.save()
31 |
32 | self.assertEqual(self.user.is_staff, True)
33 |
34 | def test_can_create_more_than_one_user_at_the_same_time(self):
35 | self.assertNotEqual(self.user, self.user_2)
36 |
37 |
38 | class ProfileTypeTests(TestCase):
39 |
40 | def setUp(self):
41 | self.profile = accounts.tests.factories.ProfileFactory()
42 | self.profile_type = accounts.tests.factories.ProfileTypeFactory()
43 |
44 | def test_can_create_profile_type_object(self):
45 | self.profile_type.profile_type = accounts.models.Choices.Profiles.ADMIN
46 | self.profile_type.save()
47 |
48 | self.assertEqual(self.profile_type.profile_type, accounts.models.Choices.Profiles.ADMIN)
49 |
50 | def test_can_have_multiple_profile_types(self):
51 |
52 | accounts.tests.factories.ProfileTypeFactory(
53 | profile=self.profile,
54 | profile_type=accounts.models.Choices.Profiles.USER
55 | )
56 |
57 | accounts.tests.factories.ProfileTypeFactory(
58 | profile=self.profile,
59 | profile_type=accounts.models.Choices.Profiles.ADMIN
60 | )
61 |
62 | profiles = accounts.models.ProfileType.objects.filter(
63 | profile=self.profile
64 | )
65 |
66 | self.assertEqual(profiles.count(), 2)
67 |
--------------------------------------------------------------------------------
/project_name/accounts/tests/utils_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.test import TestCase
3 | from slugify import slugify
4 |
5 | import accounts.tests.factories
6 | import accounts.utils
7 |
8 |
9 | class UtilsTest(TestCase):
10 |
11 | def setUp(self):
12 | self.user = accounts.tests.factories.UserFactory(username=u'{{ project_name }}-pórto')
13 |
14 | def test_can_slugify_string(self):
15 | result = slugify(self.user.username)
16 |
17 | self.assertEqual(result, u'{{ project_name }}-porto')
18 |
19 | def test_can_update_profile_slug(self):
20 | profile = accounts.tests.factories.ProfileFactory(slug=u'slug!déstá-coisa')
21 | user = accounts.tests.factories.UserFactory(profile=profile, username=u'slug!déstá-coisa')
22 | slug_slugified = slugify(user.username)
23 |
24 | self.assertEqual(slug_slugified, u'slug-desta-coisa')
25 |
26 | profile_expected = accounts.utils.update_profile_slug(profile, user)
27 | user.username = profile.slug
28 | user.save()
29 |
30 | self.assertEqual(profile_expected.slug, u'slug-desta-coisa')
31 | self.assertEqual(profile_expected.slug, user.username)
32 | self.assertEqual(user.username, u'slug-desta-coisa')
33 |
--------------------------------------------------------------------------------
/project_name/accounts/tests/views_tests.py:
--------------------------------------------------------------------------------
1 | from django_webtest import WebTest
2 | from django.core.urlresolvers import reverse
3 |
4 | import accounts.tests.factories
5 | import accounts.models
6 | import {{ project_name }}.third_parties.blacklist_domains
7 |
8 |
9 | class ListUsersViewTest(WebTest):
10 |
11 | def setUp(self):
12 | self.user = accounts.tests.factories.UserFactory(username=u'{{ project_name }}', email="cenas@cenas.com")
13 | self.url = reverse('profiles:list-users')
14 |
15 | def test_get_login_page(self):
16 | response = self.app.get(self.url, user=self.user)
17 |
18 | self.assertEqual(200, response.status_code)
19 |
20 |
21 | class LoginAndRegisterViewTest(WebTest):
22 |
23 | def setUp(self):
24 | self.user = accounts.tests.factories.UserFactory(username=u'{{ project_name }}', email="cenas@cenas.com")
25 | self.url_login = reverse('login')
26 |
27 | def _get_profile_page_data(self):
28 | return reverse('profiles:view-profile', kwargs={
29 | 'slug': self.user.profile.slug
30 | })
31 |
32 | def test_get_login_page(self):
33 | response = self.app.get(self.url_login)
34 |
35 | self.assertEqual(200, response.status_code)
36 |
37 | def test_can_get_the_profile_page_for_logged_user(self):
38 | response = self.app.get(self._get_profile_page_data(), user=self.user)
39 |
40 | self.assertEqual(200, response.status_code)
41 |
42 |
43 | class AddUserViewTest(WebTest):
44 | def setUp(self):
45 | self.user = accounts.tests.factories.UserFactory(username=u'{{ project_name }}')
46 | self.url = reverse('profiles:add-user')
47 |
48 | def _get_user_form_data(self, email='testeFirst@testeLast.com'):
49 | page = self.app.get(self.url, user=self.user)
50 | form = page.forms[0]
51 | form['first_name'] = 'testeFirst'
52 | form['last_name'] = 'testeLast'
53 | form['email'] = email
54 | form['password'] = '12345678'
55 | form['retype_password'] = '12345678'
56 | form['profile_type'] = accounts.models.Choices.ProfileType.STORE_PROFILES
57 | response = form.submit()
58 |
59 | return page, response
60 |
61 | def test_can_get_to_the_add_view_page(self):
62 | page = self.app.get(self.url, user=self.user)
63 |
64 | self.assertEqual(200, page.status_code)
65 |
66 | def test_can_add_new_user(self):
67 | page, response = self._get_user_form_data()
68 |
69 | self.assertEqual(302, response.status_code)
70 |
71 | def test_user_added_has_correct_profile_type(self):
72 | page, response = self._get_user_form_data()
73 | profile_wanted = accounts.models.Profile.objects.get(slug=self.user.profile.slug)
74 | profile_wanted.profile_type = accounts.models.Choices.ProfileType.STORE_PROFILES
75 | profile_wanted.save()
76 |
77 | self.assertEqual(profile_wanted.slug, self.user.profile.slug)
78 | self.assertEqual(profile_wanted.profile_type, accounts.models.Choices.ProfileType.STORE_PROFILES)
79 |
80 | def test_add_users_with_same_email_fails(self):
81 | page, response = self._get_user_form_data()
82 | page_now, response_now = self._get_user_form_data()
83 |
84 | self.assertIn(u'There is already an account with that email address.', str(response_now.content))
85 |
86 | def test_add_user_without_fields_that_are_required(self):
87 | page = self.app.get(self.url, user=self.user)
88 | form = page.forms[0]
89 | form['first_name'] = '123'
90 | form['last_name'] = ''
91 | form['email'] = '123@456.com'
92 | form['password'] = '12345678'
93 | form['retype_password'] = ''
94 | form['profile_type'] = accounts.models.Choices.ProfileType.STORE_PROFILES
95 | response = form.submit()
96 |
97 | self.assertFormError(response, 'form', 'last_name', 'This field is required.')
98 | self.assertFormError(response, 'form', 'retype_password', 'This field is required.')
99 |
100 | def test_add_user_with_blacklisted_domain(self):
101 | b_list = {{ project_name }}.third_parties.blacklist_domains.BLACKLISTED_DOMAINS
102 | import random
103 | email = 'test@' + random.sample(b_list, 1)[0]
104 | page, response = self._get_user_form_data(email=email)
105 |
106 | self.assertIn(u'This email address is invalid.', str(response.content))
107 |
108 |
--------------------------------------------------------------------------------
/project_name/accounts/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 |
3 | import accounts.views
4 |
5 | accounts_urlpatterns = [
6 | url(r'^user/create/$', accounts.views.RegisterProfileView.as_view(), name='add-user'),
7 | ]
8 |
--------------------------------------------------------------------------------
/project_name/accounts/utils.py:
--------------------------------------------------------------------------------
1 | import uuid
2 |
3 | from slugify import slugify
4 |
5 | import accounts.models
6 |
7 |
8 | def generate_username(first_name):
9 | uuid_user = str(uuid.uuid4())
10 | username = first_name.lower() + "-{id}".format(id=uuid_user[:12])
11 | return username
12 |
13 |
14 | def generate_profile_type(profile):
15 | profile_type = accounts.models.ProfileType.objects.create(
16 | profile=profile, profile_type=accounts.models.Choices.Profiles.ADMIN
17 | )
18 | return profile_type
19 |
20 |
21 | def update_profile_slug(profile, user):
22 | try:
23 | slug = slugify(user.username)
24 | profile.slug = slug
25 | profile.save()
26 | return profile
27 | except accounts.models.Profile.DoesNotExist:
28 | pass
29 |
--------------------------------------------------------------------------------
/project_name/accounts/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth import get_user_model
3 | from django.contrib.auth import login, logout, update_session_auth_hash
4 | from django.core.urlresolvers import reverse
5 | from django.db import IntegrityError
6 | from django.db.models import Q
7 | from django.http import HttpResponseRedirect
8 | from django.utils.translation import ugettext_lazy as _
9 | from django.views.generic import View, TemplateView, FormView, UpdateView, ListView, CreateView
10 | from lib.common.views import BaseTemplateMixin
11 |
12 | import accounts.forms
13 | import accounts.models
14 | import accounts.utils
15 | import accounts.models
16 |
17 |
18 | def login_and_handle_data_stored_in_session(user, request):
19 | session_key = request.session.session_key
20 | login(request, user)
21 |
22 |
23 | class BaseUserSystemView(BaseTemplateMixin):
24 |
25 | def get_object(self):
26 | user = accounts.models.User.objects.get(
27 | profile__slug=self.kwargs.get('slug', self.request.user.profile.slug)
28 | )
29 | if self.request.user.email == user.email:
30 | return self.request.user
31 | return user
32 |
33 | def get_back_url(self):
34 | return self.request.META.get("HTTP_REFERER", reverse('homepage'))
35 |
36 | def get_context_data(self, **kwargs):
37 | context = super().get_context_data(**kwargs)
38 | context.update({
39 | 'user': self.get_object(),
40 | 'active': False
41 | })
42 | return context
43 |
44 |
45 | class LoginView(FormView):
46 | template_name = '{{ project_name }}/auth/login.html'
47 | form_class = accounts.forms.LoginForm
48 |
49 | def get(self, request, *args, **kwargs):
50 | if request.user.is_authenticated():
51 | return HttpResponseRedirect(self.get_success_url())
52 | return super().get(request, *args, **kwargs)
53 |
54 | def form_valid(self, form):
55 | user = form.get_user()
56 | login_and_handle_data_stored_in_session(user, self.request)
57 | return HttpResponseRedirect(self.get_success_url())
58 |
59 | def form_invalid(self, form):
60 | messages.add_message(
61 | self.request, messages.ERROR, _("The credentials that you've entered don't match any account")
62 | )
63 | return super().form_invalid(form)
64 |
65 | def get_success_url(self):
66 | return reverse('homepage')
67 |
68 |
69 | class RegisterProfileView(FormView):
70 | template_name = '{{ project_name }}/application/configurations/add-user.html'
71 | form_class = accounts.forms.RegistrationForm
72 |
73 | def form_valid(self, form):
74 | first_name = form.data['first_name']
75 | last_name = form.data['last_name']
76 | email = form.data['email']
77 | password = form.data['password']
78 | profile_type = form.data['profile_type']
79 | username = accounts.utils.generate_username(first_name)
80 |
81 | try:
82 | user = get_user_model().objects.create_user(
83 | username=username, email=email, password=password,
84 | is_staff=False, is_superuser=False, first_name=first_name, last_name=last_name
85 | )
86 |
87 | except IntegrityError:
88 | msg = _("There was a problem processing your information, please try again")
89 | messages.add_message(self.request, messages.ERROR, _(msg))
90 | return super().form_valid(form)
91 |
92 | user.save()
93 | profile = user.get_or_create_profile(first_name=first_name, last_name=last_name)
94 | profile.profile_type = profile_type
95 | profile.save()
96 |
97 | msg = _("The profile for {email} has been created").format(
98 | email=user.email
99 | )
100 | messages.add_message(self.request, messages.SUCCESS, _(msg))
101 | return HttpResponseRedirect(self.get_success_url())
102 |
103 | def form_invalid(self, form):
104 | for errors in form.errors.items():
105 | key, msg = errors
106 | messages.add_message(self.request, messages.ERROR, _(msg[0]))
107 | return super().form_invalid(form)
108 |
109 | def get_success_url(self):
110 | return reverse('homepage')
111 |
112 |
113 | class LogoutView(View):
114 | def get(self, request, *args, **kwargs):
115 | logout(self.request)
116 | return HttpResponseRedirect(reverse('login'))
117 |
118 |
119 | class HomepageView(TemplateView):
120 | template_name = '{{ project_name }}/application/homepage/homepage.html'
121 |
122 | def get(self, request, *args, **kwargs):
123 | if request.user.is_authenticated():
124 | return super(HomepageView, self).get(request, *args, **kwargs)
125 | return HttpResponseRedirect(reverse('login'))
126 |
127 |
--------------------------------------------------------------------------------
/project_name/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/api/__init__.py
--------------------------------------------------------------------------------
/project_name/api/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/project_name/api/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ApiConfig(AppConfig):
5 | name = 'api'
6 |
--------------------------------------------------------------------------------
/project_name/api/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/api/migrations/__init__.py
--------------------------------------------------------------------------------
/project_name/api/mixins.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 | from rest_framework import authentication
3 | from rest_framework.authentication import SessionAuthentication, BasicAuthentication
4 | from rest_framework.permissions import IsAuthenticated
5 | from rest_framework import exceptions
6 |
7 |
8 | class AnonymousAuthentication(authentication.BaseAuthentication):
9 |
10 | def authenticate(self, request):
11 | return (request._request.user, None)
12 |
13 |
14 | class BaseAuthView(permissions.BasePermission):
15 | authentication_classes = (SessionAuthentication, BasicAuthentication,)
16 | permission_classes = (IsAuthenticated,)
17 |
18 |
19 | class BaseGetApiView(BaseAuthView, permissions.BasePermission):
20 | def has_permission(self, request, view):
21 | if request.method == 'GET':
22 | return True
23 | return request.user and request.user.is_authenticated()
24 |
25 |
26 | class BaseDestroyApiView(BaseAuthView):
27 |
28 | def has_permission(self, request, view):
29 | if request.method == 'DELETE':
30 | return True
31 | return request.user and request.user.is_authenticated()
32 |
33 |
34 | class BasePostApiView(BaseAuthView):
35 |
36 | def has_permission(self, request, view):
37 | if request.method == 'POST':
38 | return True
39 | return request.user and request.user.is_authenticated()
40 |
41 |
42 | class BasePutApiView(BaseAuthView):
43 |
44 | def has_permission(self, request, view):
45 | if request.method == 'PUT':
46 | return True
47 | return request.user and request.user.is_authenticated()
48 |
49 |
50 | class BaseUpdateApiView(BaseAuthView):
51 |
52 | def has_permission(self, request, view):
53 | if request.method == 'POST' or request.method == 'PUT' or request.method == 'PATCH':
54 | return True
55 | return request.user and request.user.is_authenticated()
56 |
57 |
58 | class BaseUpdateAndDestroyApiView(BaseAuthView):
59 |
60 | def has_permission(self, request, view):
61 | if request.method == 'POST' or request.method == 'PUT' or request.method == 'PATCH' or request.method == 'DELETE':
62 | return True
63 | return request.user and request.user.is_authenticated()
64 |
65 |
66 | class BaseCRUDApiView(BaseAuthView):
67 |
68 | def has_permission(self, request, view):
69 | if request.method == 'POST' or request.method == 'PUT' or request.method == 'PATCH' or request.method == 'GET':
70 | return True
71 | return request.user and request.user.is_authenticated()
72 |
73 |
--------------------------------------------------------------------------------
/project_name/api/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/project_name/api/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/project_name/api/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 |
3 | api_url_pattern = [
4 |
5 | ]
6 |
--------------------------------------------------------------------------------
/project_name/api/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 |
--------------------------------------------------------------------------------
/project_name/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/core/__init__.py
--------------------------------------------------------------------------------
/project_name/core/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | """
4 | Package that allow the whole project to use common functions as a resource
5 | """
6 |
7 | class TimeStampedModel(models.Model):
8 | """
9 | An Abstract Base Class provides self-updating `create` and `modified` fields, for every model
10 | """
11 | created = models.DateTimeField(auto_now_add=True)
12 | modified = models.DateTimeField(auto_now=True)
13 |
14 | class Meta:
15 | abstract = True
16 |
--------------------------------------------------------------------------------
/project_name/core/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/core/tests/__init__.py
--------------------------------------------------------------------------------
/project_name/lib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/lib/__init__.py
--------------------------------------------------------------------------------
/project_name/lib/audit/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008, 2009 James Aylett
') and content.endswith('
'): 43 | return content[3:-4] 44 | return content 45 | -------------------------------------------------------------------------------- /project_name/lib/storage.py: -------------------------------------------------------------------------------- 1 | import django.contrib.staticfiles.storage 2 | 3 | 4 | class CachedStaticFilesStorage( 5 | django.contrib.staticfiles.storage.CachedStaticFilesStorage): 6 | 7 | def hashed_name(self, name, content=None): 8 | try: 9 | return super(CachedStaticFilesStorage, self).hashed_name( 10 | name, content=content) 11 | except ValueError: 12 | # If there is a static asset that refers to another asset 13 | # that doesn't exist, don't fall over, just ignore it. 14 | return name 15 | 16 | 17 | class DatabaseRouter(object): 18 | """ 19 | Determine how to route database calls for an app's modelos (in this case, for an app named Example). 20 | All other modelos will be routed to the next router in the DATABASE_ROUTERS setting if applicable, 21 | or otherwise to the default database. 22 | """ 23 | 24 | def db_for_read(self, model, **hints): 25 | """Send all read operations on Example app modelos to `example_db`.""" 26 | if model._meta.app_label == 'example': 27 | return 'example_db' 28 | return None 29 | 30 | def db_for_write(self, model, **hints): 31 | """Send all write operations on Example app modelos to `example_db`.""" 32 | if model._meta.app_label == 'example': 33 | return 'example_db' 34 | return None 35 | 36 | def allow_relation(self, obj1, obj2, **hints): 37 | """Determine if relationship is allowed between two objects.""" 38 | 39 | # Allow any relation between two modelos that are both in the Example app. 40 | if obj1._meta.app_label == 'example' and obj2._meta.app_label == 'example': 41 | return True 42 | # No opinion if neither object is in the Example app (defer to default or other routers). 43 | elif 'example' not in [obj1._meta.app_label, obj2._meta.app_label]: 44 | return None 45 | 46 | # Block relationship if one object is in the Example app and the other isn't. 47 | return False 48 | 49 | def allow_migrate(self, db, app_label, model_name=None, **hints): 50 | """Ensure that the Example app's modelos get created on the right database.""" 51 | if app_label == 'example': 52 | # The Example app should be migrated only on the example_db database. 53 | return db == 'example_db' 54 | elif db == 'example_db': 55 | # Ensure that all other apps don't get migrated on the example_db database. 56 | return False 57 | 58 | # No opinion for all other scenarios 59 | return None 60 | -------------------------------------------------------------------------------- /project_name/lib/viewslibrary/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/lib/viewslibrary/__init__.py -------------------------------------------------------------------------------- /project_name/lib/viewslibrary/tests.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | 3 | from django import http 4 | 5 | 6 | class TestRequest(http.HttpRequest): 7 | """Just enough implementation to make it testable""" 8 | 9 | def __init__(self, uri, method='GET'): 10 | super(TestRequest, self).__init__() 11 | self.method = method 12 | _, self.host, self.path, self.query, _ = urllib.parse.urlsplit(uri) 13 | self.META['QUERY_STRING'] = self.query 14 | self.GET = http.QueryDict(self.query) 15 | 16 | def get_host(self): 17 | return self.host 18 | -------------------------------------------------------------------------------- /project_name/lib/viewslibrary/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import http 4 | from django.views.generic import FormView, UpdateView, CreateView, ListView 5 | from django.core.paginator import InvalidPage 6 | from django.core.urlresolvers import NoReverseMatch 7 | from django.core.urlresolvers import reverse, resolve 8 | from django.utils.functional import LazyObject 9 | from django.core.serializers.json import DjangoJSONEncoder 10 | from django.http.response import HttpResponse 11 | from lib.forms.generic import FormListView 12 | 13 | 14 | 15 | class AjaxTemplateMixin(object): 16 | """ Vary the template on request.is_ajax. 17 | TODO: Also attempt to call ajax_get() etc instead of the usual 18 | ones if they are present? 19 | """ 20 | 21 | def dispatch(self, request, **kwargs): 22 | if request.is_ajax(): 23 | self.template_name = self.ajax_template_name 24 | return super(AjaxTemplateMixin, self).dispatch(request, **kwargs) 25 | 26 | 27 | class AjaxLoadMoreMixin(object): 28 | """ Mixin for providing AJAX pagination facilities to any 29 | view that takes a "page" kwarg and provides a "page" variable in it's context data 30 | """ 31 | 32 | def get(self, request, **kwargs): 33 | response = super(AjaxLoadMoreMixin, self).get(request, **kwargs) 34 | if request.is_ajax(): 35 | json_data = {"content": response.render().content} 36 | 37 | try: 38 | next_page = response.context_data["page_obj"].next_page_number() 39 | if next_page: 40 | kwargs = dict(self.kwargs) 41 | kwargs["page"] = next_page 42 | try: 43 | json_data["next_url"] = reverse(resolve(request.path).url_name, args=self.args, kwargs=kwargs) 44 | except NoReverseMatch: 45 | # this results in there not being a Next button 46 | json_data["next_url"] = None 47 | except InvalidPage: 48 | pass 49 | # response = JsonResponse(json, status_code=200) 50 | return response 51 | 52 | 53 | class AjaxView(object): 54 | FORM_INVALID_HTTP_STATUS_CODE = 400 55 | ''' 56 | A specialised version of the Django FormView that provides default 57 | JSON responses for AJAX requests. 58 | ''' 59 | 60 | def form_valid(self, form): 61 | ''' 62 | Calls the appropriate handler depending on whether or not we're 63 | dealing with an AJAX request. If you would subclass this when 64 | using a normal FormView, you should subclass default_form_valid 65 | or ajax_form_valid instead. 66 | ''' 67 | if self.request.is_ajax(): 68 | return self.ajax_form_valid(form) 69 | else: 70 | return self.default_form_valid(form) 71 | 72 | def ajax_form_valid(self, form): 73 | ''' 74 | Handles AJAX submissions with valid data by responding with a 75 | 200 (OK) with a JSON body. 76 | ''' 77 | return JsonResponse( 78 | {'result': 'ok'}, 79 | status_code=200, 80 | ) 81 | 82 | def default_form_valid(self, form): 83 | ''' 84 | Handles non-AJAX submissions with valid data by calling FormView's 85 | implemenation of form_valid. 86 | ''' 87 | return super().form_valid(form) 88 | 89 | def form_invalid(self, form): 90 | ''' 91 | Calls the appropriate handler depending on whether or not we're 92 | dealing with an AJAX request. If you would subclass this when 93 | using a normal FormView, you should subclass default_form_invalid 94 | or ajax_form_invalid instead. 95 | ''' 96 | if self.request.is_ajax(): 97 | return self.ajax_form_invalid(form) 98 | else: 99 | return self.default_form_invalid(form) 100 | 101 | def ajax_form_invalid(self, form): 102 | ''' 103 | Handles AJAX submissions with invalid data and responds with a 104 | 400 (bad request) with a JSON body containing errors and the form's 105 | prefix. 106 | ''' 107 | return JsonResponse( 108 | { 109 | 'result': 'error', 110 | 'errors': form.errors, 111 | 'prefix': form.add_prefix(''), 112 | }, 113 | status_code=self.FORM_INVALID_HTTP_STATUS_CODE, 114 | ) 115 | 116 | def default_form_invalid(self, form): 117 | ''' 118 | Handles non-AJAX submissions with invalid data by calling FormView's 119 | implementation of form_invalid. 120 | ''' 121 | return super().form_invalid(form) 122 | 123 | 124 | class AjaxFormView(AjaxView, FormView): 125 | pass 126 | 127 | 128 | class AjaxUpdateView(AjaxView, UpdateView): 129 | pass 130 | 131 | 132 | class AjaxCreateView(AjaxView, CreateView): 133 | pass 134 | 135 | 136 | class AjaxListView(AjaxView, FormListView): 137 | pass 138 | 139 | 140 | class AjaxFormDataView(AjaxFormView): 141 | FORM_INVALID_HTTP_STATUS_CODE = 200 142 | 143 | def get_form_kwargs(self): 144 | if not self.request.is_ajax(): 145 | return super(AjaxFormDataView, self).get_form_kwargs() 146 | form_kwargs = super(AjaxFormDataView, self).get_form_kwargs() 147 | request_body = self.request.read() 148 | try: 149 | data = json.loads(request_body) 150 | except (ValueError, TypeError): 151 | data = {} 152 | form_kwargs['data'] = data 153 | return form_kwargs 154 | 155 | 156 | class JsonResponse(HttpResponse): 157 | 158 | def __init__(self, data, url=None, encoder=DjangoJSONEncoder, safe=True, 159 | json_dumps_params=None, **kwargs): 160 | if safe and not isinstance(data, dict): 161 | raise TypeError( 162 | 'In order to allow non-dict objects to be serialized set the ' 163 | 'safe parameter to False.' 164 | ) 165 | if json_dumps_params is None: 166 | json_dumps_params = {} 167 | kwargs.setdefault('content_type', 'application/json') 168 | data.update( 169 | {'url': url} 170 | ) 171 | data = json.dumps(data, cls=encoder, **json_dumps_params) 172 | super().__init__(content=data, **kwargs) 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /project_name/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", "{{ project_name }}.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /project_name/project_name/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/__init__.py -------------------------------------------------------------------------------- /project_name/project_name/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | from django.conf import settings 5 | 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings") 7 | 8 | 9 | CELERY_TIMEZONE = 'UTC' 10 | 11 | app = Celery('{{ project_name }}') 12 | app.config_from_object('django.conf:settings') 13 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 14 | -------------------------------------------------------------------------------- /project_name/project_name/databases.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 | 5 | BD_NON_DEFAULT = u'db_non_default' 6 | 7 | DATABASES = { 8 | 'default': { 9 | 'ENGINE': 'django.db.backends.sqlite3', 10 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 11 | }, 12 | # BD_NON_DEFAULT: { 13 | # 'ENGINE': 'django.db.backends.postgresql_psycopg2', 14 | # 'NAME': 'postgres', 15 | # 'USER': 'postgres', 16 | # 'PASSWORD': 'postgres', 17 | # 'HOST': 'postgres', 18 | # 'PORT': '5432', 19 | # } 20 | } 21 | -------------------------------------------------------------------------------- /project_name/project_name/development/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/development/__init__.py -------------------------------------------------------------------------------- /project_name/project_name/development/settings.py: -------------------------------------------------------------------------------- 1 | from {{ project_name }}.settings import * 2 | 3 | # To see outgoing email dumped to a terminal, uncomment the following and 4 | # run "python -m smtpd -n -c DebuggingServer localhost:1025" 5 | DEBUG = True 6 | 7 | DJANGOENV = 'development' 8 | 9 | try: 10 | import readline 11 | readline # make pyflakes happy, readline makes interactive mode keep history 12 | except ImportError: 13 | # no readline on Windows 14 | pass 15 | 16 | MIDDLEWARE += [ 17 | 'debug_toolbar.middleware.DebugToolbarMiddleware' 18 | ] 19 | 20 | INSTALLED_APPS += [ 21 | 'django_nose', 22 | 'django_extensions', 23 | 'debug_toolbar', 24 | 'template_repl', 25 | ] 26 | 27 | DJANGO_DEBUG_TOOLBAR = True 28 | SHOW_TOOLBAR_CALLBACK = True 29 | 30 | COMPRESS_ENABLED = False 31 | COMPRESS_OFFLINE = True 32 | 33 | BROKER_URL = 'amqp://rabbit_user:rabbit_user_default_pass@localhost:5672/' 34 | -------------------------------------------------------------------------------- /project_name/project_name/general/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/general/__init__.py -------------------------------------------------------------------------------- /project_name/project_name/general/redis_rabbitmq.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The setup for rabbitmq message broker (we can also user Redis for this) 3 | ''' 4 | import os 5 | from kombu import Exchange, Queue, serialization 6 | import djcelery 7 | from {{ project_name }}.utils import make_memcached_cache 8 | 9 | 10 | # REDIS 11 | 12 | SESSION_CACHE_ALIAS = "sessions" 13 | SESSION_ENGINE = "django.contrib.sessions.backends.cache" 14 | 15 | REDIS_HOSTNAME = 'localhost' 16 | REDIS_PORT = 6379 17 | REDIS_SERVER = (REDIS_HOSTNAME, REDIS_PORT, 0) # host, port, db 18 | REDIS_PASSWORD = '' 19 | REDIS_DB = 1 # keep cache entries in a different db so we can clear them easily 20 | REDIS_HOST = os.environ.get('REDIS_PORT_6379_TCP_ADDR', 'redis') 21 | 22 | 23 | CACHES = { 24 | "default": { 25 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 26 | }, 27 | "sessions": { 28 | "BACKEND": "redis_cache.cache.RedisCache", 29 | "LOCATION": 'redis:6379', 30 | "OPTIONS": { 31 | 'DB': 10, 32 | }, 33 | "KEY_PREFIX": "sessions" 34 | }, 35 | 'staticfiles': { 36 | 'BACKEND': 'redis_cache.cache.RedisCache', 37 | 'LOCATION': 'redis:6379', 38 | 'TIMEOUT': 86400 * 365, 39 | 'KEY_PREFIX': 'staticfiles', 40 | }, 41 | 'api': { 42 | 'BACKEND': 'redis_cache.cache.RedisCache', 43 | 'LOCATION': 'redis:6379', 44 | 'TIMEOUT': 86400 * 365, 45 | 'KEY_PREFIX': 'api', 46 | }, 47 | } 48 | 49 | 50 | # RABBITMQ 51 | RABBIT_HOSTNAME = os.environ.get('RABBIT_PORT_5672_TCP', 'rabbit') 52 | 53 | if RABBIT_HOSTNAME.startswith('tcp://'): 54 | RABBIT_HOSTNAME = RABBIT_HOSTNAME.split('//')[1] 55 | 56 | BROKER_URL = os.environ.get('BROKER_URL', '') 57 | if not BROKER_URL: 58 | BROKER_URL = 'amqp://{user}:{password}@{hostname}/{vhost}/'.format( 59 | user=os.environ.get('RABBIT_ENV_USER', 'rabbit_user'), 60 | password=os.environ.get('RABBIT_ENV_RABBITMQ_PASS', 'rabbit_user_default_pass'), 61 | hostname=RABBIT_HOSTNAME, 62 | vhost=os.environ.get('RABBIT_ENV_VHOST', '')) 63 | 64 | 65 | # We don't want to have dead connections stored on rabbitmq, so we have to negotiate using heartbeats 66 | BROKER_HEARTBEAT = '?heartbeat=30' 67 | if not BROKER_URL.endswith(BROKER_HEARTBEAT): 68 | BROKER_URL += BROKER_HEARTBEAT 69 | 70 | BROKER_POOL_LIMIT = 1 71 | BROKER_CONNECTION_TIMEOUT = 10 72 | 73 | 74 | # CELERY CONFIGURATION 75 | # CONFIGURE QUEUES, CURRENTLY WE HAVE ONLY ONE 76 | CELERY_CREATE_MISSING_QUEUES = True 77 | CELERY_RESULT_PERSISTENT = True 78 | 79 | CELERY_DEFAULT_QUEUE = 'default' 80 | CELERY_QUEUES = ( 81 | Queue('default', Exchange('default'), routing_key='default'), 82 | Queue('{{ project_name }}', Exchange('{{ project_name }}'), routing_key='{{ project_name }}'), 83 | ) 84 | 85 | 86 | # SENSIBLE SETTINGS FOR CELERY 87 | CELERY_ALWAYS_EAGER = False 88 | CELERY_ACKS_LATE = True 89 | CELERY_TASK_PUBLISH_RETRY = True 90 | CELERY_DISABLE_RATE_LIMITS = False 91 | 92 | # BY DEFAULT WE WILL IGNORE RESULT 93 | # IF YOU WANT TO SEE RESULTS AND TRY OUT TASKS INTERACTIVELY, CHANGE IT TO FALSE 94 | # OR CHANGE THIS SETTING ON TASKS LEVEL 95 | CELERY_IGNORE_RESULT = True 96 | CELERY_SEND_TASK_ERROR_EMAILS = False 97 | CELERY_TASK_RESULT_EXPIRES = 600 98 | 99 | # SET REDIS AS CELERY RESULT BACKEND 100 | CELERY_RESULT_BACKEND = 'redis://%s:%d/%d' % (REDIS_HOSTNAME, REDIS_PORT, REDIS_DB) 101 | CELERY_REDIS_MAX_CONNECTIONS = 1 102 | 103 | # DON'T USE PICKLE AS SERIALIZER, JSON IS MUCH SAFER 104 | CELERY_ACCEPT_CONTENT = ['json'] 105 | CELERY_TASK_SERIALIZER = 'json' 106 | CELERY_RESULT_SERIALIZER = 'json' 107 | 108 | CELERYD_HIJACK_ROOT_LOGGER = False 109 | CELERYD_PREFETCH_MULTIPLIER = 1 110 | CELERYD_MAX_TASKS_PER_CHILD = 1000 111 | 112 | 113 | # ACTIVATE SETTINGS 114 | djcelery.setup_loader() 115 | -------------------------------------------------------------------------------- /project_name/project_name/general/settings.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext_lazy 2 | 3 | LOGIN_REDIRECT_URL = '/' 4 | LOGOUT_REDIRECT_URL = '/' 5 | LOGIN_URL = '/login/' 6 | 7 | CANONICAL_SITE_URL = 'www.{{ project_name }}.com' 8 | 9 | AUTH_PROFILE_MODULE = 'profiles.profile' 10 | ABSOLUTE_URL_OVERRIDES = { 11 | 'auth.user': lambda u: '/user/%i/' % u.profile.pk, 12 | } 13 | 14 | # Sessions 15 | POSTGRES_HOSTNAME = 'localhost' 16 | 17 | # LANGUAGES 18 | LANGUAGE_CODE = 'en-gb' 19 | 20 | LANGUAGES = ( 21 | ('en-us', ugettext_lazy('American English')), 22 | ('en-gb', ugettext_lazy('British English')), 23 | ) 24 | 25 | # Pseudolanguage is used for Enabling Crowdin translation mode 26 | PSEUDO_LANGUAGE = 'zu' 27 | VISIBLE_LANGUAGES = tuple(x for x in LANGUAGES if x[0] != PSEUDO_LANGUAGE) 28 | 29 | LOGGING = { 30 | "version": 1, 31 | "disable_existing_loggers": False, 32 | 'formatters': { 33 | 'json': { 34 | '()': 'pythonjsonlogger.jsonlogger.JsonFormatter', 35 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 36 | }, 37 | 'verbose': { 38 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 39 | }, 40 | }, 41 | "handlers": { 42 | 'bugsnag': { 43 | 'level': 'ERROR', 44 | 'class': 'bugsnag.handlers.BugsnagHandler', 45 | }, 46 | 'console': { 47 | 'level': 'INFO', 48 | 'class': 'logging.StreamHandler', 49 | 'formatter': 'json', 50 | }, 51 | 'investigation': { 52 | "level": "INFO", 53 | "class": "bugsnag.handlers.BugsnagHandler" 54 | } 55 | }, 56 | "loggers": { 57 | "django.request": { 58 | "level": "ERROR", 59 | "propagate": True, 60 | }, 61 | "celery": { 62 | "handlers": ["console", "bugsnag"], 63 | "level": "INFO", 64 | "propagate": True 65 | }, 66 | "httpstream": { 67 | "handlers": ["console", "bugsnag"], 68 | "level": "ERROR", 69 | "propagate": True 70 | }, 71 | "elasticsearch": { 72 | "handlers": ["console", "bugsnag"], 73 | "level": "WARNING", 74 | "propagate": False 75 | }, 76 | "urllib3": { 77 | "handlers": ["console", "bugsnag"], 78 | "level": "WARNING", 79 | "propagate": False 80 | }, 81 | "celery.investigation": { 82 | "level": "INFO", 83 | "handlers": ["investigation"], 84 | "propagate": False 85 | } 86 | 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /project_name/project_name/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from {{ project_name }}.databases import * 4 | from {{ project_name }}.general.settings import * 5 | from {{ project_name }}.third_parties.blacklist_domains import * 6 | from {{ project_name }}.general.redis_rabbitmq import * 7 | 8 | SITE_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 9 | {{ project_name }}_VERSION = os.path.basename(os.path.dirname(SITE_ROOT)) 10 | DJANGOENV = None 11 | 12 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 13 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 18 | 19 | # IMPORTANT!: 20 | # You must keep this secret, you can store it in an 21 | # environment variable and set it with: 22 | # export SECRET_KEY="secret-export!key-781" 23 | # https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/#secret-key 24 | SECRET_KEY = "secret_change_this_after" 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = False 28 | 29 | ALLOWED_HOSTS = ['*'] 30 | 31 | # Application definition 32 | SITE_ID = 1 33 | 34 | BASE_INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'django.contrib.sites', 42 | 'django.contrib.humanize', 43 | 'django.contrib.sitemaps', 44 | 'django.contrib.redirects', 45 | 'djcelery', 46 | 'lib.common', 47 | 'lib.audit', 48 | 'lib.cache', 49 | 'lib.viewslibrary', 50 | 'statici18n', 51 | 'compressor', 52 | 'rest_framework', 53 | 'channels', 54 | ] 55 | 56 | INSTALLED_APPS = [ 57 | 'accounts', 58 | 'api', 59 | ] + BASE_INSTALLED_APPS 60 | 61 | 62 | MIDDLEWARE = [ 63 | 'django.middleware.security.SecurityMiddleware', 64 | 'django.contrib.sessions.middleware.SessionMiddleware', 65 | 'django.middleware.common.CommonMiddleware', 66 | 'django.middleware.csrf.CsrfViewMiddleware', 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 68 | 'django.contrib.messages.middleware.MessageMiddleware', 69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 70 | ] 71 | 72 | REST_FRAMEWORK = { 73 | # Use Django's standard `django.contrib.auth` permissions, 74 | # or allow read-only access for unauthenticated users. 75 | 'DEFAULT_PERMISSION_CLASSES': [ 76 | 'rest_framework.authentication.BasicAuthentication', 77 | 'rest_framework.authentication.SessionAuthentication', 78 | 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', 79 | 80 | ], 81 | 'DEFAULT_RENDERER_CLASSES': ( 82 | 'rest_framework.renderers.JSONRenderer', 83 | ), 84 | # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 85 | # 'PAGE_SIZE': 20 86 | } 87 | 88 | 89 | ROOT_URLCONF = '{{ project_name }}.urls' 90 | 91 | TEMPLATES = [ 92 | { 93 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 94 | 'DIRS': [ 95 | os.path.join(SITE_ROOT, 'templates'), 96 | os.path.join(SITE_ROOT, 'static'), 97 | ], 98 | 'APP_DIRS': False, 99 | 'OPTIONS': { 100 | 'context_processors': [ 101 | 'django.template.context_processors.debug', 102 | 'django.template.context_processors.request', 103 | 'django.contrib.auth.context_processors.auth', 104 | 'django.contrib.messages.context_processors.messages', 105 | 'django.template.context_processors.i18n', 106 | 'django.template.context_processors.csrf', 107 | 108 | ], 109 | 'loaders': [ 110 | 'django.template.loaders.filesystem.Loader', 111 | 'django.template.loaders.app_directories.Loader', 112 | 'django.template.loaders.eggs.Loader', 113 | ], 114 | 'debug': DEBUG, 115 | }, 116 | }, 117 | ] 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | 124 | STATICFILES_DIRS = [] 125 | 126 | STATICFILES_STORAGE = 'lib.storage.CachedStaticFilesStorage' 127 | 128 | STATICFILES_FINDERS = ( 129 | 'django.contrib.staticfiles.finders.FileSystemFinder', 130 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 131 | 'compressor.finders.CompressorFinder', 132 | ) 133 | 134 | # Statici18n Config 135 | STATICI18N_ROOT = os.path.join(SITE_ROOT, 'static') 136 | 137 | STATICFILES_DIRS += [STATICI18N_ROOT] 138 | 139 | STATIC_ROOT = os.path.join(SITE_ROOT, 'staticfiles') 140 | 141 | LOCALE_PATHS = [os.path.join(SITE_ROOT, 'locale')] 142 | 143 | COMPRESS_ROOT = '/tmp/pr-static' 144 | COMPRESS_ENABLED = True 145 | 146 | WSGI_APPLICATION = '{{ project_name }}.wsgi.application' 147 | 148 | # Password validation 149 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 150 | 151 | AUTH_PASSWORD_VALIDATORS = [ 152 | { 153 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 154 | }, 155 | { 156 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 157 | }, 158 | { 159 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 160 | }, 161 | { 162 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 163 | }, 164 | ] 165 | 166 | AUTH_USER_MODEL = 'accounts.User' 167 | 168 | # Internationalization 169 | # https://docs.djangoproject.com/en/1.11.4/topics/i18n/ 170 | 171 | LANGUAGE_CODE = 'en-us' 172 | 173 | INTERNAL_IPS = ('127.0.0.1', '192.168.56.1', '10.0.2.2',) 174 | 175 | TIME_ZONE = 'UTC' 176 | 177 | USE_I18N = True 178 | 179 | USE_L10N = True 180 | 181 | USE_TZ = True 182 | 183 | 184 | # Pull slug max_length out ot 185 | SLUG_MAX_LENGTH = 64 186 | 187 | LOGIN_REDIRECT_URL = '/' 188 | LOGOUT_REDIRECT_URL = '/' 189 | -------------------------------------------------------------------------------- /project_name/project_name/staging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/staging/__init__.py -------------------------------------------------------------------------------- /project_name/project_name/staging/settings.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/staging/settings.py -------------------------------------------------------------------------------- /project_name/project_name/testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/testing/__init__.py -------------------------------------------------------------------------------- /project_name/project_name/testing/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | testing.settings will pull in (probably global) local_settings, 3 | This is a special thanks to David Dyball for helping me understand and build something very familiar to me 4 | in terms of settings and how to set them up 5 | 6 | To all who contribute for this, thank you very much. 7 | """ 8 | from {{ project_name }}.settings import * 9 | import os 10 | 11 | ''' 12 | If you are using windows by default, the permissions to access subfolders for tests are disabled 13 | Activate them using NOSE_INCLUDE_EXE = 1 or an environment variable in your OS with the same name and value 14 | ''' 15 | NOSE_INCLUDE_EXE = 1 16 | ''' 17 | Other settings 18 | ''' 19 | DEBUG = True 20 | TESTING = True 21 | 22 | ''' 23 | Tells the django environment 24 | ''' 25 | DJANGOENV = "testing" 26 | 27 | REUSE_DB = bool(int(os.environ.get("REUSE_DB", 0))) 28 | 29 | DATABASES = { 30 | 'default': { 31 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 32 | 'NAME': 'postgres', 33 | 'USER': 'postgres', 34 | 'PASSWORD': 'postgres', 35 | 'HOST': 'postgres', 36 | 'PORT': '5432', 37 | } 38 | } 39 | 40 | if REUSE_DB: 41 | DATABASE_ROUTERS = [] 42 | 43 | POSTGIS_TEMPLATE = "template_postgis" 44 | POSTGIS_VERSION = (2, 1, 2) 45 | 46 | SOUTH_TESTS_MIGRATE = bool(int(os.environ.get("MIGRATE", 0))) 47 | 48 | # If you have Django Debug Toolbar installed 49 | DEBUG_TOOLBAR_PANELS = () 50 | 51 | # Disable the Secure SSL Redirect (special thanks to @DD) 52 | SECURE_SSL_REDIRECT = False 53 | 54 | # Use this if you have local_settings.pt file 55 | DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" 56 | MEDIA_URL = '/media/' 57 | STATIC_ROOT = '/tmp/assets-upload' 58 | STATIC_URL = "/static/" 59 | MEDIA_ROOT = '/tmp/media-root' 60 | 61 | # Roll in the API URLs so we can test everything in one go 62 | API_URLS_IN_MAIN = True 63 | 64 | # nosetests - NoseTestSuiteRunner 65 | TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' 66 | 67 | # Args passed to nose 68 | NOSE_ARGS = ["--logging-clear-handlers", "--logging-filter", "INFO"] 69 | 70 | # Give ourselves a test instance of redis (another special thanks to @DD for this) 71 | REDIS_SERVER = ('redis', 6379, 2) # host, port, dbs 72 | REDIS_PASSWORD = None 73 | 74 | # We don't want to run Memcached for tests. 75 | SESSION_ENGINE = "django.contrib.sessions.backends.db" 76 | 77 | 78 | CACHES = { 79 | 'default': { 80 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 81 | }, 82 | 'api': { 83 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 84 | }, 85 | 'staticfiles': { 86 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 87 | }, 88 | 'depictions': { 89 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 90 | }, 91 | 'thumbnails': { 92 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 93 | }, 94 | } 95 | 96 | # allow for a local override that won't be used by development 97 | try: 98 | from {{ project_name }}.{{ project_name }}.testing.local_settings import * 99 | except ImportError: 100 | pass 101 | 102 | 103 | # We don't care about secure password for tests, use MD5 which is faster. 104 | PASSWORD_HASHERS = ( 105 | 'django.contrib.auth.hashers.MD5PasswordHasher', 106 | # This one is only needed for some of the old fixtures. 107 | 'django.contrib.auth.hashers.SHA1PasswordHasher', 108 | ) 109 | 110 | 111 | LOGGING = { 112 | "version": 1, 113 | "disable_existing_loggers": True, 114 | 'root': { 115 | 'level': 'INFO', 116 | 'handlers': ['console'], 117 | }, 118 | 'formatters': { 119 | 'verbose': { 120 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 121 | }, 122 | }, 123 | "handlers": { 124 | 'console': { 125 | 'level': 'DEBUG', 126 | 'class': 'logging.StreamHandler', 127 | 'formatter': 'verbose' 128 | }, 129 | } 130 | } 131 | 132 | 133 | MIDDLEWARE = list(MIDDLEWARE) 134 | if 'bugsnag.django.middleware.BugsnagMiddleware' in MIDDLEWARE: 135 | MIDDLEWARE.remove('bugsnag.django.middleware.BugsnagMiddleware') 136 | 137 | COMPRESS_ENABLED = False 138 | -------------------------------------------------------------------------------- /project_name/project_name/third_parties/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarsil/django-boilerplate/35ed124d3290417037dcb382936eebe62dd2f13f/project_name/project_name/third_parties/__init__.py -------------------------------------------------------------------------------- /project_name/project_name/third_parties/blacklist_domains.py: -------------------------------------------------------------------------------- 1 | BLACKLISTED_DOMAINS = { 2 | "2rainmail.org.uk", 3 | "armyspy.com", 4 | "barchor.org.uk", 5 | "cannotmail.org.uk", 6 | "cool.fr.nf", 7 | "courriel.fr.nf", 8 | "crymet.org.uk", 9 | "cuvox.de", 10 | "dayrep.com", 11 | "drecom01.co.uk", 12 | "eelmail.com", 13 | "einrot.com", 14 | "example.com", 15 | "europe.eu", 16 | "europe.com", 17 | "fleckens.hu", 18 | "freggnet.co.uk", 19 | "gamil.com", 20 | "gmail.con", 21 | "gmail.co.uk", 22 | "gmail.co", 23 | "gmil.com", 24 | "gmail.pt", 25 | "gmil.pt", 26 | "grr.la", 27 | "guerrillamail.biz", 28 | "guerrillamail.com", 29 | "guerrillamail.de", 30 | "guerrillamail.net", 31 | "guerrillamail.org", 32 | "guerrillamailblock.com", 33 | "gustr.com", 34 | "homail.co.uk", 35 | "homail.com", 36 | "hotmail.co", 37 | "hoodmail.co.uk", 38 | "jetable.fr.nf", 39 | "jourrapide.com", 40 | "kreahnet.org.uk", 41 | "lonynet.org.uk", 42 | "mail.cat", 43 | "mail.fr", 44 | "mailbreaker.co.uk", 45 | "mailinator.com", 46 | "mega.zik.dj", 47 | "moncourrier.fr.nf", 48 | "monemail.fr.nf", 49 | "monmail.fr.nf", 50 | "moussenetmail.co.uk", 51 | "mywheelboxmail.org.uk", 52 | "nomail.xl.cx", 53 | "nospam.ze.tc", 54 | "pluntermail.org.uk", 55 | "prainnet.org.uk", 56 | "rackernet.org.uk", 57 | "railosnet.co.uk", 58 | "rhyta.com", 59 | "rottmail.co.uk", 60 | "runracemail.org.uk", 61 | "runwaynet.org.uk", 62 | "sharklasers.com", 63 | "sherrymail.co.uk", 64 | "shortsmail.co.uk", 65 | "spam.co.uk", 66 | "spam.com", 67 | "spam4.me", 68 | "speed.1s.fr", 69 | "stonetimenet.co.uk", 70 | "superrito.com", 71 | "teleworm.us", 72 | "telph1line.org.uk", 73 | "test.be", 74 | "test.co", 75 | "test.com", 76 | "test.de", 77 | "test.eu", 78 | "test.it", 79 | "test.fr", 80 | "test.nl", 81 | "test.net", 82 | "test.pl", 83 | "threemailnet.co.uk", 84 | "trbvm.com", 85 | "tyermail.org.uk", 86 | "wonandron.co.uk", 87 | "wormail.co.uk", 88 | "ya.com", 89 | "yopmail.com", 90 | "yopmail.fr", 91 | "yopmail.net", 92 | } -------------------------------------------------------------------------------- /project_name/project_name/urls.py: -------------------------------------------------------------------------------- 1 | """{{ project_name }} URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | from accounts.views import LoginView, LogoutView, HomepageView 19 | import accounts.urls 20 | import api.urls 21 | 22 | admin.autodiscover() 23 | 24 | urlpatterns = [ 25 | url(r'^admin/', admin.site.urls), 26 | url(r'^$', HomepageView.as_view(), name=('homepage')), 27 | url(r'^login/$', LoginView.as_view(), name=('login')), 28 | url(r'^logout/$', LogoutView.as_view(), name=('logout')), 29 | url(r'^u/', include(accounts.urls.accounts_urlpatterns, namespace="accounts")), 30 | 31 | # API 32 | url(r'^api/v1/', include(api.urls.api_url_pattern, namespace="api-v1")), 33 | ] 34 | -------------------------------------------------------------------------------- /project_name/project_name/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for {{ project_name }} 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.11/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", "{{ project_name }}.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /project_name/static/css/project_name.css: -------------------------------------------------------------------------------- 1 | .log-in-form { 2 | border: 1px solid $medium-gray; 3 | padding: $form-spacing; 4 | border-radius: $global-radius; 5 | } -------------------------------------------------------------------------------- /project_name/templates/project_name/application/core.html: -------------------------------------------------------------------------------- 1 | {% templatetag openblock %}load static from staticfiles {% templatetag closeblock %} 2 | {% templatetag openblock %}load statici18n {% templatetag closeblock %} 3 | {% templatetag openblock %}load i18n {% templatetag closeblock %} 4 | 5 | 6 | 7 | 8 | 9 | 10 |