sample api work

This commit is contained in:
mahdavi 2024-07-20 23:10:03 +03:30
parent dffa0a86f8
commit c18430c083
11 changed files with 325 additions and 38 deletions

View 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')),
],
),
]

View file

@ -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')},
),
]

View file

12
apps/crud/serializers.py Normal file
View 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
View 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

View file

@ -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),
),
]

View file

@ -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

View file

@ -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,
},

View file

@ -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',
),
]

View file

@ -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',
}
}

View file

@ -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)