irongit

Django + Next + Postgres + Docker + Nginx

need multiple monitors now

FIRST_NAME LAST_NAMEauthored
parent da78a2dcommit 222c5c7ef4dec09565d46b26a32aad1af136cec5Browse files

23 files changed, +355 -3

+0-0backend/api/__init__.py

No content changes (mode or rename only).

+3-0backend/api/admin.py
@@ -0,0 +1,3 @@
1+from django.contrib import admin
2+
3+# Register your models here.
+6-0backend/api/apps.py
@@ -0,0 +1,6 @@
1+from django.apps import AppConfig
2+
3+
4+class ApiConfig(AppConfig):
5+ default_auto_field = 'django.db.models.BigAutoField'
6+ name = 'api'
+0-0backend/api/auth/__init__.py

No content changes (mode or rename only).

+41-0backend/api/auth/serializers.py
@@ -0,0 +1,41 @@
1+from rest_framework import serializers
2+from django.contrib.auth.models import User
3+from rest_framework.exceptions import ValidationError, NotFound, AuthenticationFailed
4+from django.contrib.auth import login, logout, authenticate
5+
6+
7+class LoginSerializer(serializers.ModelSerializer):
8+ username = serializers.CharField(required=False)
9+ email = serializers.EmailField(required=False)
10+ password = serializers.CharField(write_only=True)
11+ class Meta:
12+ model = User
13+ fields = ['username', 'email', 'password']
14+
15+ def create(self, validated_data):
16+ request = self.context['request']
17+ logout(request)
18+ username = validated_data.get('username')
19+ email = validated_data.get('email')
20+ password = validated_data.get('password')
21+ if not username and not email:
22+ raise ValidationError('Requires username or email.')
23+ if username:
24+ user = authenticate(username=username, password=password)
25+ else:
26+ query = User.objects.filter(email=email)
27+ if not query:
28+ raise NotFound(f'{email} has no account.')
29+ username = query.first().username
30+ user = authenticate(username, password)
31+ if not user:
32+ raise AuthenticationFailed('Incorrect username or password.')
33+ login(self.context['request'], user)
34+ return user
35+
36+
37+class LogoutSerializer(serializers.Serializer):
38+ detail = serializers.CharField(read_only=True, default="Success")
39+
40+class LoggedInSerializer(serializers.Serializer):
41+ detail = serializers.BooleanField(read_only=True, default=False)
+11-0backend/api/auth/urls.py
@@ -0,0 +1,11 @@
1+from django.urls import path
2+from .views import LoginView, AuthRoot, LogoutView, LoggedInView
3+
4+
5+urlpatterns = [
6+ path('login', LoginView.as_view(), name='api-login'),
7+ path('', AuthRoot.as_view(), name='api-auth-root'),
8+ path('logout', LogoutView.as_view(), name='api-logout'),
9+ path('logged-in', LoggedInView.as_view(), name='api-logged-in')
10+]
11+
+66-0backend/api/auth/views.py
@@ -0,0 +1,66 @@
1+from rest_framework.generics import CreateAPIView, GenericAPIView, RetrieveAPIView
2+from rest_framework.views import APIView
3+from rest_framework.routers import APIRootView
4+from rest_framework.reverse import reverse
5+from rest_framework.response import Response
6+from rest_framework.permissions import IsAuthenticated
7+from rest_framework.schemas.openapi import AutoSchema
8+from rest_framework.permissions import AllowAny
9+from .serializers import LoginSerializer, LogoutSerializer, LoggedInSerializer
10+from django.contrib.auth.models import User
11+from django.contrib.auth import logout
12+
13+
14+class AuthRoot(APIRootView):
15+
16+ def get(self, request, *args, **kwargs):
17+ return Response({
18+ 'login': reverse('api-login', request=request),
19+ 'logout': reverse('api-logout', request=request),
20+ 'logged-in': reverse('api-logged-in', request=request)
21+ })
22+
23+
24+class LoginView(CreateAPIView):
25+ schema = AutoSchema(
26+ tags=['Auth'],
27+ component_name='login',
28+ operation_id_base='Login'
29+ )
30+ serializer_class = LoginSerializer
31+ queryset = User.objects.all()
32+ permission_classes = [AllowAny]
33+
34+
35+class LoggedInView(RetrieveAPIView):
36+ schema = AutoSchema(
37+ tags=['Auth'],
38+ component_name='logged-in',
39+ operation_id_base='LoggedIn'
40+ )
41+ serializer_class = LoggedInSerializer
42+
43+ def retrieve(self, request, *args, **kwargs):
44+ if request.user.is_authenticated:
45+ return Response({
46+ 'detail': True
47+ })
48+ return Response({'detail': False})
49+
50+
51+class LogoutView(RetrieveAPIView):
52+ schema = AutoSchema(
53+ tags=['Auth'],
54+ component_name='logout',
55+ operation_id_base='Logout'
56+ )
57+ serializer_class = LogoutSerializer
58+ permission_classes = [IsAuthenticated]
59+
60+ def retrieve(self, request):
61+ user = request.user
62+ logout(request)
63+ return Response({
64+ 'details': f'Successfully logged out {user}.'
65+ })
66+
+0-0backend/api/management/commands/__init__.py

