Compare commits

..

10 commits

Author SHA1 Message Date
Sayyid Hamid Mahdavi
709ee8c454 add application Preferences 2025-06-10 15:51:09 +03:30
Sayyid Hamid Mahdavi
53786a7a4a add application Preferences 2025-06-10 15:38:56 +03:30
Sayyid Hamid Mahdavi
4dca5cea43 add application Preferences 2025-06-10 15:38:45 +03:30
sahama
2e8f58968b "rest_framework.parsers.JSONParser", 2024-08-12 14:11:07 +03:30
sahama
0a735c1ebd get_filter_link 2024-08-08 00:32:26 +03:30
sahama
b14482e383 get_filter_link 2024-08-07 19:01:26 +03:30
sahama
957b2705f3 color 2024-08-07 18:42:04 +03:30
sahama
579e959b1b color 2024-08-07 18:34:49 +03:30
sahama
623d98de5b new oauth urls 2024-08-07 17:24:05 +03:30
mahdavi
2ba7a2e1e9 add application to data json 2024-07-29 17:22:56 +03:30
12 changed files with 98 additions and 23 deletions

View file

@ -28,3 +28,11 @@ def get_proper_elided_page_range(p, number, on_each_side=2, on_ends=2):
@register.filter @register.filter
def get_item(dictionary, key): def get_item(dictionary, key):
return dictionary.get(key) return dictionary.get(key)
@register.simple_tag
def get_filter_link(request, key, value):
qs: str = request.GET.urlencode()
qs_dict = request.GET.dict()
qs_dict.update({key: value})
return '&'.join(f'{k}={v}' for k, v in qs_dict.items())
# return 'page={{ i }}{% for fil, fil_value in filter.data.items %}{% if fil != 'page' %}&{{fil}}={{fil_value}}{% endif %}{% endfor %}">{{ i }}</a></li>

View file

