diff options
-rw-r--r-- | .dockerignore | 42 | ||||
-rw-r--r-- | .flake8 | 2 | ||||
-rw-r--r-- | .gitlab-ci.yml | 157 | ||||
-rw-r--r-- | .hadolint.yaml | 4 | ||||
-rw-r--r-- | .mdlrc | 1 | ||||
-rw-r--r-- | CHANGELOG.md | 39 | ||||
-rw-r--r-- | CONTRIBUTING.md | 74 | ||||
l---------[-rw-r--r--] | Dockerfile | 19 | ||||
-rw-r--r-- | Pipfile | 45 | ||||
-rw-r--r-- | Pipfile.lock | 306 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | SETUP.md | 11 | ||||
-rw-r--r-- | admin/__init__.py | 0 | ||||
-rw-r--r-- | admin/urls.py | 7 | ||||
-rw-r--r-- | api/admin.py | 15 | ||||
-rw-r--r-- | api/migrations/0006_add_help_texts.py | 44 | ||||
-rw-r--r-- | api/models.py | 102 | ||||
-rw-r--r-- | api/tests/test_models.py | 43 | ||||
-rw-r--r-- | docker-compose.yml | 34 | ||||
-rw-r--r-- | docker/app/alpine/3.6/Dockerfile | 28 | ||||
-rw-r--r-- | docker/app/alpine/3.7/Dockerfile | 28 | ||||
-rw-r--r-- | docker/app/stretch/3.6/Dockerfile | 33 | ||||
-rw-r--r-- | docker/app/stretch/3.7/Dockerfile | 33 | ||||
-rw-r--r-- | docker/app/uwsgi.ini | 32 | ||||
-rw-r--r-- | docker/nging/Dockerfile | 18 | ||||
-rw-r--r-- | docs/README.md | 13 | ||||
-rw-r--r-- | docs/setup.md | 71 | ||||
-rw-r--r-- | home/urls.py | 4 | ||||
-rwxr-xr-x | manage.py | 8 | ||||
-rw-r--r-- | pysite/hosts.py | 4 | ||||
-rw-r--r-- | pysite/settings.py | 45 | ||||
-rw-r--r-- | pysite/urls.py | 6 | ||||
-rw-r--r-- | setup.py | 47 |
33 files changed, 744 insertions, 580 deletions
diff --git a/.dockerignore b/.dockerignore index b2eb2073..55fb0e6d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,16 +1,38 @@ -.venv -scripts -htmlcov -__pycache__ -.vagrant -.pytest_cache -.git -.github -.gitlab .cache -Vagrantfile .coverage .coveragerc +.git +.github .gitignore +.gitlab +.pytest_cache .travis.yml +.vagrant +.venv +__pycache__ +admin/migrations +admin/tests +admin/tests.py +api/migrations +api/tests +api/tests.py +CHANGELOG.md +CONTRIBUTING.md docker +!docker/app/uwsgi.ini +docker-compose.yml +Dockerfile +docs +home/migrations +home/tests +home/tests.py +htmlcov +LICENSE +pysite.egg-info +README.md +scripts +Vagrantfile +venv +wiki/migrations +wiki/tests +wiki/tests.py @@ -1,5 +1,5 @@ [flake8] max-line-length=100 -application_import_names=pysite +application_import_names=admin,api,home,pysite,wiki exclude=__pycache__, venv, .venv, **/migrations import-order-style=pycharm diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index babb9b71..dfc69d29 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,26 +1,19 @@ stages: + - build - lint - test + - publish - deploy -image: python:3.7-alpine - -cache: - paths: - - .cache/ - variables: - PIPENV_CACHE_DIR: "$CI_PROJECT_DIR/.cache" - PIPENV_HIDE_EMOJIS: 1 - PIPENV_IGNORE_VIRTUALENVS: 1 - PIPENV_MAX_SUBPROCESS: 2 - PIPENV_NOSPIN: 1 - PIPENV_VENV_IN_PROJECT: 1 + BASE_IMAGE_URL: registry.gitlab.com/python-discord/projects/site/django-ci .test-template: &test-template stage: test services: - - postgres:10-alpine + - postgres:11-alpine + before_script: + - python manage.py migrate script: - python manage.py test tags: @@ -30,25 +23,94 @@ variables: POSTGRES_DB: pysite POSTGRES_PASSWORD: supersecret POSTGRES_USER: django + SECRET_KEY: supersecret -lint: - stage: lint +build-alpine-3.7: + stage: build + image: docker:dind + before_script: + - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY + script: + - > + docker build + --build-arg EXTRAS=test,lint + -t $BASE_IMAGE_URL:alpine-3.7-$CI_COMMIT_REF_SLUG + -f docker/app/alpine/3.7/Dockerfile + . + - docker push $BASE_IMAGE_URL:alpine-3.7-$CI_COMMIT_REF_SLUG + +build-alpine-3.6: + stage: build + image: docker:dind + before_script: + - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY + script: + - > + docker build + --build-arg EXTRAS=test,lint + -t $BASE_IMAGE_URL:alpine-3.6-$CI_COMMIT_REF_SLUG + -f docker/app/alpine/3.6/Dockerfile + . + - docker push $BASE_IMAGE_URL:alpine-3.6-$CI_COMMIT_REF_SLUG + +build-stretch-3.7: + stage: build + image: docker:dind + before_script: + - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY + script: + - > + docker build + --build-arg EXTRAS=test,lint + -t $BASE_IMAGE_URL:stretch-3.7-$CI_COMMIT_REF_SLUG + -f docker/app/stretch/3.7/Dockerfile + . + - docker push $BASE_IMAGE_URL:stretch-3.7-$CI_COMMIT_REF_SLUG + +build-stretch-3.6: + stage: build + image: docker:dind before_script: - - apk add python3-dev git libpq postgresql-dev gcc cmake autoconf automake musl-dev - - python3 -m pip install pipenv - - pipenv install --dev --system + - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY + script: + - > + docker build + --build-arg EXTRAS=test,lint + -t $BASE_IMAGE_URL:stretch-3.6-$CI_COMMIT_REF_SLUG + -f docker/app/stretch/3.6/Dockerfile + . + - docker push $BASE_IMAGE_URL:stretch-3.6-$CI_COMMIT_REF_SLUG + +lint-python: + stage: lint + image: $BASE_IMAGE_URL:alpine-3.7-$CI_COMMIT_REF_SLUG script: - flake8 tags: - docker +lint-docker: + stage: lint + image: hadolint/hadolint:latest-debian + script: + - hadolint docker/**/**/**/Dockerfile + tags: + - docker + +lint-markdown: + stage: lint + image: ruby:2.5-alpine + before_script: + - gem install mdl + script: + - mdl *.md **/*.md + tags: + - docker + test-3.7-alpine: <<: *test-template - image: python:3.7-alpine + image: $BASE_IMAGE_URL:alpine-3.7-$CI_COMMIT_REF_SLUG before_script: - - apk add python3-dev git libpq postgresql-dev gcc cmake autoconf automake musl-dev - - python3 -m pip install pipenv - - pipenv install --dev --system - python manage.py migrate script: - coverage run --source=api,home,pysite,wiki --branch manage.py test @@ -60,39 +122,19 @@ test-3.7-alpine: test-3.6-alpine: <<: *test-template - image: python:3.6-alpine - before_script: - - apk add python3-dev git libpq postgresql-dev gcc cmake autoconf automake musl-dev - - python3 -m pip install pipenv - - pipenv install --system - - python manage.py migrate + image: $BASE_IMAGE_URL:alpine-3.6-$CI_COMMIT_REF_SLUG test-3.7-stretch: <<: *test-template - image: python:3.6-stretch - services: - - postgres:11 - before_script: - - apt-get update -y - - apt-get install -y libpython3-dev git libpq-dev gcc cmake autoconf automake libc-dev - - python3 -m pip install pipenv - - pipenv install --system - - python manage.py migrate + image: $BASE_IMAGE_URL:stretch-3.7-$CI_COMMIT_REF_SLUG test-3.6-stretch: <<: *test-template - image: python:3.6-stretch - services: - - postgres:11 - before_script: - - apt-get update -y - - apt-get install -y libpython3-dev git libpq-dev gcc cmake autoconf automake libc-dev - - python3 -m pip install pipenv - - pipenv install --system - - python manage.py migrate + image: $BASE_IMAGE_URL:stretch-3.6-$CI_COMMIT_REF_SLUG pages: - stage: deploy + image: python:3.7-alpine + stage: publish dependencies: - test-3.7-alpine before_script: @@ -106,7 +148,7 @@ pages: push-django: image: docker:stable-git - stage: deploy + stage: publish script: - echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin - docker build -t pythondiscord/django:latest . @@ -117,15 +159,20 @@ push-django: tags: - docker -push-nging: - image: docker:stable-git +deploy: stage: deploy + image: alpine:latest + before_script: + - apk add --no-cache openssh-client + - echo "$DJANGO_DEPLOY_SSH_PRIVATE_KEY" > id_ed25519 + - chmod 400 id_ed25519 script: - - echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin - - docker build -t pythondiscord/nging:latest . - - docker push pythondiscord/nging:latest + - ssh -i id_ed25519 -p 583 -o "StrictHostKeyChecking=no" [email protected] + environment: + name: Django staging + url: https://pysite.jchri.st + tags: + - docker only: - master - django - tags: - - docker diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 00000000..5a0a0197 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,4 @@ +ignored: + # Ignore suggestion for pinned versions in `apt-get´ and `apk` + - DL3008 + - DL3018 @@ -0,0 +1 @@ +rules '~MD024' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..c59fe469 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Markdown linting + +### Changed + +- Added unneded files to `.dockerignore` + +## [0.3.0] - 2018-18-09 + +### Added + +- Do not recommend pushes to `master` in `CONTRIBUTING.md` + +- Documentation about how to set up the site and + Postgres up locally using Docker and `pip` + +- Healthchecks for the `app` container + +- Require 100% code coverage in `CONTRIBUTING.md` + +- Require `CHANGELOG.md` updates in `CONTRIBUTING.md` + +- The `psmgr` console script as a shortcut to `python manage.py` + +- This file + +### Changed + +- Improved build speed by not installing unneeded dependencies. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36152fc5..3a9b1dc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,41 +1,69 @@ # Contributing to one of our projects -Our projects are open-source, and are deployed as commits are pushed to the `master` branch on each repository. -We've created a set of guidelines here in order to keep everything clean and in working order. Please note that -contributions may be rejected on the basis of a contributor failing to follow the guidelines. +Our projects are open-source, and are deployed as commits are +pushed to the `master` branch on each repository. We've created +a set of guidelines here in order to keep everything clean and +in working order. Please note that contributions may be rejected +on the basis of a contributor failing to follow the guidelines. ## Rules 1. **No force-pushes** or modifying the Git history in any way. -1. If you have direct access to the repository, **create a branch for your changes** and create a merge request for that branch. +1. If you have direct access to the repository, + **create a branch for your changes** and + create a merge request for that branch. If not, fork it and work on a separate branch there. - * Some repositories require this and will reject any direct pushes to `master`. Make this a habit! -1. If someone is working on a merge request, **do not open your own merge request for the same task**. Instead, leave some comments - on the existing merge request. Communication is key, and there's no point in two separate implementations of the same thing. - * One option is to fork the other contributor's repository, and submit your changes to their branch with your - own merge request. If you do this, we suggest following these guidelines when interacting with their repository - as well. -1. **Adhere to the prevailing code style**, which we enforce using [flake8](http://flake8.pycqa.org/en/latest/index.html). - * Additionally, run `flake8` against your code before you push it. Your commit will be rejected by the build server + * Some repositories require this and will reject + any direct pushes to `master`. Make this a habit! +1. If someone is working on a merge request, + **do not open your own merge request for the same task**. + Instead, leave some comments on the existing merge request. + Communication is key, and there's no point in two separate + implementations of the same thing. + * One option is to fork the other contributor's repository, + and submit your changes to their branch with your own merge + request. If you do this, we suggest following these guidelines + when interacting with their repository as well. +1. **Adhere to the prevailing code style**, which we enforce using + [flake8](http://flake8.pycqa.org/en/latest/index.html). + * Additionally, run `flake8` against your code before + you push it. Your commit will be rejected by the build server if it fails to lint. -1. **Don't fight the framework**. Every framework has its flaws, but the frameworks we've picked out have been carefully - chosen for their particular merits. If you can avoid it, please resist reimplementing swathes of framework logic - the + * Keep the coverage at 100%. Your reason not to do so is not good enough. + * No pushes to `master` without a really really good reason. + If you're unsure, it is not good enough. + * Update the `CHANGELOG.md` file as necessary. + Maintainers will tag releases as appropriate. +1. **Don't fight the framework**. Every framework has its flaws, but + the frameworks we've picked out have been carefully + chosen for their particular merits. If you can avoid it, + please resist reimplementing swathes of framework logic - the work has already been done for you! -1. **Work as a team** and cooperate where possible. Keep things friendly, and help each other out - these are shared +1. **Work as a team** and cooperate where possible. Keep things + friendly, and help each other out - these are shared projects, and nobody likes to have their feet trodden on. -1. **Internal projects are internal**. As a contributor, you have access to information that the rest of the server - does not. With this trust comes responsibility - do not release any information you have learned as a result of - your contributor position. We are very strict about announcing things at specific times, and many staff members +1. **Internal projects are internal**. As a contributor, + you have access to information that the rest of the server + does not. With this trust comes responsibility - do not + release any information you have learned as a result of + your contributor position. We are very strict about + announcing things at specific times, and many staff members will not appreciate a disruption of the announcement schedule. -Above all, the needs of our community should come before the wants of an individual. Work together, build solutions to -problems and try to do so in a way that people can learn from easily. Abuse of our trust may result in the loss of your Contributor role, especially in relation to Rule 7. +Above all, the needs of our community should come before the wants +of an individual. Work together, build solutions to problems and +try to do so in a way that people can learn from easily. +Abuse of our trust may result in the loss of your +Contributor role, especially in relation to Rule 7. ## Changes to this arrangement -All projects evolve over time, and this contribution guide is no different. This document may also be subject to pull -requests or changes by contributors, where you believe you have something valuable to add or change. +All projects evolve over time, and this contribution +guide is no different. This document may also be subject to pull +requests or changes by contributors, where you +believe you have something valuable to add or change. ## Footnotes -This document was inspired by the [Glowstone contribution guidelines](https://github.com/GlowstoneMC/Glowstone/blob/dev/docs/CONTRIBUTING.md). +This document was inspired by the +[Glowstone contribution guidelines](https://github.com/GlowstoneMC/Glowstone/blob/dev/docs/CONTRIBUTING.md). diff --git a/Dockerfile b/Dockerfile index 6eb2f6d8..9b50dcb7 100644..120000 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1 @@ -FROM python:3.7-alpine - -RUN apk add python3-dev git libpq postgresql-dev gcc cmake autoconf automake musl-dev -RUN python3 -m pip install pipenv - -ENV PIPENV_HIDE_EMOJIS=1 -ENV PIPENV_IGNORE_VIRTUALENVS=1 -ENV PIPENV_MAX_SUBPROCESS=2 -ENV PIPENV_NOSPIN=1 -ENV PIPENV_VENV_IN_PROJECT=1 - -COPY . /app -WORKDIR /app - -RUN pipenv install --deploy --system -RUN apk del git gcc cmake autoconf automake - -CMD ["gunicorn", "--workers", "4", "--bind", "0.0.0.0:4000", "pysite.wsgi:application"] +docker/app/alpine/3.7/Dockerfile
\ No newline at end of file diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 5cd38df1..00000000 --- a/Pipfile +++ /dev/null @@ -1,45 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -django = "==2.1.1" -django-hosts = "==3.0" -django-environ = "==0.4.5" -"psycopg2-binary" = "==2.7.5" -djangorestframework = "==3.8.2" -djangorestframework-bulk = "==0.2.1" -gunicorn = "==19.9.0" - -[dev-packages] -"flake8" = "==3.5.0" -"flake8-bugbear" = "==18.8.0" -"flake8-bandit" = "==1.0.2" -"flake8-import-order" = "==0.18" -"flake8-tidy-imports" = "==1.1.0" -"flake8-string-format" = "==0.2.3" -coverage = "==4.5.1" -"pep8-naming" = "==0.7.0" -mccabe = "==0.6.1" - -[requires] -python_version = "3.7" - -[scripts] -build = "docker build -t pythondiscord/site:latest -f docker/Dockerfile ." -buildci = "docker build -t pythondiscord/site-ci:latest -f docker/ci.Dockerfile ." -buildbase = "docker build -t pythondiscord/site-base:latest -f docker/Dockerfile.base ." -buildjs = "gulp" -#buildscss = "python scss.py scss/pysite:scss/pysite/style.scss:static/css/style.css scss/uikit:scss/uikit/uikit_blurple.scss:static/css/uikit_blurple.css" -clean = "rm -rf __pycache__ htmlcov .coverage .pytest_cache" -fixjs = "eslint static/js --fix" -#start = "gunicorn -w 12 -b 0.0.0.0:10012 -c gunicorn_config.py --log-level info -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker app:app" -lint = "python -m flake8" -lintjs = "eslint js/src" -lintscss = "scss-lint scss/pysite" -push = "docker push pythondiscord/site:latest" -pushbase = "docker push pythondiscord/site-base:latest" -pushci = "docker push pythondiscord/site-ci:latest" -#rundev = "python app.py" -#test = "py.test tests --cov pysite --cov-report term-missing -v" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index a9bdf29c..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,306 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "fdbb17e1a02adedbd991ee8bec24aadc6ed53ee895aa039bffd3da3f009f724c" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "django": { - "hashes": [ - "sha256:04f2e423f2e60943c02bd2959174b844f7d1bcd19eabb7f8e4282999958021fd", - "sha256:e1cc1cd6b658aa4e052f5f2b148bfda08091d7c3558529708342e37e4e33f72c" - ], - "index": "pypi", - "version": "==2.1.1" - }, - "django-environ": { - "hashes": [ - "sha256:6c9d87660142608f63ec7d5ce5564c49b603ea8ff25da595fd6098f6dc82afde", - "sha256:c57b3c11ec1f319d9474e3e5a79134f40174b17c7cc024bbb2fad84646b120c4" - ], - "index": "pypi", - "version": "==0.4.5" - }, - "django-hosts": { - "hashes": [ - "sha256:3599645f37b4c51df6140d659bef356e05ae7ff7748f8fef14c2c84083dd8089", - "sha256:8e83232dbd7ff0d9de5c814f16bdf4cd1971bd00c54fa1f3e507aed4f93215a8" - ], - "index": "pypi", - "version": "==3.0" - }, - "djangorestframework": { - "hashes": [ - "sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9", - "sha256:c375e4f95a3a64fccac412e36fb42ba36881e52313ec021ef410b40f67cddca4" - ], - "index": "pypi", - "version": "==3.8.2" - }, - "djangorestframework-bulk": { - "hashes": [ - "sha256:39230d8379acebd86d313df6c9150cafecb636eae1d097c30a26389ab9fee5b1" - ], - "index": "pypi", - "version": "==0.2.1" - }, - "gunicorn": { - "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" - ], - "index": "pypi", - "version": "==19.9.0" - }, - "psycopg2-binary": { - "hashes": [ - "sha256:04afb59bbbd2eab3148e6816beddc74348078b8c02a1113ea7f7822f5be4afe3", - "sha256:098b18f4d8857a8f9b206d1dc54db56c2255d5d26458917e7bcad61ebfe4338f", - "sha256:0bf855d4a7083e20ead961fda4923887094eaeace0ab2d76eb4aa300f4bbf5bd", - "sha256:197dda3ffd02057820be83fe4d84529ea70bf39a9a4daee1d20ffc74eb3d042e", - "sha256:278ef63afb4b3d842b4609f2c05ffbfb76795cf6a184deeb8707cd5ed3c981a5", - "sha256:3cbf8c4fc8f22f0817220891cf405831559f4d4c12c4f73913730a2ea6c47a47", - "sha256:4305aed922c4d9d6163ab3a41d80b5a1cfab54917467da8168552c42cad84d32", - "sha256:47ee296f704fb8b2a616dec691cdcfd5fa0f11943955e88faa98cbd1dc3b3e3d", - "sha256:4a0e38cb30457e70580903367161173d4a7d1381eb2f2cfe4e69b7806623f484", - "sha256:4d6c294c6638a71cafb82a37f182f24321f1163b08b5d5ca076e11fe838a3086", - "sha256:4f3233c366500730f839f92833194fd8f9a5c4529c8cd8040aa162c3740de8e5", - "sha256:5221f5a3f4ca2ddf0d58e8b8a32ca50948be9a43351fda797eb4e72d7a7aa34d", - "sha256:5c6ca0b507540a11eaf9e77dee4f07c131c2ec80ca0cffa146671bf690bc1c02", - "sha256:789bd89d71d704db2b3d5e67d6d518b158985d791d3b2dec5ab85457cfc9677b", - "sha256:7b94d29239efeaa6a967f3b5971bd0518d2a24edd1511edbf4a2c8b815220d07", - "sha256:89bc65ef3301c74cf32db25334421ea6adbe8f65601ea45dcaaf095abed910bb", - "sha256:89d6d3a549f405c20c9ae4dc94d7ed2de2fa77427a470674490a622070732e62", - "sha256:97521704ac7127d7d8ba22877da3c7bf4a40366587d238ec679ff38e33177498", - "sha256:a395b62d5f44ff6f633231abe568e2203b8fabf9797cd6386aa92497df912d9a", - "sha256:a6d32c37f714c3f34158f3fa659f3a8f2658d5f53c4297d45579b9677cc4d852", - "sha256:a89ee5c26f72f2d0d74b991ce49e42ddeb4ac0dc2d8c06a0f2770a1ab48f4fe0", - "sha256:b4c8b0ef3608e59317bfc501df84a61e48b5445d45f24d0391a24802de5f2d84", - "sha256:b5fcf07140219a1f71e18486b8dc28e2e1b76a441c19374805c617aa6d9a9d55", - "sha256:b86f527f00956ecebad6ab3bb30e3a75fedf1160a8716978dd8ce7adddedd86f", - "sha256:be4c4aa22ba22f70de36c98b06480e2f1697972d49eb20d525f400d204a6d272", - "sha256:c2ac7aa1a144d4e0e613ac7286dae85671e99fe7a1353954d4905629c36b811c", - "sha256:de26ef4787b5e778e8223913a3e50368b44e7480f83c76df1f51d23bd21cea16", - "sha256:e70ebcfc5372dc7b699c0110454fc4263967f30c55454397e5769eb72c0eb0ce", - "sha256:eadbd32b6bc48b67b0457fccc94c86f7ccc8178ab839f684eb285bb592dc143e", - "sha256:ecbc6dfff6db06b8b72ae8a2f25ff20fbdcb83cb543811a08f7cb555042aa729" - ], - "index": "pypi", - "version": "==2.7.5" - }, - "pytz": { - "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" - ], - "version": "==2018.5" - } - }, - "develop": { - "attrs": { - "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" - ], - "version": "==18.2.0" - }, - "bandit": { - "hashes": [ - "sha256:45bf1b361004e861e5b423b36ff5c700d21442753c841013c87f14a4639b1d74", - "sha256:a3aa04802194ec1fd290849e02b915824f9c3234623d7dcea6a33b1605ddb0ac" - ], - "version": "==1.5.0" - }, - "coverage": { - "hashes": [ - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" - ], - "index": "pypi", - "version": "==4.5.1" - }, - "flake8": { - "hashes": [ - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" - ], - "index": "pypi", - "version": "==3.5.0" - }, - "flake8-bandit": { - "hashes": [ - "sha256:a66c7b42af9530d5e988851ccee02958a51a85d46f1f4609ecc3546948f809b8", - "sha256:f7c3421fd9aebc63689c0693511e16dcad678fd4a0ce624b78ca91ae713eacdc" - ], - "index": "pypi", - "version": "==1.0.2" - }, - "flake8-bugbear": { - "hashes": [ - "sha256:07b6e769d7f4e168d590f7088eae40f6ddd9fa4952bed31602def65842682c83", - "sha256:0ccf56975f4db1d69dc1cf3598c99d768ebf95d0cad27d76087954aa399b515a" - ], - "index": "pypi", - "version": "==18.8.0" - }, - "flake8-import-order": { - "hashes": [ - "sha256:9be5ca10d791d458eaa833dd6890ab2db37be80384707b0f76286ddd13c16cbf", - "sha256:feca2fd0a17611b33b7fa84449939196c2c82764e262486d5c3e143ed77d387b" - ], - "index": "pypi", - "version": "==0.18" - }, - "flake8-polyfill": { - "hashes": [ - "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", - "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda" - ], - "version": "==1.0.2" - }, - "flake8-string-format": { - "hashes": [ - "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2", - "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1" - ], - "index": "pypi", - "version": "==0.2.3" - }, - "flake8-tidy-imports": { - "hashes": [ - "sha256:5fc28c82bba16abb4f1154dc59a90487f5491fbdb27e658cbee241e8fddc1b91", - "sha256:c05c9f7dadb5748a04b6fa1c47cb6ae5a8170f03cfb1dca8b37aec58c1ee6d15" - ], - "index": "pypi", - "version": "==1.1.0" - }, - "gitdb2": { - "hashes": [ - "sha256:87783b7f4a8f6b71c7fe81d32179b3c8781c1a7d6fa0c69bff2f315b00aff4f8", - "sha256:bb4c85b8a58531c51373c89f92163b92f30f81369605a67cd52d1fc21246c044" - ], - "version": "==2.0.4" - }, - "gitpython": { - "hashes": [ - "sha256:563221e5a44369c6b79172f455584c9ebbb122a13368cc82cb4b5addff788f82", - "sha256:8237dc5bfd6f1366abeee5624111b9d6879393d84745a507de0fda86043b65a8" - ], - "version": "==2.1.11" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "index": "pypi", - "version": "==0.6.1" - }, - "pbr": { - "hashes": [ - "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", - "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa" - ], - "version": "==4.2.0" - }, - "pep8-naming": { - "hashes": [ - "sha256:360308d2c5d2fff8031c1b284820fbdb27a63274c0c1a8ce884d631836da4bdd", - "sha256:624258e0dd06ef32a9daf3c36cc925ff7314da7233209c5b01f7e5cdd3c34826" - ], - "index": "pypi", - "version": "==0.7.0" - }, - "pycodestyle": { - "hashes": [ - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" - ], - "version": "==2.3.1" - }, - "pyflakes": { - "hashes": [ - "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", - "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" - ], - "version": "==1.6.0" - }, - "pyyaml": { - "hashes": [ - "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", - "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", - "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", - "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", - "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", - "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", - "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", - "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", - "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", - "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", - "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" - ], - "version": "==3.13" - }, - "six": { - "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - ], - "version": "==1.11.0" - }, - "smmap2": { - "hashes": [ - "sha256:0dd53d991af487f9b22774fa89451358da3607c02b9b886a54736c6a313ece0b", - "sha256:dc216005e529d57007ace27048eb336dcecb7fc413cfb3b2f402bb25972b69c6" - ], - "version": "==2.0.4" - }, - "stevedore": { - "hashes": [ - "sha256:1e153545aca7a6a49d8337acca4f41c212fbfa60bf864ecd056df0cafb9627e8", - "sha256:c7eac1c0d95824c88b655273da5c17cdde6482b2739f47c30bf851dcc9d3c2c0" - ], - "version": "==1.29.0" - } - } -} @@ -1,8 +1,7 @@ # Python Discord: Site -This is all of the code that is responsible for maintaining [our website](https://pythondiscord.com), and all of -its subdomains. +This is all of the code that is responsible for maintaining +[our website](https://pythondiscord.com), and all of its subdomains. -If you're looking to contribute or play around with the code, take a look at our -[Project Guide](https://wiki.pythondiscord.com/wiki/contributing/project/site), hosted on the wiki you'll find -in this very repository.
\ No newline at end of file +If you're looking to contribute or play around with the code, +take a look at the [`docs` directory](docs). diff --git a/SETUP.md b/SETUP.md deleted file mode 100644 index dd974338..00000000 --- a/SETUP.md +++ /dev/null @@ -1,11 +0,0 @@ -# Setting up & running up the website locally -*to be put on the wiki* - -- `pipenv sync` -- `psql -c 'CREATE USER pysite WITH CREATEDB;'` -- `psql -c 'CREATE DATABASE pysite OWNER pysite;'` -- `echo 'DEBUG=1' >> .env` -- `echo 'DATABASE_URL=postgres://pysite:@localhost/pysite' >> .env` -- `pipenv shell` -- `python manage.py migrate` -- `python manage.py runserver` diff --git a/admin/__init__.py b/admin/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/admin/__init__.py diff --git a/admin/urls.py b/admin/urls.py new file mode 100644 index 00000000..146c6496 --- /dev/null +++ b/admin/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path + + +urlpatterns = ( + path('', admin.site.urls), +) diff --git a/api/admin.py b/api/admin.py index 4185d360..54cb33ea 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,3 +1,14 @@ -# from django.contrib import admin +from django.contrib import admin -# Register your models here. +from .models import ( + DocumentationLink, Member, + OffTopicChannelName, Role, + SnakeName +) + + +admin.site.register(DocumentationLink) +admin.site.register(Member) +admin.site.register(OffTopicChannelName) +admin.site.register(Role) +admin.site.register(SnakeName) diff --git a/api/migrations/0006_add_help_texts.py b/api/migrations/0006_add_help_texts.py new file mode 100644 index 00000000..a57d2289 --- /dev/null +++ b/api/migrations/0006_add_help_texts.py @@ -0,0 +1,44 @@ +# Generated by Django 2.1.1 on 2018-09-21 20:26 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0005_user'), + ] + + operations = [ + migrations.AlterField( + model_name='documentationlink', + name='base_url', + field=models.URLField(help_text='The base URL from which documentation will be available for this project. Used to generate links to various symbols within this package.'), + ), + migrations.AlterField( + model_name='documentationlink', + name='inventory_url', + field=models.URLField(help_text='The URL at which the Sphinx inventory is available for this package.'), + ), + migrations.AlterField( + model_name='documentationlink', + name='package', + field=models.CharField(help_text='The Python package name that this documentation link belongs to.', max_length=50, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='offtopicchannelname', + name='name', + field=models.CharField(help_text='The actual channel name that will be used on our Discord server.', max_length=96, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex='^[a-z0-9-]+$')]), + ), + migrations.AlterField( + model_name='snakename', + name='name', + field=models.CharField(help_text="The regular name for this snake, e.g. 'Python'.", max_length=100, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='snakename', + name='scientific', + field=models.CharField(help_text="The scientific name for this snake, e.g. 'Python bivittatus'.", max_length=150), + ), + ] diff --git a/api/models.py b/api/models.py index 4e4de9e0..6b681ebc 100644 --- a/api/models.py +++ b/api/models.py @@ -1,31 +1,80 @@ +from operator import itemgetter + from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator from django.db import models -class DocumentationLink(models.Model): +class ModelReprMixin: + """ + Adds a `__repr__` method to the model subclassing this + mixin which will display the model's class name along + with all parameters used to construct the object. + """ + + def __repr__(self): + attributes = ' '.join( + f'{attribute}={value!r}' + for attribute, value in sorted( + self.__dict__.items(), + key=itemgetter(0) + ) + if not attribute.startswith('_') + ) + return f'<{self.__class__.__name__}({attributes})>' + + +class DocumentationLink(ModelReprMixin, models.Model): """A documentation link used by the `!docs` command of the bot.""" - package = models.CharField(primary_key=True, max_length=50) - base_url = models.URLField() - inventory_url = models.URLField() + package = models.CharField( + primary_key=True, + max_length=50, + help_text="The Python package name that this documentation link belongs to." + ) + base_url = models.URLField( + help_text=( + "The base URL from which documentation will be available for this project. " + "Used to generate links to various symbols within this package." + ) + ) + inventory_url = models.URLField( + help_text="The URL at which the Sphinx inventory is available for this package." + ) + def __str__(self): + return f"{self.package} - {self.base_url}" -class OffTopicChannelName(models.Model): + +class OffTopicChannelName(ModelReprMixin, models.Model): name = models.CharField( primary_key=True, max_length=96, - validators=(RegexValidator(regex=r'^[a-z0-9-]+$'),) + validators=(RegexValidator(regex=r'^[a-z0-9-]+$'),), + help_text="The actual channel name that will be used on our Discord server." ) + def __str__(self): + return self.name + -class SnakeName(models.Model): +class SnakeName(ModelReprMixin, models.Model): """A snake name used by the bot's snake cog.""" - name = models.CharField(primary_key=True, max_length=100) - scientific = models.CharField(max_length=150) + name = models.CharField( + primary_key=True, + max_length=100, + help_text="The regular name for this snake, e.g. 'Python'." + ) + scientific = models.CharField( + max_length=150, + help_text="The scientific name for this snake, e.g. 'Python bivittatus'." + ) + + def __str__(self): + return f"{self.name} ({self.scientific})" -class Role(models.Model): +class Role(ModelReprMixin, models.Model): """A role on our Discord server.""" id = models.BigIntegerField( # noqa @@ -65,8 +114,11 @@ class Role(models.Model): help_text="The integer value of the permission bitset of this role from Discord." ) + def __str__(self): + return self.name + -class Member(models.Model): +class Member(ModelReprMixin, models.Model): """A member of our Discord server.""" id = models.BigIntegerField( # noqa @@ -105,29 +157,5 @@ class Member(models.Model): help_text="Any roles this user has on our server." ) - -class Tag(models.Model): - """A tag providing (hopefully) useful content, shown by the bot.""" - - author = models.ForeignKey( - Member, - help_text="The user that originally created this tag.", - on_delete=models.CASCADE - ) - title = models.CharField( - max_length=256, - help_text="The title of this tag, displayed in the Embed.", - unique=True - ) - content = models.CharField( - max_length=2048, - help_text="The content of this tag, displayed in the Embed." - ) - image_url = models.URLField( - null=True, - help_text="An optional image to display in the tag embed." - ) - thumbnail_url = models.URLField( - null=True, - help_text="An optional thumbnail to display in the tag embed." - ) + def __str__(self): + return f"{self.name}#{self.discriminator}" diff --git a/api/tests/test_models.py b/api/tests/test_models.py new file mode 100644 index 00000000..ff4bb226 --- /dev/null +++ b/api/tests/test_models.py @@ -0,0 +1,43 @@ +from django.test import SimpleTestCase + +from ..models import ( + DocumentationLink, Member, ModelReprMixin, + OffTopicChannelName, Role, SnakeName +) + + +class SimpleClass(ModelReprMixin): + def __init__(self, is_what): + self.the_cake = is_what + + +class ReprMixinTests(SimpleTestCase): + def setUp(self): + self.klass = SimpleClass('is a lie') + + def test_shows_attributes(self): + expected = "<SimpleClass(the_cake='is a lie')>" + self.assertEqual(repr(self.klass), expected) + + +class StringDunderMethodTests(SimpleTestCase): + def setUp(self): + self.objects = ( + DocumentationLink( + 'test', 'http://example.com', 'http://example.com' + ), + OffTopicChannelName(name='bob-the-builders-playground'), + SnakeName(name='python', scientific='3'), + Role( + id=5, name='test role', + colour=0x5, permissions=0 + ), + Member( + id=5, name='bob', + discriminator=1, avatar_hash=None + ) + ) + + def test_returns_string(self): + for instance in self.objects: + self.assertIsInstance(str(instance), str) diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 542caa82..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: "3.6" -services: - django: - image: registry.gitlab.com/python-discord/projects/site/django:latest - depends_on: - - migrator - - postgres - - web - environment: - DATABASE_URL: postgres://pysite:@postgres/pysite - DEBUG: "0" - SECRET_KEY: suitable-for-development-only - ports: - - "127.0.0.1:4000:4000" - - migrator: - image: registry.gitlab.com/python-discord/projects/site/django:latest - command: python manage.py migrate - depends_on: - - postgres - environment: - DATABASE_URL: postgres://pysite:@postgres/pysite - DEBUG: "0" - SECRET_KEY: suitable-for-development-only - - postgres: - image: postgres:11-alpine - environment: - POSTGRES_DB: pysite - POSTGRES_PASSWORD: "" - POSTGRES_USER: pysite - - web: - image: pythondiscord/nging diff --git a/docker/app/alpine/3.6/Dockerfile b/docker/app/alpine/3.6/Dockerfile new file mode 100644 index 00000000..b9cb557b --- /dev/null +++ b/docker/app/alpine/3.6/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.6-alpine + +STOPSIGNAL SIGQUIT +ARG EXTRAS=deploy + +RUN adduser \ + -D \ + -H \ + -u 1500 \ + pysite + +RUN apk add --no-cache --virtual build \ + gcc \ + linux-headers \ + musl-dev \ + && \ + apk add --no-cache \ + curl \ + postgresql-dev + +WORKDIR /app +COPY setup.py /app/setup.py +RUN python3 -m pip install .[$EXTRAS] +RUN apk del --purge build + +COPY . . + +CMD ["uwsgi", "--ini", "docker/app/uwsgi.ini"] diff --git a/docker/app/alpine/3.7/Dockerfile b/docker/app/alpine/3.7/Dockerfile new file mode 100644 index 00000000..4a8b5b34 --- /dev/null +++ b/docker/app/alpine/3.7/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.7-alpine + +STOPSIGNAL SIGQUIT +ARG EXTRAS=deploy + +RUN adduser \ + -D \ + -H \ + -u 1500 \ + pysite + +RUN apk add --no-cache --virtual build \ + gcc \ + linux-headers \ + musl-dev \ + && \ + apk add --no-cache \ + curl \ + postgresql-dev + +WORKDIR /app +COPY setup.py /app/setup.py +RUN python3 -m pip install .[$EXTRAS] +RUN apk del --purge build + +COPY . . + +CMD ["uwsgi", "--ini", "docker/app/uwsgi.ini"] diff --git a/docker/app/stretch/3.6/Dockerfile b/docker/app/stretch/3.6/Dockerfile new file mode 100644 index 00000000..8a37925c --- /dev/null +++ b/docker/app/stretch/3.6/Dockerfile @@ -0,0 +1,33 @@ +FROM python:3.6-stretch + +STOPSIGNAL SIGQUIT +ARG EXTRAS=deploy + +RUN adduser \ + --disabled-login \ + --no-create-home \ + --uid 1500 \ + pysite + +RUN apt-get update -y \ + && \ + apt-get install --no-install-recommends -y \ + gcc \ + libc-dev \ + libpq-dev \ + && \ + apt-get clean \ + && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY setup.py /app/setup.py +RUN python3 -m pip install .[$EXTRAS] +COPY . . + +RUN apt-get purge -y \ + gcc \ + libc-dev \ + libpq-dev + +CMD ["uwsgi", "--ini", "docker/app/uwsgi.ini"] diff --git a/docker/app/stretch/3.7/Dockerfile b/docker/app/stretch/3.7/Dockerfile new file mode 100644 index 00000000..1674eece --- /dev/null +++ b/docker/app/stretch/3.7/Dockerfile @@ -0,0 +1,33 @@ +FROM python:3.7-stretch + +STOPSIGNAL SIGQUIT +ARG EXTRAS=deploy + +RUN adduser \ + --disabled-login \ + --no-create-home \ + --uid 1500 \ + pysite + +RUN apt-get update -y \ + && \ + apt-get install --no-install-recommends -y \ + gcc \ + libc-dev \ + libpq-dev \ + && \ + apt-get clean \ + && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY setup.py /app/setup.py +RUN python3 -m pip install .[$EXTRAS] +COPY . . + +RUN apt-get purge -y \ + gcc \ + libc-dev \ + libpq-dev + +CMD ["uwsgi", "--ini", "docker/app/uwsgi.ini"] diff --git a/docker/app/uwsgi.ini b/docker/app/uwsgi.ini new file mode 100644 index 00000000..3bfcd3f8 --- /dev/null +++ b/docker/app/uwsgi.ini @@ -0,0 +1,32 @@ +[uwsgi] +### Exposed ports +# uWSGI protocol socket +socket = :4000 + +### File settings +# WSGI application +wsgi = pysite.wsgi:application +# Directory to move into at startup +chdir = /app + +### Concurrency options +# Run a master to supervise the workers +master = true +# Keep a minimum of 1 worker +cheaper = 1 +# Allow a maximum of 4 workers +workers = 4 +# Automatically set up meanginful process names +auto-procname = true +# Prefix process names with `pysite : ` +procname-prefix-spaced = pysite : + +### Startup settings +# Exit if we can't load the app +need-app = true +# `setuid` to an unprivileged user +uid = 1500 + +### Hook setup +# Gracefully kill workers on `SIGQUIT` +hook-master-start = unix_signal:3 gracefully_kill_them_all diff --git a/docker/nging/Dockerfile b/docker/nging/Dockerfile deleted file mode 100644 index 378099b8..00000000 --- a/docker/nging/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM pythondiscord/django AS builder - -ENV DATABASE_URL postgres://user:pass@host/db -ENV SECRET_KEY unused - -RUN mkdir -p /var/www/pythondiscord.com - -RUN python3 manage.py collectstatic --noinput - - -## NGINX setup -# Copy over only the static files from the previous stage -# to ensure a minimal image size in our NGINX container. -FROM nginx:alpine - -COPY --from=builder /var/www/pythondiscord.com /var/www/pythondiscord.com - -CMD ["nginx", "-g", "daemon off;"] diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..2e9f15a1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# Documentation + +This directory contains useful documentation for working with and using our site. + +## Table of contents + +* [Setup guide](setup.md) + + * [PostgreSQL setup](setup.md#postgresql-setup) + + * [Development with Docker](setup.md#development-with-docker) + + * [Development with `pip`](setup.md#development-with-pip) diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 00000000..d6e5a7bf --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,71 @@ +# Setup + +Setting up the Python site for local development +is quick and easy using `pip`. +Alternatively, you can set it up using Docker. +Both of these methods are documented here. + +## PostgreSQL setup + +Install PostgreSQL according to its documentation. +Then, create databases and users: + +```sql +$ psql -qd postgres +postgres=# CREATE USER pysite WITH CREATEDB; +postgres=# CREATE DATABASE pysite OWNER pysite; +``` + +Using different databases for development +and tests is recommended because Django +will expect an empty database when running tests. +Now that PostgreSQL is set up, simply set the proper database URL +in your environment variables: + +```sh +export DATABASE_URL=postgres://pysite@localhost/pysite +``` + +A simpler approach to automatically configuring this might come in the +near future - if you have any suggestions, please let us know! + +## Development with Docker + +To quickly set up the site locally, you can use Docker. +You will need Docker itself and `docker-compose` - +you can omit the latter if you want to use PostgreSQL on +your host. Refer to the docker documentation on how to install Docker. + +If you want to set the site up using `docker-compose`, simply run + +```sh +docker-compose up +``` + +and it will do its magic. + +Otherwise, you need to set a bunch of environment variables (or pass them along to +the container). You will also need to have a running PostgreSQL instance if you want +to run on your host's PostgreSQL instance. + +## Development with `pip` + +This is the recommended way if you wish to quickly test your changes and don't want +the overhead that Docker brings. + +Follow the PostgreSQL setup instructions above. Then, create a virtual environment +for the project. If you're new to this, you may want to check out [Installing packages +using pip and virtualenv](https://packaging.python.org/guides/installing-using-pip-and-virtualenv/) +from the Python Packaging User Guide. + +Enter the virtual environment. Now you can run + +```sh +pip install -e .[lint,test] +``` + +to install base dependencies along with lint and test dependencies. + +You can either use `python manage.py` directly, or you can use the console +entrypoint for it, `psmgr`. For example, to run tests, you could use either +`python manage.py test` or `psmgr test`. Happy hacking! diff --git a/home/urls.py b/home/urls.py index 1e84b7f4..a01e019e 100644 --- a/home/urls.py +++ b/home/urls.py @@ -1,8 +1,10 @@ +from django.contrib import admin from django.urls import path from django.views.generic import TemplateView app_name = 'home' urlpatterns = [ - path('', TemplateView.as_view(template_name='home/index.html'), name='index') + path('', TemplateView.as_view(template_name='home/index.html'), name='index'), + path('admin/', admin.site.urls) ] @@ -2,7 +2,9 @@ import os import sys -if __name__ == '__main__': + +# Separate definition to ease calling this in other scripts. +def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pysite.settings') try: from django.core.management import execute_from_command_line @@ -13,3 +15,7 @@ if __name__ == '__main__': "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/pysite/hosts.py b/pysite/hosts.py index 67fc2451..3cf41e7f 100644 --- a/pysite/hosts.py +++ b/pysite/hosts.py @@ -4,8 +4,8 @@ from django_hosts import host, patterns host_patterns = patterns( '', # > | Subdomain | URL Module | Host entry name | - # host(r"admin", "admin", name="admin"), - host(r'api', 'api.urls', name='api'), + host(r'admin', 'admin.urls', name="admin"), + host(r'api', 'api.urls', name='api'), # host(r"staff", "staff", name="staff"), # host(r"wiki", "wiki", name="wiki"), # host(r"ws", "ws", name="ws"), diff --git a/pysite/settings.py b/pysite/settings.py index 6bcf9a53..ed732872 100644 --- a/pysite/settings.py +++ b/pysite/settings.py @@ -46,13 +46,16 @@ elif 'CI' in os.environ: SECRET_KEY = "{©ø¬½.Þ7&Ñ`Q^Kº*~¢j<wxß¾±ðÛJ@q" else: - ALLOWED_HOSTS = [ - 'pythondiscord.com', - 'admin.pythondiscord.com', - 'api.pythondiscord.com', - 'staff.pythondiscord.local', - 'wiki.pythondiscord.local' - ] + ALLOWED_HOSTS = env.list( + 'ALLOWED_HOSTS', + default=[ + 'pythondiscord.com', + 'admin.pythondiscord.com', + 'api.pythondiscord.com', + 'staff.pythondiscord.local', + 'wiki.pythondiscord.local' + ] + ) SECRET_KEY = env('SECRET_KEY') @@ -168,7 +171,7 @@ DEFAULT_HOST = 'home' if DEBUG: PARENT_HOST = 'pythondiscord.local:8000' else: - PARENT_HOST = 'pythondiscord.com' + PARENT_HOST = env('PARENT_HOST', default='pythondiscord.com') # Django REST framework # http://www.django-rest-framework.org @@ -181,3 +184,29 @@ REST_FRAMEWORK = { ), 'TEST_REQUEST_DEFAULT_FORMAT': 'json' } + +# Logging +# https://docs.djangoproject.com/en/2.1/topics/logging/ +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'default': { + 'format': '%(asctime)s %(levelname)s %(module)s %(message)s' + } + }, + 'handlers': { + 'gunicorn': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'default' + } + }, + 'loggers': { + 'gunicorn.errors': { + 'level': 'DEBUG', + 'handlers': ['gunicorn'], + 'propagate': True + } + } +} diff --git a/pysite/urls.py b/pysite/urls.py index 3e2e05e8..e162a092 100644 --- a/pysite/urls.py +++ b/pysite/urls.py @@ -1,6 +1,6 @@ from django.urls import include, path -urlpatterns = [ - path('', include('home.urls', namespace='home')) -] +urlpatterns = ( + path('', include('home.urls', namespace='home')), +) diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..ab4a61a2 --- /dev/null +++ b/setup.py @@ -0,0 +1,47 @@ +from setuptools import find_packages, setup + + +setup( + name='pysite', + url='https://pythondiscord.com', + description="Python Discord community website", + project_urls={ + 'Source code': 'https://gitlab.com/python-discord/projects/site' + }, + version='0.3.0', + packages=find_packages( + exclude=('**/static',) + ), + python_requires='>= 3.6', + install_requires=[ + 'django>=2.1.1', + 'djangorestframework>=3.8.2', + 'djangorestframework-bulk>=0.2.1', + 'django-hosts>=3.0', + 'django-environ>=0.4.5', + 'psycopg2-binary>=2.7.5' + ], + extras_require={ + 'deploy': [ + 'uwsgi>=2.0.17.1' + ], + 'lint': [ + 'flake8>=3.5.0', + 'flake8-bandit>=1.0.2', + 'flake8-bugbear>=18.8.0', + 'flake8-import-order>=0.18', + 'flake8-string-format>=0.2.3', + 'flake8-tidy-imports>=1.1.0', + 'pep8-naming>=0.7.0', + 'mccabe>=0.6.1' + ], + 'test': [ + 'coverage>=4.5.1' + ] + }, + entry_points={ + 'console_scripts': ( + 'psmgr = manage:main', + ) + } +) |