Browse Source

Add user creation api

pull/2031/head
Hironsan 1 year ago
parent
commit
3930e89ad4
7 changed files with 176 additions and 12 deletions
  1. 4
      backend/config/settings/base.py
  2. 135
      backend/poetry.lock
  3. 2
      backend/pyproject.toml
  4. 19
      backend/users/tests/test_views.py
  5. 4
      backend/users/tests/utils.py
  6. 3
      backend/users/urls.py
  7. 21
      backend/users/views.py

4
backend/config/settings/base.py

@ -56,7 +56,11 @@ INSTALLED_APPS = [
"polymorphic",
"corsheaders",
"drf_yasg",
"allauth",
"allauth.account",
"allauth.socialaccount",
"dj_rest_auth",
"dj_rest_auth.registration",
"django_celery_results",
"django_drf_filepond",
"health_check",

135
backend/poetry.lock

@ -186,6 +186,17 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "cffi"
version = "1.15.1"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]]
name = "chardet"
version = "4.0.0"
@ -298,6 +309,25 @@ python-versions = ">=3.7"
[package.extras]
toml = ["tomli"]
[[package]]
name = "cryptography"
version = "38.0.2"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "defusedxml"
version = "0.7.1"
@ -316,7 +346,7 @@ python-versions = "*"
[[package]]
name = "dj-rest-auth"
version = "2.2.3"
version = "2.2.5"
description = "Authentication and Registration in Django Rest Framework"
category = "main"
optional = false
@ -324,10 +354,11 @@ python-versions = ">=3.5"
[package.dependencies]
Django = ">=2.0"
django-allauth = {version = ">=0.40.0,<0.51.0", optional = true, markers = "extra == \"with_social\""}
djangorestframework = ">=3.7.0"
[package.extras]
with_social = ["django-allauth (>=0.40.0,<0.48.0)"]
with_social = ["django-allauth (>=0.40.0,<0.51.0)"]
[[package]]
name = "django"
@ -347,6 +378,21 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "django-allauth"
version = "0.50.0"
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Django = ">=2.0"
pyjwt = {version = ">=1.7", extras = ["crypto"]}
python3-openid = ">=3.0.8"
requests = "*"
requests-oauthlib = ">=0.3.0"
[[package]]
name = "django-celery-results"
version = "2.4.0"
@ -930,6 +976,19 @@ category = "main"
optional = false
python-versions = ">=3.8"
[[package]]
name = "oauthlib"
version = "3.2.2"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openpyxl"
version = "3.0.9"
@ -1072,6 +1131,14 @@ category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pycparser"
version = "2.21"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pydantic"
version = "1.9.0"
@ -1142,6 +1209,23 @@ category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pyjwt"
version = "2.6.0"
description = "JSON Web Token implementation in Python"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
[package.extras]
crypto = ["cryptography (>=3.4.0)"]
dev = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.4.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "pre-commit"]
docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
[[package]]
name = "pyparsing"
version = "3.0.7"
@ -1187,6 +1271,21 @@ python-versions = ">=3.5"
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "python3-openid"
version = "3.2.0"
description = "OpenID support for modern servers and consumers."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
defusedxml = "*"
[package.extras]
mysql = ["mysql-connector-python"]
postgresql = ["psycopg2"]
[[package]]
name = "pytz"
version = "2021.3"
@ -1213,6 +1312,21 @@ urllib3 = ">=1.21.1,<1.27"
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "requests-oauthlib"
version = "1.3.1"
description = "OAuthlib authentication support for Requests."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
oauthlib = ">=3.0.0"
requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "rsa"
version = "4.8"
@ -1554,7 +1668,7 @@ postgresql = []
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "f56b900c4334045e2ce4e2dd4f57262e9ea236435e89c07c9ab53c4559050cf6"
content-hash = "a93579bf687bac1c8f4132105b0beb395c14e4151d40f81cd9bdbd490faf2c2c"
[metadata.files]
amqp = [
@ -1637,6 +1751,7 @@ certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
cffi = []
chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
@ -1716,6 +1831,7 @@ coverage = [
{file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"},
{file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"},
]
cryptography = []
defusedxml = [
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
@ -1724,10 +1840,9 @@ dj-database-url = [
{file = "dj-database-url-0.5.0.tar.gz", hash = "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163"},
{file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"},
]
dj-rest-auth = [
{file = "dj-rest-auth-2.2.3.tar.gz", hash = "sha256:f292f3dffb8fc896da10adf47e94a254fb8bf42e672a066e63566363d764ad42"},
]
dj-rest-auth = []
django = []
django-allauth = []
django-celery-results = []
django-cleanup = [
{file = "django-cleanup-6.0.0.tar.gz", hash = "sha256:922e06ef8839c92bd3ab37a84db6058b8764f3fe44dbb4487bbca941d288280a"},
@ -2084,6 +2199,7 @@ numpy = [
{file = "numpy-1.22.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a176959b6e7e00b5a0d6f549a479f869829bfd8150282c590deee6d099bbb6e"},
{file = "numpy-1.22.2.zip", hash = "sha256:076aee5a3763d41da6bef9565fdf3cb987606f567cd8b104aded2b38b7b47abf"},
]
oauthlib = []
openpyxl = [
{file = "openpyxl-3.0.9-py2.py3-none-any.whl", hash = "sha256:8f3b11bd896a95468a4ab162fc4fcd260d46157155d1f8bfaabb99d88cfcf79f"},
{file = "openpyxl-3.0.9.tar.gz", hash = "sha256:40f568b9829bf9e446acfffce30250ac1fa39035124d55fc024025c41481c90f"},
@ -2204,6 +2320,10 @@ pycodestyle = [
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
]
pycparser = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydantic = [
{file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"},
{file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"},
@ -2257,6 +2377,7 @@ pyflakes = [
{file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
]
pyjwt = []
pyparsing = [
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
@ -2273,6 +2394,7 @@ python-dotenv = [
{file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
{file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
]
python3-openid = []
pytz = [
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
{file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
@ -2281,6 +2403,7 @@ requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
requests-oauthlib = []
rsa = [
{file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"},
{file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"},

2
backend/pyproject.toml

@ -52,7 +52,7 @@ dj-database-url = "^0.5.0"
pyexcel-xlsx = "^0.6.0"
gunicorn = "^20.1.0"
auto-labeling-pipeline = "^0.1.21"
dj-rest-auth = "^2.2.3"
dj-rest-auth = {extras = ["with_social"], version = "^2.2.5"}
django-drf-filepond = "^0.4.1"
celery = "^5.2.3"
django-celery-results = "2.4.0"

19
backend/users/tests/test_views.py

@ -38,3 +38,22 @@ class TestMeAPI(APITestCase):
def test_does_not_return_information_to_unauthenticated_user(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class TestUserCreationAPI(APITestCase):
@classmethod
def setUpTestData(cls):
cls.staff = make_user(username="bob", is_staff=True)
cls.non_staff = make_user(username="tom", is_staff=False)
cls.url = reverse(viewname="user_create")
cls.payload = {"username": "hironsan", "password1": "foobarbaz", "password2": "foobarbaz"}
def test_staff_can_create_user(self):
self.client.force_login(self.staff)
response = self.client.post(self.url, data=self.payload)
self.assertEqual(response.data["username"], "hironsan")
def test_non_staff_cannot_create_user(self):
self.client.force_login(self.non_staff)
response = self.client.post(self.url, data=self.payload)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

4
backend/users/tests/utils.py

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model
def make_user(username: str = "bob"):
def make_user(username: str = "bob", is_staff: bool = False):
user_model = get_user_model()
user, _ = user_model.objects.get_or_create(username=username, password="pass")
user, _ = user_model.objects.get_or_create(username=username, password="pass", is_staff=is_staff)
return user

3
backend/users/urls.py

@ -1,9 +1,10 @@
from django.urls import include, path
from .views import Me, Users
from .views import Me, UserCreation, Users
urlpatterns = [
path(route="me", view=Me.as_view(), name="me"),
path(route="users", view=Users.as_view(), name="user_list"),
path(route="users/create", view=UserCreation.as_view(), name="user_create"),
path("auth/", include("dj_rest_auth.urls")),
]

21
backend/users/views.py

@ -1,7 +1,8 @@
from dj_rest_auth.registration.serializers import RegisterSerializer
from django.contrib.auth.models import User
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters, generics
from rest_framework.permissions import IsAuthenticated
from rest_framework import filters, generics, status
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
@ -24,3 +25,19 @@ class Users(generics.ListAPIView):
pagination_class = None
filter_backends = (DjangoFilterBackend, filters.SearchFilter)
search_fields = ("username",)
class UserCreation(generics.CreateAPIView):
serializer_class = RegisterSerializer
permission_classes = [IsAuthenticated & IsAdminUser]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
user = serializer.save(self.request)
return user
Loading…
Cancel
Save