diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3ac8c47 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +# http://editorconfig.org +# from https://github.com/pydanny/cookiecutter-django + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{py,rst,ini}] +indent_style = space +indent_size = 4 + +[*.{html,css,scss,json,yml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab + +[nginx.conf] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index 2b00f1b..b63e4c5 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ ENV/ # SQLite3 db.sqlite3 + +# IDE +.idea/ diff --git a/README.md b/README.md index a90f2e3..2f11ca0 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,24 @@ This repo is functionality complete — PR's and issues welcome! + +## Prerequisites + +* Python 3.4+ +* Git +* Shell prompt ([Git BASH for windows](https://gitforwindows.org/) recommended for windows) + + ## Installation -1. Clone this repository: `git clone git@github.com:gothinkster/productionready-django-api.git`. -2. `cd` into `conduit-django`: `cd productionready-django-api`. -3. Install [pyenv](https://github.com/yyuu/pyenv#installation). -4. Install [pyenv-virtualenv](https://github.com/yyuu/pyenv-virtualenv#installation). -5. Install Python 3.5.2: `pyenv install 3.5.2`. -6. Create a new virtualenv called `productionready`: `pyenv virtualenv 3.5.2 productionready`. -7. Set the local virtualenv to `productionready`: `pyenv local productionready`. -8. Reload the `pyenv` environment: `pyenv rehash`. +1. Clone this repository: `git clone git@github.com:gothinkster/django-realworld-example-app.git`. +2. `cd` into `django-realworld-example-app`. +3. Bootstrap the environment `bin/bootstrap.sh` + +Django local dev server should now be serving on port 4000 -If all went well then your command line prompt should now start with `(productionready)`. -If your command line prompt does not start with `(productionready)` at this point, try running `pyenv activate productionready` or `cd ../productionready-django-api`. +#### Notes -If pyenv is still not working, visit us in the Thinkster Slack channel so we can help you out. +* `python` should be Python 3.4+, check using `python --version`. To be explicit, if on Linux/MacOS you may have a `python3.5` or `python3.6` on your path. +* If using windows use `venv\Scripts\python` instead of `venv/bin/python` diff --git a/TODO b/TODO new file mode 100644 index 0000000..7c99535 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +TODO namespace APIs into apps/api/ +TODO add tests (prefer unittest for now) +TODO rm django-extensions (not used?) +TODO marry CORS_ORIGIN_WHITELIST with ALLOW_HOSTS +TODO form opinion on dev/staging/prod settings split (perfer common.py pattern with some settings imported from ini at root) +TODO move config to root https://github.com/agconti/cookiecutter-django-rest/pull/571/files diff --git a/bin/bootstrap.sh b/bin/bootstrap.sh new file mode 100755 index 0000000..98becaa --- /dev/null +++ b/bin/bootstrap.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -e # fail fast + +# bootstraps to django dev server from start to finish + +CWD="$(cd "$(dirname "$0")" && pwd)" +pushd $CWD/.. + +DJANGO_PORT='4000' + +function create_venv() { + echo "Creating venv/ using system $(python3 --version) ..." + python3 -m venv venv/ --clear + + echo "Upgrading packaging tools in venv..." + venv/bin/pip install --upgrade --ignore-installed pip + venv/bin/pip install --upgrade setuptools wheel +} + +function install_app() { + echo "Installing conduit from setup.py ..." + venv/bin/pip install -e . +} + +function migrate() { + rm -rf *.sqlite3 + bin/django.sh migrate + echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'admin')" | bin/django.sh shell +} + +function collectstatic() { + bin/django.sh collectstatic --link --noinput +} + +function runserver() { + echo "Starting dev server, login to http://$(hostname -I | cut -d' ' -f1):${DJANGO_PORT} with admin@example.com/admin" + bin/django.sh runserver 0.0.0.0:${DJANGO_PORT} +} + +function main() { + create_venv + install_app + migrate +# collectstatic + runserver +} + +if [ $# -eq 0 ]; then + main +elif [ $# -eq 1 ]; then + "$@" # call func by name ie. $ ./bootstrap.sh runserver +fi diff --git a/bin/django.sh b/bin/django.sh new file mode 100755 index 0000000..8eb5824 --- /dev/null +++ b/bin/django.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e # fail fast + +CWD="$(cd "$(dirname "$0")" && pwd)" +pushd $CWD/.. + +venv/bin/python manage.py $1 ${@:2} diff --git a/bin/regen_requirements.sh b/bin/regen_requirements.sh new file mode 100755 index 0000000..ed21ffb --- /dev/null +++ b/bin/regen_requirements.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Use this script to regenerate requirements.txt from packages installed in venv/ using pipdeptree's nice tree layout + +CWD="$(cd "$(dirname "$0")" && pwd)" +pushd $CWD/.. + +echo "Generating new requirements.txt ..." +venv/bin/pipdeptree --local-only --freeze --exclude pip,setuptools,wheel,conduit > requirements.txt + +popd diff --git a/conduit/apps/core/admin.py b/conduit/apps/core/admin.py new file mode 100644 index 0000000..d77aaa6 --- /dev/null +++ b/conduit/apps/core/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin +from django.db.models.fields.related import ManyToManyField + + +def auto_register(model): + """ Auto-register all models incl Django and 3rd party models (obviously not for production) + + Based on: http://technowhisp.com/2017/08/13/django/auto-registering-models-in-django-admin/ + """ + # Get all fields from model, but exclude autocreated reverse relations and ManyToManyField + field_list = [f.name for f in model._meta.get_fields() if f.auto_created == False and not isinstance(f, ManyToManyField)] + # Dynamically create ModelAdmin class and register it. + my_admin = type('MyAdmin', (admin.ModelAdmin,), + {'list_display': field_list} + ) + try: + admin.site.register(model, my_admin) + except admin.sites.AlreadyRegistered: + # This model is already registered + pass + + +from django.apps import apps + +for model in apps.get_models(): + auto_register(model) diff --git a/conduit/settings.py b/conduit/settings.py index 17b2df7..bd8d9d6 100644 --- a/conduit/settings.py +++ b/conduit/settings.py @@ -11,6 +11,7 @@ """ import os +import socket # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -25,8 +26,30 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +def get_ip(): + """ Cross-platform method of getting primary internal IP + https://stackoverflow.com/a/28950776/2819573 + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except: + IP = '127.0.0.1' + finally: + s.close() + return IP + +INTERNAL_IP = get_ip() + +ALLOWED_HOSTS = [ + INTERNAL_IP, + '.lvh.me', + 'localhost', + '0.0.0.0' +] # Application definition @@ -132,6 +155,7 @@ CORS_ORIGIN_WHITELIST = ( '0.0.0.0:4000', 'localhost:4000', + INTERNAL_IP + ':4000', ) # Tell Django about the custom `User` model we created. The string diff --git a/requirements.txt b/requirements.txt index 6eb9d54..8a76549 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ -Django==1.10.5 +Django==1.11.14 + pytz==2018.5 django-cors-middleware==1.3.1 django-extensions==1.7.1 -djangorestframework==3.4.4 -PyJWT==1.4.2 -six==1.10.0 + six==1.10.0 +djangorestframework==3.8.2 +pipdeptree==0.13.0 +PyJWT==1.6.4 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ea09fac --- /dev/null +++ b/setup.py @@ -0,0 +1,48 @@ +import os + +from setuptools import find_packages, setup + +CWD = os.path.dirname(__file__) # FIXME __file__ is not freeze-proof + +with open(os.path.join(CWD, 'README.md')) as readme: + README = readme.read() + +with open(os.path.join(CWD, 'requirements.txt')) as f: + install_reqs = [ + s for s in [ + line.strip(' \n') for line in f + ] if not s.startswith('#') and s != '' + ] + +# allow setup.py to be run from any path +os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) + +setup( + name='conduit', # FYI this clashes with an existing package on PYPI! + version='0.1', + packages=find_packages(), + include_package_data=True, + license='BSD License', # example license + description='Example Django DRF codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld API spec.', + long_description=README, + url='https://github.com/gothinkster/realworld-example-apps', + author='Your Name', + author_email='yourname@example.com', + install_requires=install_reqs, + classifiers=[ + 'Environment :: Web Environment', + 'Framework :: Django', + 'Framework :: Django :: 1.11', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], +)