Merge branch 'master' into drf-auth-token

This commit is contained in:
Bruno Alla 2020-03-15 19:19:36 +00:00
commit 729cd2adab
25 changed files with 115 additions and 119 deletions

4
.github/FUNDING.yml vendored
View File

@ -1,7 +1,7 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: danielroygreenfeld
github: pydanny
patreon: roygreenfeld
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel

View File

@ -15,10 +15,6 @@ matrix:
include:
- name: Test results
script: tox -e py37
- name: Run flake8 on result
script: tox -e flake8
- name: Run black on result
script: tox -e black
- name: Black template
script: tox -e black-template
- name: Basic Docker

View File

@ -49,6 +49,7 @@ Listed in alphabetical order.
Adam Dobrawy `@ad-m`_
Adam Steele `@adammsteele`_
Agam Dua
Agustín Scaramuzza `@scaramagus`_ @scaramagus
Alberto Sanchez `@alb3rto`_
Alex Tsai `@caffodian`_
Alvaro [Andor] `@andor-pierdelacabeza`_
@ -118,6 +119,7 @@ Listed in alphabetical order.
Garry Cairns `@garry-cairns`_
Garry Polley `@garrypolley`_
Gilbishkosma `@Gilbishkosma`_
Guilherme Guy `@guilherme1guy`_
Hamish Durkin `@durkode`_
Hana Quadara `@hanaquadara`_
Harry Moreno `@morenoh149`_ @morenoh149
@ -275,6 +277,7 @@ Listed in alphabetical order.
.. _@dhepper: https://github.com/dhepper
.. _@dot2dotseurat: https://github.com/dot2dotseurat
.. _@dsclementsen: https://github.com/dsclementsen
.. _@guilherme1guy: https://github.com/guilherme1guy
.. _@durkode: https://github.com/durkode
.. _@Egregors: https://github.com/Egregors
.. _@epileptic-fish: https://gihub.com/epileptic-fish
@ -350,6 +353,7 @@ Listed in alphabetical order.
.. _@rolep: https://github.com/rolep
.. _@romanosipenko: https://github.com/romanosipenko
.. _@saschalalala: https://github.com/saschalalala
.. _@scaramagus: https://github.com/scaramagus
.. _@shireenrao: https://github.com/shireenrao
.. _@show0k: https://github.com/show0k
.. _@shultz: https://github.com/shultz

View File

@ -1,7 +1,4 @@
[pytest]
addopts = -x --tb=short
addopts = -v --tb=short
python_paths = .
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*
markers =
flake8: Run flake8 on all possible template combinations
black: Run black on all possible template combinations

View File

@ -6,12 +6,12 @@ binaryornot==0.4.4
# ------------------------------------------------------------------------------
black==19.10b0
flake8==3.7.9
flake8-isort==2.8.0
# Testing
# ------------------------------------------------------------------------------
tox==3.14.5
pytest==5.3.5
pytest_cases==1.12.2
pytest==5.4.1
pytest-cookies==0.5.1
pytest-xdist==1.31.0
pytest-instafail==0.4.1.post0
pyyaml==5.3

View File

@ -3,7 +3,6 @@ import re
import pytest
from cookiecutter.exceptions import FailedHookException
from pytest_cases import fixture_plus
import sh
import yaml
from binaryornot.check import is_binary
@ -26,49 +25,62 @@ def context():
}
@fixture_plus
@pytest.mark.parametrize("windows", ["y", "n"], ids=lambda yn: f"win:{yn}")
@pytest.mark.parametrize("use_docker", ["y", "n"], ids=lambda yn: f"docker:{yn}")
@pytest.mark.parametrize("use_celery", ["y", "n"], ids=lambda yn: f"celery:{yn}")
@pytest.mark.parametrize("use_mailhog", ["y", "n"], ids=lambda yn: f"mailhog:{yn}")
@pytest.mark.parametrize("use_sentry", ["y", "n"], ids=lambda yn: f"sentry:{yn}")
@pytest.mark.parametrize("use_compressor", ["y", "n"], ids=lambda yn: f"cmpr:{yn}")
@pytest.mark.parametrize("use_drf", ["y", "n"], ids=lambda yn: f"drf:{yn}")
@pytest.mark.parametrize(
"use_whitenoise,cloud_provider",
[
("y", "AWS"),
("y", "GCP"),
("y", "None"),
("n", "AWS"),
("n", "GCP"),
# no whitenoise + no cloud provider is not supported
],
ids=lambda id: f"wnoise:{id[0]}-cloud:{id[1]}",
)
def context_combination(
windows,
use_docker,
use_celery,
use_mailhog,
use_sentry,
use_compressor,
use_whitenoise,
use_drf,
cloud_provider,
):
"""Fixture that parametrize the function where it's used."""
return {
"windows": windows,
"use_docker": use_docker,
"use_compressor": use_compressor,
"use_celery": use_celery,
"use_mailhog": use_mailhog,
"use_sentry": use_sentry,
"use_whitenoise": use_whitenoise,
"use_drf": use_drf,
"cloud_provider": cloud_provider,
}
SUPPORTED_COMBINATIONS = [
{"open_source_license": "MIT"},
{"open_source_license": "BSD"},
{"open_source_license": "GPLv3"},
{"open_source_license": "Apache Software License 2.0"},
{"open_source_license": "Not open source"},
{"windows": "y"},
{"windows": "n"},
{"use_pycharm": "y"},
{"use_pycharm": "n"},
{"use_docker": "y"},
{"use_docker": "n"},
{"postgresql_version": "11.3"},
{"postgresql_version": "10.8"},
{"postgresql_version": "9.6"},
{"postgresql_version": "9.5"},
{"postgresql_version": "9.4"},
{"cloud_provider": "AWS", "use_whitenoise": "y"},
{"cloud_provider": "AWS", "use_whitenoise": "n"},
{"cloud_provider": "GCP", "use_whitenoise": "y"},
{"cloud_provider": "GCP", "use_whitenoise": "n"},
{"cloud_provider": "None", "use_whitenoise": "y"},
# Note: cloud_provider=None AND use_whitenoise=n is not supported
{"use_drf": "y"},
{"use_drf": "n"},
{"js_task_runner": "None"},
{"js_task_runner": "Gulp"},
{"custom_bootstrap_compilation": "y"},
{"custom_bootstrap_compilation": "n"},
{"use_compressor": "y"},
{"use_compressor": "n"},
{"use_celery": "y"},
{"use_celery": "n"},
{"use_mailhog": "y"},
{"use_mailhog": "n"},
{"use_sentry": "y"},
{"use_sentry": "n"},
{"use_whitenoise": "y"},
{"use_whitenoise": "n"},
{"use_heroku": "y"},
{"use_heroku": "n"},
{"ci_tool": "None"},
{"ci_tool": "Travis"},
{"ci_tool": "Gitlab"},
{"keep_local_envs_in_vcs": "y"},
{"keep_local_envs_in_vcs": "n"},
{"debug": "y"},
{"debug": "n"},
]
UNSUPPORTED_COMBINATIONS = [{"cloud_provider": "None", "use_whitenoise": "n"}]
def _fixture_id(ctx):
"""Helper to get a user friendly test name from the parametrized context."""
return "-".join(f"{key}:{value}" for key, value in ctx.items())
def build_files_list(root_dir):
@ -81,9 +93,7 @@ def build_files_list(root_dir):
def check_paths(paths):
"""Method to check all paths have correct substitutions,
used by other tests cases
"""
"""Method to check all paths have correct substitutions."""
# Assert that no match is found in any of the files
for path in paths:
if is_binary(path):
@ -95,13 +105,10 @@ def check_paths(paths):
assert match is None, msg.format(path)
def test_project_generation(cookies, context, context_combination):
"""
Test that project is generated and fully rendered.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context={**context, **context_combination})
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_project_generation(cookies, context, context_override):
"""Test that project is generated and fully rendered."""
result = cookies.bake(extra_context={**context, **context_override})
assert result.exit_code == 0
assert result.exception is None
assert result.project.basename == context["project_slug"]
@ -112,34 +119,26 @@ def test_project_generation(cookies, context, context_combination):
check_paths(paths)
@pytest.mark.flake8
def test_flake8_passes(cookies, context_combination):
"""
Generated project should pass flake8.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context=context_combination)
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_flake8_passes(cookies, context_override):
"""Generated project should pass flake8."""
result = cookies.bake(extra_context=context_override)
try:
sh.flake8(str(result.project))
except sh.ErrorReturnCode as e:
pytest.fail(e)
pytest.fail(e.stdout.decode())
@pytest.mark.black
def test_black_passes(cookies, context_combination):
"""
Generated project should pass black.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context=context_combination)
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_black_passes(cookies, context_override):
"""Generated project should pass black."""
result = cookies.bake(extra_context=context_override)
try:
sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/")
except sh.ErrorReturnCode as e:
pytest.fail(e)
pytest.fail(e.stdout.decode())
def test_travis_invokes_pytest(cookies, context):
@ -187,9 +186,10 @@ def test_invalid_slug(cookies, context, slug):
assert isinstance(result.exception, FailedHookException)
def test_no_whitenoise_and_no_cloud_provider(cookies, context):
"""It should not generate project if neither whitenoise or cloud provider are set"""
context.update({"use_whitenoise": "n", "cloud_provider": "None"})
@pytest.mark.parametrize("invalid_context", UNSUPPORTED_COMBINATIONS)
def test_error_if_incompatible(cookies, context, invalid_context):
"""It should not generate project an incompatible combination is selected."""
context.update(invalid_context)
result = cookies.bake(extra_context=context)
assert result.exit_code != 0

12
tox.ini
View File

@ -1,18 +1,10 @@
[tox]
skipsdist = true
envlist = py37,flake8,black,black-template
envlist = py37,black-template
[testenv]
deps = -rrequirements.txt
commands = pytest -m "not flake8" -m "not black" {posargs:./tests}
[testenv:flake8]
deps = -rrequirements.txt
commands = pytest -m flake8 {posargs:./tests}
[testenv:black]
deps = -rrequirements.txt
commands = pytest -m black {posargs:./tests}
commands = pytest {posargs:./tests}
[testenv:black-template]
deps = black

View File

@ -13,8 +13,8 @@ indent_style = space
indent_size = 4
[*.py]
line_length = 120
known_first_party = {{ cookiecutter.project_slug }}
line_length = 88
known_first_party = {{cookiecutter.project_slug}},config
multi_line_output = 3
default_section = THIRDPARTY
recursive = true

View File

@ -6,6 +6,7 @@ variables:
POSTGRES_USER: '{{ cookiecutter.project_slug }}'
POSTGRES_PASSWORD: ''
POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}'
POSTGRES_HOST_AUTH_METHOD: trust
flake8:
stage: lint

View File

@ -2,4 +2,5 @@ release: python manage.py migrate
web: gunicorn config.wsgi:application
{% if cookiecutter.use_celery == "y" -%}
worker: celery worker --app=config.celery_app --loglevel=info
beat: celery beat --app=config.celery_app --loglevel=info
{%- endif %}

View File

@ -1,6 +1,7 @@
FROM python:3.7-slim-buster
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
RUN apt-get update \
# dependencies for building Python packages

View File

@ -1,5 +1,6 @@
from rest_framework.routers import DefaultRouter, SimpleRouter
from django.conf import settings
from rest_framework.routers import DefaultRouter, SimpleRouter
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
if settings.DEBUG:

View File

@ -1,4 +1,5 @@
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.

View File

@ -1,3 +1,4 @@
"""isort:skip_file"""
{% if cookiecutter.use_sentry == 'y' -%}
import logging

View File

@ -1,10 +1,10 @@
from django.conf import settings
from django.urls import include, path
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic import TemplateView
from django.urls import include, path
from django.views import defaults as default_views
{% if cookiecutter.use_drf == 'y' -%}
from django.views.generic import TemplateView
{%- if cookiecutter.use_drf == 'y' %}
from rest_framework.authtoken.views import obtain_auth_token
{%- endif %}

View File

@ -10,7 +10,7 @@ whitenoise==5.0.1 # https://github.com/evansd/whitenoise
{%- endif %}
redis==3.4.1 # https://github.com/andymccurdy/redis-py
{%- if cookiecutter.use_celery == "y" %}
celery==4.4.0 # pyup: < 5.0 # https://github.com/celery/celery
celery==4.4.1 # pyup: < 5.0 # https://github.com/celery/celery
django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
{%- if cookiecutter.use_docker == 'y' %}
flower==0.9.3 # https://github.com/mher/flower
@ -19,7 +19,7 @@ flower==0.9.3 # https://github.com/mher/flower
# Django
# ------------------------------------------------------------------------------
django==2.2.10 # pyup: < 3.0 # https://www.djangoproject.com/
django==2.2.11 # pyup: < 3.0 # https://www.djangoproject.com/
django-environ==0.4.5 # https://github.com/joke2k/django-environ
django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
django-allauth==0.41.0 # https://github.com/pennersr/django-allauth

View File

@ -1,8 +1,8 @@
-r ./base.txt
Werkzeug==1.0.0 # https://github.com/pallets/werkzeug
ipdb==0.13.1 # https://github.com/gotcha/ipdb
Sphinx==2.4.3 # https://github.com/sphinx-doc/sphinx
ipdb==0.13.2 # https://github.com/gotcha/ipdb
Sphinx==2.4.4 # https://github.com/sphinx-doc/sphinx
{%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- else %}
@ -11,21 +11,22 @@ psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2
# Testing
# ------------------------------------------------------------------------------
mypy==0.761 # https://github.com/python/mypy
django-stubs==1.4.0 # https://github.com/typeddjango/django-stubs
mypy==0.770 # https://github.com/python/mypy
django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs
pytest==5.3.5 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar
# Code quality
# ------------------------------------------------------------------------------
flake8==3.7.9 # https://github.com/PyCQA/flake8
flake8-isort==2.8.0 # https://github.com/gforcada/flake8-isort
coverage==5.0.3 # https://github.com/nedbat/coveragepy
black==19.10b0 # https://github.com/ambv/black
pylint-django==2.0.14 # https://github.com/PyCQA/pylint-django
{%- if cookiecutter.use_celery == 'y' %}
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
{%- endif %}
pre-commit==2.1.1 # https://github.com/pre-commit/pre-commit
pre-commit==2.2.0 # https://github.com/pre-commit/pre-commit
# Django
# ------------------------------------------------------------------------------

View File

@ -5,7 +5,7 @@
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==2.0.1 # https://github.com/antonagestam/collectfast
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
{%- endif %}
{%- if cookiecutter.use_sentry == "y" %}
sentry-sdk==0.14.2 # https://github.com/getsentry/sentry-python

View File

@ -1,4 +1,5 @@
from rest_framework import serializers
from {{ cookiecutter.project_slug }}.users.models import User

View File

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, UpdateModelMixin
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

View File

@ -1,4 +1,4 @@
from django.contrib.auth import get_user_model, forms
from django.contrib.auth import forms, get_user_model
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

View File

@ -1,7 +1,6 @@
import pytest
from celery.result import EagerResult
from {{ cookiecutter.project_slug }}.users.tasks import get_users_count
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory

View File

@ -1,5 +1,5 @@
import pytest
from django.urls import reverse, resolve
from django.urls import resolve, reverse
from {{ cookiecutter.project_slug }}.users.models import User

View File

@ -1,9 +1,9 @@
from django.urls import path
from {{ cookiecutter.project_slug }}.users.views import (
user_detail_view,
user_redirect_view,
user_update_view,
user_detail_view,
)
app_name = "users"

View File

@ -1,9 +1,9 @@
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.views.generic import DetailView, RedirectView, UpdateView
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, RedirectView, UpdateView
User = get_user_model()