aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.dockerignore42
-rw-r--r--.flake82
-rw-r--r--.gitlab-ci.yml157
-rw-r--r--.hadolint.yaml4
-rw-r--r--.mdlrc1
-rw-r--r--CHANGELOG.md39
-rw-r--r--CONTRIBUTING.md74
l---------[-rw-r--r--]Dockerfile19
-rw-r--r--Pipfile45
-rw-r--r--Pipfile.lock306
-rw-r--r--README.md9
-rw-r--r--SETUP.md11
-rw-r--r--admin/__init__.py0
-rw-r--r--admin/urls.py7
-rw-r--r--api/admin.py15
-rw-r--r--api/migrations/0006_add_help_texts.py44
-rw-r--r--api/models.py102
-rw-r--r--api/tests/test_models.py43
-rw-r--r--docker-compose.yml34
-rw-r--r--docker/app/alpine/3.6/Dockerfile28
-rw-r--r--docker/app/alpine/3.7/Dockerfile28
-rw-r--r--docker/app/stretch/3.6/Dockerfile33
-rw-r--r--docker/app/stretch/3.7/Dockerfile33
-rw-r--r--docker/app/uwsgi.ini32
-rw-r--r--docker/nging/Dockerfile18
-rw-r--r--docs/README.md13
-rw-r--r--docs/setup.md71
-rw-r--r--home/urls.py4
-rwxr-xr-xmanage.py8
-rw-r--r--pysite/hosts.py4
-rw-r--r--pysite/settings.py45
-rw-r--r--pysite/urls.py6
-rw-r--r--setup.py47
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
diff --git a/.flake8 b/.flake8
index d34064b1..7c2490f8 100644
--- a/.flake8
+++ b/.flake8
@@ -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
diff --git a/.mdlrc b/.mdlrc
new file mode 100644
index 00000000..0c02cde4
--- /dev/null
+++ b/.mdlrc
@@ -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"
- }
- }
-}
diff --git a/README.md b/README.md
index 79e3c1c6..7f54930a 100644
--- a/README.md
+++ b/README.md
@@ -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)
]
diff --git a/manage.py b/manage.py
index f43b0986..42d82d80 100755
--- a/manage.py
+++ b/manage.py
@@ -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',
+ )
+ }
+)