Skip to content

Commit

Permalink
chore: remove node-progressive (#2375)
Browse files Browse the repository at this point in the history
* chore: remove node-progressive

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* chore: update ci options

* chore: update ci settings location
  • Loading branch information
agconti authored Aug 30, 2024
1 parent 37a2263 commit 8e8cf0e
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 61 deletions.
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

0 comments on commit 8e8cf0e

Please sign in to comment.