No content changes (mode or rename only).

+74-0backend/api/management/commands/createapi.py
@@ -0,0 +1,74 @@
1+from django.core.management.base import BaseCommand, CommandError, CommandParser
2+from django.template.loader import render_to_string
3+from django.utils.html import strip_tags
4+import os
5+
6+
7+class Command(BaseCommand):
8+ # Show this when the user types help
9+ help = "Creates a new API. Usage: python manage.py createapi api_name [root_dir]"
10+
11+ def add_arguments(self, parser: CommandParser) -> None:
12+ parser.add_argument('model_name', type=str, help='The name of the API model')
13+ parser.add_argument('root_dir', type=str, nargs='?', default='api', help='The root directory for the API model. Defaults to "api"')
14+
15+ def handle(self, *args, **options):
16+ # Get the api name from the first argument
17+ model_name = options['model_name']
18+ api_name = model_name.lower()
19+ # Get the root directory
20+ root_dir = options['root_dir'].lower()
21+ # Create context for the templates
22+ context = {
23+ 'api_name': api_name,
24+ 'root_dir': root_dir,
25+ 'model_name': model_name,
26+ }
27+ # If the api model does not exist raise a CommandError
28+ try:
29+ # Test if the model exists
30+ getattr(__import__(root_dir).models, model_name)
31+ except:
32+ raise CommandError(f"Model {model_name} does not exist")
33+ # If the root directory does not exist raise a CommandError
34+ if not os.path.exists(root_dir):
35+ raise CommandError(f"Directory {root_dir} does not exist")
36+ # Create the api directory
37+ api_dir = os.path.join(root_dir, api_name)
38+ os.makedirs(api_dir, exist_ok=True)
39+ # Create an __init__.py file
40+ init_file = os.path.join(api_dir, '__init__.py')
41+ with open(init_file, 'w') as f:
42+ f.write('')
43+ # Load the viewset template and write it to the views file
44+ views_file = os.path.join(api_dir, 'views.py')
45+ with open(views_file, 'w') as f:
46+ f.write(strip_tags(render_to_string('api/viewset.html', context)))
47+ # Load the serializers template and write it to the serializers file
48+ serializers_file = os.path.join(api_dir, 'serializers.py')
49+ with open(serializers_file, 'w') as f:
50+ f.write(strip_tags(render_to_string('api/serializers.html', context)))
51+ # Load the urls template and write it to the urls file
52+ urls_file = os.path.join(api_dir, 'urls.py')
53+ with open(urls_file, 'w') as f:
54+ f.write(strip_tags(render_to_string('api/urls.html', context)))
55+ # Add the urls to the root urls file
56+ root_urls_file = os.path.join(root_dir, 'urls.py')
57+ with open(root_urls_file, 'r') as f:
58+ lines = f.readlines()
59+ with open(root_urls_file, 'w') as f:
60+ for line in lines:
61+ f.write(line)
62+ if 'Auto-generated' in line:
63+ f.write(f"\tre_path('', include('{root_dir}.{api_name}.urls')),\n")
64+ # Add the url to the api root
65+ api_root_file = os.path.join(root_dir, 'views.py')
66+ with open(api_root_file, 'r') as f:
67+ lines = f.readlines()
68+ with open(api_root_file, 'w') as f:
69+ for line in lines:
70+ f.write(line)
71+ if 'Auto-generated' in line:
72+ print('found user-list')
73+ f.write(f"\t\t\t'{api_name}': reverse('{api_name}-list', request=request),\n")
74+ self.stdout.write(self.style.SUCCESS(f"Successfully created API {api_name}"))
+3-0backend/api/models.py
@@ -0,0 +1,3 @@
1+from django.db import models
2+
3+# Create your models here.
+9-0backend/api/templates/api/serializers.html
@@ -0,0 +1,9 @@
1+<pre>from rest_framework import serializers
2+from {{root_dir}}.models import {{model_name}}
3+
4+
5+class {{model_name}}Serializer(serializers.HyperlinkedModelSerializer):
6+ class Meta:
7+ model = {{model_name}}
8+ fields = '__all__'
9+</pre>
+11-0backend/api/templates/api/urls.html
@@ -0,0 +1,11 @@
1+<pre>from django.urls import path, include
2+from api.{{api_name}}.views import {{model_name}}ViewSet
3+from rest_framework.routers import SimpleRouter
4+
5+router = SimpleRouter()
6+router.register(r'{{api_name}}', {{model_name}}ViewSet)
7+
8+urlpatterns = [
9+ path('', include(router.urls)),
10+]
11+</pre>
+20-0backend/api/templates/api/viewset.html
@@ -0,0 +1,20 @@
1+<pre>from rest_framework import viewsets
2+from rest_framework.schemas.openapi import AutoSchema
3+from rest_framework.permissions import AllowAny
4+from {{root_dir}}.models import {{model_name}}
5+from api.{{api_name}}.serializers import {{model_name}}Serializer
6+
7+
8+class {{model_name}}ViewSet(viewsets.ModelViewSet):
9+ """
10+ API endpoint that allows {{model_name}} to be viewed or edited.
11+ """
12+ schema = AutoSchema(
13+ tags=['{{model_name}}'],
14+ component_name='{{api_name}}',
15+ operation_id_base='{{model_name}}'
16+ )
17+ queryset = {{model_name}}.objects.all()
18+ serializer_class = {{model_name}}Serializer
19+ permission_classes = [AllowAny]
20+</pre>
+31-0backend/api/templates/swagger-ui.html
@@ -0,0 +1,31 @@
1+<!DOCTYPE html>
2+<html>
3+
4+<head>
5+ <title>Swagger</title>
6+ <meta charset="utf-8" />
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" />
9+</head>
10+
11+<body>
12+ <div id="swagger-ui"></div>
13+ <script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
14+ <script>
15+ const ui = SwaggerUIBundle({
16+ url: "{% url schema_url %}",
17+ dom_id: '#swagger-ui',
18+ presets: [
19+ SwaggerUIBundle.presets.apis,
20+ SwaggerUIBundle.SwaggerUIStandalonePreset
21+ ],
22+ layout: "BaseLayout",
23+ requestInterceptor: (request) => {
24+ request.headers['X-CSRFToken'] = "{{ csrf_token }}"
25+ return request;
26+ }
27+ })
28+ </script>
29+</body>
30+
31+</html>
+3-0backend/api/tests.py
@@ -0,0 +1,3 @@
1+from django.test import TestCase
2+
3+# Create your tests here.
+14-0backend/api/urls.py
@@ -0,0 +1,14 @@
1+from django.urls import include, re_path
2+from .views import ApiRoot, schema_view, swagger_view
3+
4+
5+urlpatterns = [
6+ # Auto-generated: a keyword used by the createapi command to know where to add new urls
7+ re_path('$', ApiRoot.as_view(), name='api-root'),
8+ re_path('', include('api.user.urls')),
9+ re_path('openapi', schema_view, name='openapi-schema'),
10+ re_path('swagger-ui', swagger_view, name='swagger-ui'),
11+ re_path('openapi', schema_view, name='openapi-schema'),
12+ re_path('swagger-ui', swagger_view, name='swagger-ui'),
13+ re_path('auth/', include('api.auth.urls')),
14+]
+9-0backend/api/user/serializers.py
@@ -0,0 +1,9 @@
1+from rest_framework import serializers
2+from django.contrib.auth.models import User
3+
4+
5+class UserSerializer(serializers.HyperlinkedModelSerializer):
6+ class Meta:
7+ model = User
8+ fields = ['url', 'id', 'username', 'first_name', 'last_name', 'email', 'password']
9+ extra_kwargs = {'password': {'write_only': True}}
+10-0backend/api/user/urls.py
@@ -0,0 +1,10 @@
1+from django.urls import path, include
2+from api.user.views import UserViewSet
3+from rest_framework.routers import SimpleRouter
4+
5+router = SimpleRouter()
6+router.register(r'user', UserViewSet)
7+
8+urlpatterns = [
9+ path('', include(router.urls)),
10+]
+19-0backend/api/user/views.py
@@ -0,0 +1,19 @@
1+from rest_framework import viewsets
2+from rest_framework.schemas.openapi import AutoSchema
3+from rest_framework.permissions import IsAuthenticated, AllowAny
4+from django.contrib.auth.models import User
5+from api.user.serializers import UserSerializer
6+
7+
8+class UserViewSet(viewsets.ModelViewSet):
9+ """
10+ API endpoint that allows users to be viewed or edited.
11+ """
12+ schema = AutoSchema(
13+ tags=['User'],
14+ component_name='user',
15+ operation_id_base='User'
16+ )
17+ queryset = User.objects.all().order_by('-date_joined')
18+ serializer_class = UserSerializer
19+ permission_classes = [AllowAny]
+21-0backend/api/views.py
@@ -0,0 +1,21 @@
1+from django.views.generic import TemplateView
2+from rest_framework.response import Response
3+from rest_framework.routers import APIRootView
4+from rest_framework.reverse import reverse
5+from rest_framework.schemas import get_schema_view
6+
7+schema_view = get_schema_view(title='Rango API')
8+swagger_view = TemplateView.as_view(template_name='swagger-ui.html', extra_context={'schema_url': 'openapi-schema'})
9+
10+
11+# Create your views here.
12+class ApiRoot(APIRootView):
13+
14+ def get(self, request, *args, **kwargs):
15+ return Response({
16+ # Auto-generated: a keyword used by the createapi command to know where to add new urls
17+ 'user': reverse('user-list', request=request),
18+ 'openapi': reverse('openapi-schema', request=request),
19+ 'swagger-ui': reverse('swagger-ui', request=request),
20+ 'auth': reverse('api-auth-root', request=request),
21+ })
+1-0backend/backend/settings.py
@@ -41,6 +41,7 @@ INSTALLED_APPS = [
4141 'django.contrib.messages',
4242 'django.contrib.staticfiles',
4343 'backend',
44+ 'api',
4445 ]
4546
4647 MIDDLEWARE = [
+1-1backend/backend/urls.py
@@ -14,7 +14,7 @@ Including another URLconf
1414 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1515 """
1616 from django.contrib import admin
17-from django.urls import path
17+from django.urls import path, include
1818
1919 urlpatterns = [
2020 path('admin/', admin.site.urls),
+2-2docker-compose.yml
@@ -1,7 +1,7 @@
11 services:
2- nppn:
2+ djext:
33 build: .
4- container_name: nppn
4+ container_name: djext
55 volumes:
66 - ./postgres:/var/lib/postgresql/data
77 ports: