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', +        ) +    } +) | 
