sample api work
This commit is contained in:
parent
dffa0a86f8
commit
c18430c083
11 changed files with 325 additions and 38 deletions
37
apps/crud/migrations/0001_initial.py
Normal file
37
apps/crud/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-20 11:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Plan',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('record_count', models.IntegerField(default=0)),
|
||||
('record_limit', models.IntegerField(default=0)),
|
||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Data',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.JSONField(default=dict)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='crud.plan')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-20 14:04
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('crud', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='client_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='data',
|
||||
unique_together={('user', 'plan')},
|
||||
),
|
||||
]
|
||||
0
apps/crud/migrations/__init__.py
Normal file
0
apps/crud/migrations/__init__.py
Normal file
12
apps/crud/serializers.py
Normal file
12
apps/crud/serializers.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from rest_framework import serializers
|
||||
from .models import Data, Plan
|
||||
|
||||
|
||||
class DataSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Data
|
||||
fields = ('user',
|
||||
'data',
|
||||
)
|
||||
|
||||
read_only_fields = ['user']
|
||||
57
apps/crud/urls.py
Normal file
57
apps/crud/urls.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from rest_framework import routers
|
||||
from rest_framework.routers import DefaultRouter, DynamicRoute, Route
|
||||
from . import views
|
||||
|
||||
|
||||
class CRUDRouter(DefaultRouter):
|
||||
routes = [
|
||||
# List route.
|
||||
Route(
|
||||
url=r'^{prefix}{trailing_slash}$',
|
||||
mapping={
|
||||
'get': 'list',
|
||||
'post': 'create'
|
||||
},
|
||||
name='{basename}-list',
|
||||
detail=False,
|
||||
initkwargs={'suffix': 'List'}
|
||||
),
|
||||
# Dynamically generated list routes. Generated using
|
||||
# @action(detail=False) decorator on methods of the viewset.
|
||||
DynamicRoute(
|
||||
url=r'^{prefix}/{url_path}{trailing_slash}$',
|
||||
name='{basename}-{url_name}',
|
||||
detail=False,
|
||||
initkwargs={}
|
||||
),
|
||||
# Detail route.
|
||||
Route(
|
||||
url=r'^{prefix}/item{trailing_slash}$',
|
||||
mapping={
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'patch': 'partial_update',
|
||||
'delete': 'destroy'
|
||||
},
|
||||
name='{basename}-detail',
|
||||
detail=True,
|
||||
initkwargs={'suffix': 'Instance'}
|
||||
),
|
||||
# Dynamically generated detail routes. Generated using
|
||||
# @action(detail=True) decorator on methods of the viewset.
|
||||
DynamicRoute(
|
||||
url=r'^{prefix}/item/{url_path}{trailing_slash}$',
|
||||
name='{basename}-{url_name}',
|
||||
detail=True,
|
||||
initkwargs={}
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
router = CRUDRouter()
|
||||
|
||||
app_name = 'crud'
|
||||
|
||||
router.register('', views.DataViewSet, basename='crud')
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-04 11:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gooyal_oauth2', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accesstoken',
|
||||
name='client_owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
# models
|
||||
import uuid
|
||||
from django.db import models
|
||||
from oauth2_provider.models import AbstractApplication, AbstractAccessToken, AbstractGrant, AbstractRefreshToken, \
|
||||
AbstractIDToken
|
||||
|
||||
|
||||
class AccessToken(AbstractAccessToken):
|
||||
id=None
|
||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||
detail = models.JSONField(null=True, blank=True)
|
||||
client_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
client_owner = models.ForeignKey('users.User', on_delete=models.PROTECT, null=True, blank=True)
|
||||
|
|
@ -14,21 +17,29 @@ class AccessToken(AbstractAccessToken):
|
|||
|
||||
|
||||
class Application(AbstractApplication):
|
||||
id=None
|
||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||
class Meta:
|
||||
abstract = False
|
||||
|
||||
|
||||
class Grant(AbstractGrant):
|
||||
id = None
|
||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||
class Meta:
|
||||
abstract = False
|
||||
|
||||
|
||||
class RefreshToken(AbstractRefreshToken):
|
||||
id = None
|
||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||
class Meta:
|
||||
abstract = False
|
||||
|
||||
|
||||
class IDToken(AbstractIDToken):
|
||||
id = None
|
||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||
class Meta:
|
||||
abstract = False
|
||||
|
||||
|
|
|
|||
|
|
@ -115,15 +115,17 @@ class OAuth2Validator(BaseOAuth2Validator): # pylint: disable=w0223
|
|||
log.exception("Introspection: Failed to parse response as json")
|
||||
return None
|
||||
|
||||
client_owner_value = None
|
||||
owner_value = None
|
||||
application_uuid = None
|
||||
if "active" in application_introspection_content and application_introspection_content["active"] is True:
|
||||
if "client_owner" in application_introspection_content:
|
||||
client_owner_value = application_introspection_content["client_owner"]
|
||||
if "owner" in application_introspection_content:
|
||||
owner_value = application_introspection_content["owner"]
|
||||
application_uuid = application_introspection_content.get("uuid")
|
||||
|
||||
if client_owner_value:
|
||||
client_owner, _ = UserModel.objects.get_or_create(pk=client_owner_value)
|
||||
if owner_value:
|
||||
owner, _ = UserModel.objects.get_or_create(pk=owner_value)
|
||||
else:
|
||||
client_owner = None
|
||||
owner = None
|
||||
|
||||
if "username" in content:
|
||||
user = self.get_or_create_user_from_content(content)
|
||||
|
|
@ -149,13 +151,17 @@ class OAuth2Validator(BaseOAuth2Validator): # pylint: disable=w0223
|
|||
)
|
||||
|
||||
# TODO: get application owner and put it here
|
||||
application, _created = Application.objects.get_or_create(
|
||||
client_id=content["client_id"],
|
||||
uuid=application_introspection_content["uuid"]
|
||||
)
|
||||
access_token, _created = AccessToken.objects.update_or_create(
|
||||
token=token,
|
||||
defaults={
|
||||
"user": user,
|
||||
"client_id": content.get("client_id", ""),
|
||||
"client_owner": client_owner,
|
||||
"application": None,
|
||||
"client_id": content["client_id"],
|
||||
"client_owner": owner,
|
||||
"application_id": application_uuid,
|
||||
"scope": scope,
|
||||
"expires": expires,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-20 07:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0004_alter_oauthcode_code_verifier'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='balance',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='iban',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='iban_verified',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='last_checkout_request',
|
||||
),
|
||||
]
|
||||
|
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.0/ref/settings/
|
|||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
|
@ -23,39 +24,94 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||
SECRET_KEY = 'django-insecure-e_%y)zafwj5!_euf29(fv5ku#yw*#6nl%)ukiku2i8o#c@donr'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
DEBUG = config('DEBUG', default=True, cast=bool)
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# django apps
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# pip installed apps
|
||||
'drf_spectacular',
|
||||
'oauth2_provider',
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
'crispy_forms',
|
||||
'crispy_bootstrap5',
|
||||
'django_filters',
|
||||
'jalali_date',
|
||||
|
||||
# local apps
|
||||
'apps.core',
|
||||
'apps.users',
|
||||
'apps.gooyal_oauth2',
|
||||
'apps.crud',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
# 'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'oauth2_provider.middleware.OAuth2TokenMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
]
|
||||
|
||||
|
||||
OAUTH2_PROVIDER_APPLICATION_MODEL = "gooyal_oauth2.Application"
|
||||
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "gooyal_oauth2.AccessToken"
|
||||
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "gooyal_oauth2.IDToken"
|
||||
OAUTH2_PROVIDER_GRANT_MODEL = "gooyal_oauth2.Grant"
|
||||
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "gooyal_oauth2.RefreshToken"
|
||||
|
||||
BASE_OAUTH2_PROVIDER_URL = "https://accounts.gooyal.com"
|
||||
CLIENT_ID = config('CLIENT_ID')
|
||||
CLIENT_SECRET = config('CLIENT_SECRET')
|
||||
SCOPES = config('SCOPES', default='')
|
||||
OAUTH2_PROVIDER = {
|
||||
# this is the list of available scopes
|
||||
# 'SCOPES_BACKEND_CLASS': 'apps.gooyal_oauth2.scopes.Scopes',
|
||||
# 'SCOPES': {'read': 'Read scope',
|
||||
# 'write': 'Write scope',
|
||||
# 'groups': 'Access to your groups',
|
||||
# 'introspection': 'Introspect token scope'},
|
||||
'OAUTH2_VALIDATOR_CLASS': 'apps.gooyal_oauth2.validators.OAuth2Validator',
|
||||
'RESOURCE_SERVER_INTROSPECTION_URL': BASE_OAUTH2_PROVIDER_URL + '/oauth2/introspect/',
|
||||
# 'RESOURCE_SERVER_AUTH_TOKEN': '3yUqsWtwKYKHnfivFcJu', # OR this but not both:
|
||||
'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': (CLIENT_ID, CLIENT_SECRET),
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
),
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 20,
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
}
|
||||
|
||||
ROOT_URLCONF = 'data_crud.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates']
|
||||
,
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
|
@ -68,6 +124,10 @@ TEMPLATES = [
|
|||
},
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'oauth2_provider.backends.OAuth2Backend',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
)
|
||||
WSGI_APPLICATION = 'data_crud.wsgi.application'
|
||||
|
||||
|
||||
|
|
@ -76,8 +136,12 @@ WSGI_APPLICATION = 'data_crud.wsgi.application'
|
|||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': config('DB_NAME'),
|
||||
'USER': config('DB_USER'),
|
||||
'PASSWORD': config('DB_PASSWORD'),
|
||||
'HOST': config('DB_HOST', '127.0.0.1'),
|
||||
'PORT': config('DB_PORT', ''),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,15 +174,61 @@ TIME_ZONE = 'UTC'
|
|||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
LOCALE_PATHS = [
|
||||
BASE_DIR / 'locale',
|
||||
]
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||
|
||||
|
||||
MEDIA_URL = 'media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
if DEBUG == True:
|
||||
STATICFILES_DIRS = (
|
||||
BASE_DIR / 'static',
|
||||
)
|
||||
else:
|
||||
STATIC_ROOT = BASE_DIR / 'static'
|
||||
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
|
||||
|
||||
LOGIN_URL = '/users/login/request/'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'srv',
|
||||
'DESCRIPTION': 'service api reference',
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
# OTHER SETTINGS
|
||||
}
|
||||
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
CSRF_TRUSTED_ORIGINS = ['http://*.gooyal.com', 'https://*.gooyal.com']
|
||||
|
||||
JALALI_DATE_DEFAULTS = {
|
||||
'Strftime': {
|
||||
'date': '%Y/%m/%d',
|
||||
'datetime': '%H:%M:%S _ %Y/%m/%d',
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,31 @@ Including another URLconf
|
|||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
# from django.conf.urls import url
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path, include
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from rest_framework import permissions
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||
|
||||
|
||||
# from apps.gooyal_oauth2.views.introspect import introspect_token
|
||||
|
||||
from django.contrib.auth import urls as auth_urls
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('swagger/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('swagger/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
path('swagger/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
|
||||
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('apps.core.urls')),
|
||||
path('users/', include('apps.users.urls')),
|
||||
path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')),
|
||||
path('crud/', include('apps.crud.urls', namespace='crud')),
|
||||
]
|
||||
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue