Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: remove node-progressive #2375

Merged
merged 31 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:

- name: Run Web tests
run: docker compose run --rm web bash -c "python3 -m flake8 . &&
python3 wait_for_postgres.py &&
python3 -Wall ./manage.py check --deploy &&
python3 -m pytest --ds=piedpiper.settings --dc=Local"
python3 wait_for_postgres.py &&
python3 -m pytest --ds=piedpiper.config --dc=Local -p no:cacheprovider -s"

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ You need to make a scalable api on a deadline. You deeply care about the quality


## Highlights
- Modern Python development with Python 3.8+
- Bleeding edge Django 3.0+
- Modern Python development with Python 3.12+
- Bleeding edge Django 5.0+
- Fully dockerized, local development via docker-compose.
- PostgreSQL 11.6+
- PostgreSQL 16.4+
- Start off with full test coverage and [continuous integration](https://github.com/agconti/cookiecutter-django-rest/blob/master/%7B%7Bcookiecutter.github_repository_name%7D%7D/.travis.yml).
- Complete [Django Rest Framework](http://www.django-rest-framework.org/) integration
- Always current dependencies and security updates enforced by [pyup.io](https://pyup.io/).
Expand Down
17 changes: 12 additions & 5 deletions {{cookiecutter.github_repository_name}}/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
FROM python:3.8
ENV PYTHONUNBUFFERED 1
FROM python:3.12-slim as base
FROM base as builder

# Allows docker to cache installed dependencies between builds
RUN apt-get update && apt-get -y install libpq-dev gcc
COPY ./requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN pip3 install --no-cache-dir --target=packages -r requirements.txt

FROM base as runtime
COPY --from=builder packages /usr/lib/python3.12/site-packages
ENV PYTHONPATH=/usr/lib/python3.12/site-packages

# Security Context
RUN useradd -m nonroot
USER nonroot

# Adds our application code to the image
COPY . code
WORKDIR code

EXPOSE 8000

# Run the production server
CMD newrelic-admin run-program gunicorn --bind 0.0.0.0:$PORT --access-logfile - {{cookiecutter.app_name}}.wsgi:application
7 changes: 7 additions & 0 deletions {{cookiecutter.github_repository_name}}/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest

# Ensures pytest waits for the database to load
# https://pytest-django.readthedocs.io/en/latest/faq.html#how-can-i-give-database-access-to-all-my-tests-without-the-django-db-marker
@pytest.fixture(autouse=True)
def enable_db_access_for_all_tests(db):
pass
9 changes: 5 additions & 4 deletions {{cookiecutter.github_repository_name}}/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
version: '3'
services:
postgres:
image: postgres:11.6
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
image: postgres:16.4
web:
restart: always
environment:
- DJANGO_SECRET_KEY=local
build: ./
command: >
bash -c "python wait_for_postgres.py &&
bash -c "python3 wait_for_postgres.py &&
./manage.py migrate &&
./manage.py runserver 0.0.0.0:8000"
volumes:
Expand All @@ -20,7 +21,7 @@ services:
documentation:
restart: always
build: ./
command: "mkdocs serve"
command: "python3 -m mkdocs serve"
volumes:
- ./:/code
ports:
Expand Down
5 changes: 1 addition & 4 deletions {{cookiecutter.github_repository_name}}/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Core
pytz==2024.1
Django==5.1
django-configurations==2.5.1
gunicorn==23.0.0
Expand All @@ -15,7 +14,6 @@ django_unique_upload==0.2.1

# Rest apis
djangorestframework==3.15.2
Markdown==3.7
django-filter==24.3

# Developer Tools
Expand All @@ -27,8 +25,7 @@ flake8==7.1.1
# Testing
mock==5.1.0
factory-boy==3.3.1
django-nose==1.4.7
nose-progressive==1.5.2
pytest-django==4.8.0
coverage==7.6.1

# Static and Media Storage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ class Local(Common):

# Testing
INSTALLED_APPS = Common.INSTALLED_APPS
INSTALLED_APPS += ('django_nose',)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
BASE_DIR,
'-s',
'--nologcapture',
'--with-coverage',
'--with-progressive',
'--cover-package={{cookiecutter.app_name}}'
]

# Mail
EMAIL_HOST = 'localhost'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
DJANGO_CONFIGURATION = Local
DJANGO_SETTINGS_MODULE = {{cookiecutter.app_name}}.config
python_files = tests.py test_*.py *_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
from django.views.generic.base import RedirectView
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views
from .users.views import UserViewSet, UserCreateViewSet
from .users.views import UserViewSet

router = DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'users', UserCreateViewSet)

urlpatterns = [
path('admin/', admin.site.urls),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from rest_framework import permissions


class IsUserOrReadOnly(permissions.BasePermission):
class IsUserOrCreatingAccountOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Object-level permission that allows users to create accounts or edit their
own accounts.
"""

def has_object_permission(self, request, view, obj):
user_is_making_new_account = view.action == 'create'
if user_is_making_new_account:
return True

if request.method in permissions.SAFE_METHODS:
is_read_only_action = request.method in permissions.SAFE_METHODS
if is_read_only_action:
return True

return obj == request.user
is_accessing_their_own_user_object = obj == request.user
return is_accessing_their_own_user_object
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
from django.test import TestCase
from django.forms.models import model_to_dict
from django.contrib.auth.hashers import check_password
from nose.tools import eq_, ok_
import pytest
from .factories import UserFactory
from ..serializers import CreateUserSerializer


@pytest.mark.django_db
class TestCreateUserSerializer(TestCase):

def setUp(self):
self.user_data = model_to_dict(UserFactory.build())

def test_serializer_with_empty_data(self):
serializer = CreateUserSerializer(data={})
eq_(serializer.is_valid(), False)
assert serializer.is_valid() is False

def test_serializer_with_valid_data(self):
serializer = CreateUserSerializer(data=self.user_data)
ok_(serializer.is_valid())
assert serializer.is_valid()

def test_serializer_hashes_password(self):
serializer = CreateUserSerializer(data=self.user_data)
ok_(serializer.is_valid())
assert serializer.is_valid()

user = serializer.save()
ok_(check_password(self.user_data.get('password'), user.password))
assert check_password(self.user_data.get('password'), user.password)
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from django.urls import reverse
from django.contrib.auth.hashers import check_password
from nose.tools import ok_, eq_
from rest_framework.test import APITestCase
from rest_framework import status
from faker import Faker
import factory
import pytest
from ..models import User
from .factories import UserFactory

fake = Faker()


@pytest.mark.django_db
class TestUserListTestCase(APITestCase):
"""
Tests /users list operations.
Expand All @@ -22,15 +22,15 @@ def setUp(self):

def test_post_request_with_no_data_fails(self):
response = self.client.post(self.url, {})
eq_(response.status_code, status.HTTP_400_BAD_REQUEST)
assert response.status_code == status.HTTP_400_BAD_REQUEST

def test_post_request_with_valid_data_succeeds(self):
response = self.client.post(self.url, self.user_data)
eq_(response.status_code, status.HTTP_201_CREATED)
assert response.status_code == status.HTTP_201_CREATED

user = User.objects.get(pk=response.data.get('id'))
eq_(user.username, self.user_data.get('username'))
ok_(check_password(self.user_data.get('password'), user.password))
assert user.username == self.user_data.get('username')
assert check_password(self.user_data.get('password'), user.password)


class TestUserDetailTestCase(APITestCase):
Expand All @@ -45,13 +45,13 @@ def setUp(self):

def test_get_request_returns_a_given_user(self):
response = self.client.get(self.url)
eq_(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK

def test_put_request_updates_a_user(self):
new_first_name = fake.first_name()
payload = {'first_name': new_first_name}
response = self.client.put(self.url, payload)
eq_(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK

user = User.objects.get(pk=self.user.id)
eq_(user.first_name, new_first_name)
assert user.first_name == new_first_name
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
from rest_framework import viewsets, mixins
from rest_framework.permissions import AllowAny
from .models import User
from .permissions import IsUserOrReadOnly
from .permissions import IsUserOrCreatingAccountOrReadOnly
from .serializers import CreateUserSerializer, UserSerializer


class UserViewSet(mixins.RetrieveModelMixin,
class UserViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
"""
Updates and retrieves user accounts
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsUserOrReadOnly,)
permission_classes = (IsUserOrCreatingAccountOrReadOnly,)


class UserCreateViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
"""
Creates user accounts
"""
queryset = User.objects.all()
serializer_class = CreateUserSerializer
permission_classes = (AllowAny,)
def get_serializer_class(self):
is_creating_a_new_user = self.action == 'create'
if is_creating_a_new_user:
return CreateUserSerializer
return self.serializer_class