@ -0,0 +1,26 @@
# Generated by Django 5.1.4 on 2025-06-10 12:07
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crud', '0003_alter_plan_owner'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='data',
name='plan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='crud.plan'),
),
migrations.AlterField(
model_name='data',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

View file

@ -12,9 +12,15 @@ class Plan(models.Model):
class Data(models.Model): class Data(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.PROTECT, null=True, blank=True)
plan = models.ForeignKey(Plan, on_delete=models.PROTECT) plan = models.ForeignKey(Plan, on_delete=models.PROTECT, null=True, blank=True)
data = models.JSONField(default=dict) data = models.JSONField(default=dict)
@property
def application(self):
return str(self.plan.application.uuid)
class Meta: class Meta:
unique_together = ('user', 'plan') unique_together = ('user', 'plan')

View file

@ -5,8 +5,7 @@ from .models import Data, Plan
class DataSerializer(serializers.ModelSerializer): class DataSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Data model = Data
fields = ('user', fields = (
'data', 'data',
) )
read_only_fields = ['user']

View file

@ -26,7 +26,7 @@ class CRUDRouter(DefaultRouter):
), ),
# Detail route. # Detail route.
Route( Route(
url=r'^{prefix}/item{trailing_slash}$', url=r'^{prefix}{trailing_slash}$',
mapping={ mapping={
'get': 'retrieve', 'get': 'retrieve',
'put': 'update', 'put': 'update',
@ -40,7 +40,7 @@ class CRUDRouter(DefaultRouter):
# Dynamically generated detail routes. Generated using # Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset. # @action(detail=True) decorator on methods of the viewset.
DynamicRoute( DynamicRoute(
url=r'^{prefix}/item/{url_path}{trailing_slash}$', url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}', name='{basename}-{url_name}',
detail=True, detail=True,
initkwargs={} initkwargs={}
@ -52,6 +52,7 @@ router = CRUDRouter()
app_name = 'crud' app_name = 'crud'
router.register('', views.DataViewSet, basename='crud') router.register('user', views.UserDataViewSet, basename='user-data')
router.register('application', views.ApplicationDataViewSet, basename='application-data')
urlpatterns = router.urls urlpatterns = router.urls

View file

@ -7,10 +7,12 @@ from rest_framework.viewsets import GenericViewSet
from apps.crud.models import Data, Plan from apps.crud.models import Data, Plan
from apps.crud.serializers import DataSerializer from apps.crud.serializers import DataSerializer
from apps.gooyal_oauth2.models import Application
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
from apps.gooyal_oauth2.utils import get_application
class DataViewSet(mixins.RetrieveModelMixin, class UserDataViewSet(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
GenericViewSet): GenericViewSet):
@ -28,7 +30,7 @@ class DataViewSet(mixins.RetrieveModelMixin,
def get_object(self): def get_object(self):
user = self.request.user user = self.request.user
auth = self.request.auth auth = self.request.auth
if auth: if auth and user:
# plan = Plan.objects.filter(application_id=auth.application_id).first() # plan = Plan.objects.filter(application_id=auth.application_id).first()
plan, created = Plan.objects.get_or_create(application_id=auth.application_id) plan, created = Plan.objects.get_or_create(application_id=auth.application_id)
# data, created = Data.objects.get_or_create(plan_id=plan.pk, user=user) # data, created = Data.objects.get_or_create(plan_id=plan.pk, user=user)
@ -36,3 +38,25 @@ class DataViewSet(mixins.RetrieveModelMixin,
return data return data
raise Http404 raise Http404
class ApplicationDataViewSet(mixins.RetrieveModelMixin,
GenericViewSet):
queryset = Application.objects.all()
serializer_class = DataSerializer
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
required_alternate_scopes = {
"GET": [["data_crud.data:retrieve"]],
}
def get_object(self):
application = get_application(self.request)
if application:
# plan = Plan.objects.filter(application_id=auth.application_id).first()
plan, created = Plan.objects.get_or_create(application=application)
# data, created = Data.objects.get_or_create(plan_id=plan.pk, user=user)
data, created = Data.objects.get_or_create(plan=plan, user=None)
return data
raise Http404

View file

@ -0,0 +1,6 @@
def get_application(request):
try:
application = request.auth.application
except:
application = None
return application

View file

@ -91,7 +91,7 @@ class OAuthCode(models.Model):
return code_challenge return code_challenge
def generate_login_url(self): def generate_login_url(self):
url = f'''{settings.BASE_OAUTH2_PROVIDER_URL}/oauth2/authorize/?response_type=code&code_challenge={self.generate_code_challenge()}&code_challenge_method=S256&client_id={settings.CLIENT_ID}&scope={settings.SCOPES}&state={self.uuid}''' url = f'''{settings.BASE_OAUTH2_PROVIDER_PUBLIC_URL}/authorize/?response_type=code&code_challenge={self.generate_code_challenge()}&code_challenge_method=S256&client_id={settings.CLIENT_ID}&scope={settings.SCOPES}&state={self.uuid}'''
return url return url
def validate_code(self): def validate_code(self):
@ -107,6 +107,6 @@ class OAuthCode(models.Model):
"grant_type": "authorization_code" "grant_type": "authorization_code"
} }
response = requests.post(f'{settings.BASE_OAUTH2_PROVIDER_URL}/oauth2/token/', data=data, headers=headers) response = requests.post(f'{settings.BASE_OAUTH2_PROVIDER_PRIVATE_URL}/token/', data=data, headers=headers)
return response.json() return response.json()

View file

@ -76,7 +76,9 @@ OAUTH2_PROVIDER_ID_TOKEN_MODEL = "gooyal_oauth2.IDToken"
OAUTH2_PROVIDER_GRANT_MODEL = "gooyal_oauth2.Grant" OAUTH2_PROVIDER_GRANT_MODEL = "gooyal_oauth2.Grant"
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "gooyal_oauth2.RefreshToken" OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "gooyal_oauth2.RefreshToken"
BASE_OAUTH2_PROVIDER_URL = "https://accounts.gooyal.com" BASE_OAUTH2_PROVIDER_PUBLIC_URL = config("BASE_OAUTH2_PROVIDER_PUBLIC_URL")
BASE_OAUTH2_PROVIDER_PRIVATE_URL = config("BASE_OAUTH2_PROVIDER_PRIVATE_URL")
CLIENT_ID = config('CLIENT_ID') CLIENT_ID = config('CLIENT_ID')
CLIENT_SECRET = config('CLIENT_SECRET') CLIENT_SECRET = config('CLIENT_SECRET')
SCOPES = config('SCOPES', default='') SCOPES = config('SCOPES', default='')
@ -88,7 +90,7 @@ OAUTH2_PROVIDER = {
# 'groups': 'Access to your groups', # 'groups': 'Access to your groups',
# 'introspection': 'Introspect token scope'}, # 'introspection': 'Introspect token scope'},
'OAUTH2_VALIDATOR_CLASS': 'apps.gooyal_oauth2.validators.OAuth2Validator', 'OAUTH2_VALIDATOR_CLASS': 'apps.gooyal_oauth2.validators.OAuth2Validator',
'RESOURCE_SERVER_INTROSPECTION_URL': BASE_OAUTH2_PROVIDER_URL + '/oauth2/introspect/', 'RESOURCE_SERVER_INTROSPECTION_URL': BASE_OAUTH2_PROVIDER_PRIVATE_URL + '/introspect/',
# 'RESOURCE_SERVER_AUTH_TOKEN': '3yUqsWtwKYKHnfivFcJu', # OR this but not both: # 'RESOURCE_SERVER_AUTH_TOKEN': '3yUqsWtwKYKHnfivFcJu', # OR this but not both:
'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': (CLIENT_ID, CLIENT_SECRET), 'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': (CLIENT_ID, CLIENT_SECRET),
} }
@ -220,6 +222,8 @@ SPECTACULAR_SETTINGS = {
'VERSION': '1.0.0', 'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False, 'SERVE_INCLUDE_SCHEMA': False,
# OTHER SETTINGS # OTHER SETTINGS
"PARSER_WHITELIST": ["rest_framework.parsers.JSONParser"],
} }
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = 'users.User'

View file

@ -37,7 +37,7 @@ urlpatterns = [
path('', include('apps.core.urls')), path('', include('apps.core.urls')),
path('users/', include('apps.users.urls')), path('users/', include('apps.users.urls')),
path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')), path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')),
path('crud/', include('apps.crud.urls', namespace='crud')), path('data/', include('apps.crud.urls', namespace='crud')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -42,8 +42,9 @@
<div class="row vh-100 overflow-auto"> <div class="row vh-100 overflow-auto">
{# {% include "include/sidebar.html" %}#} {# {% include "include/sidebar.html" %}#}
<div class="col d-flex flex-column h-sm-100"> <div class="col d-flex flex-column h-sm-100">
<div class="m-0 p-0 w-100 " style="background-color: yellow; height: 3px;"></div>
<main class="row overflow-auto pb-3"> <main class="row overflow-auto pb-3">
<div class="col pt-3"> <div class="col pt-2">
{% include "include/top_bar.html" %} {% include "include/top_bar.html" %}
{% include "include/messages.html" %} {% include "include/messages.html" %}
{# {% block breadcrumbs %}#} {# {% block breadcrumbs %}#}

View file

@ -3,8 +3,8 @@
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% load jalali_tags %} {% load jalali_tags %}
{% block title %}wallet: Home{% endblock %} {% block title %}CRUD: Home{% endblock %}
{% block page_title %}wallet: Home{% endblock %} {% block page_title %}CRUD: Home{% endblock %}
{% block content %} {% block content %}