new pages for applications

This commit is contained in:
sahama 2024-08-03 16:52:09 +03:30
parent 99ef4287bd
commit 19d5742563
11 changed files with 297 additions and 25 deletions

View file

@ -5,6 +5,7 @@ Django forms for use with the gooyal-restrict-scopes package.
from django import forms from django import forms
from oauth2_provider.scopes import get_scopes_backend from oauth2_provider.scopes import get_scopes_backend
from .models import Application
class DelimitedListField(forms.MultipleChoiceField): class DelimitedListField(forms.MultipleChoiceField):
@ -35,12 +36,12 @@ class ApplicationForm(forms.ModelForm):
""" """
Form for creating or updating a restricted application. Form for creating or updating a restricted application.
""" """
#  allowed_scope is a space-delimited list, but we want to present # allowed_scope is a space-delimited list, but we want to present
#  a selection of valid scopes with checkboxes # a selection of valid scopes with checkboxes
allowed_scope = DelimitedListField( allowed_scope = DelimitedListField(
label='Allowed scopes', label='Allowed scopes',
#  The choices and initial values are callables, because the scopes might # The choices and initial values are callables, because the scopes might
#  not be available at import type, e.g. if coming from the database # not be available at import type, e.g. if coming from the database
choices=lambda: get_scopes_backend().get_all_scopes().items(), choices=lambda: get_scopes_backend().get_all_scopes().items(),
initial=lambda: get_scopes_backend().get_default_scopes(), initial=lambda: get_scopes_backend().get_default_scopes(),
delimiter=' ', delimiter=' ',
@ -49,3 +50,30 @@ class ApplicationForm(forms.ModelForm):
class Meta: class Meta:
exclude = () exclude = ()
class ApplicationCreateForm(forms.ModelForm):
available_scopes_list = forms.CharField(widget=forms.Textarea(),required=False)
class Meta:
model = Application
fields = ['name',
'client_id',
'client_secret',
# 'available_scopes_list',
# 'authorization_grant_type',
'redirect_uris',
'post_logout_redirect_uris']
readonly_fields = ['client_id', 'client_secret']
class ApplicationUpdateForm(forms.ModelForm):
class Meta:
model = Application
fields = ['name',
'client_id',
'client_secret',
# 'authorization_grant_type',
'redirect_uris',
'post_logout_redirect_uris']
readonly_fields = ['client_id', 'client_secret']

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-08-03 12:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gooyal_oauth2', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='application',
name='name',
field=models.CharField(blank=True, max_length=255, unique=True),
),
]

View file

@ -30,7 +30,7 @@ class Application(AbstractApplication):
""" """
id=None id=None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
name = models.CharField(max_length=255, blank=False, unique=True)
user = models.ForeignKey( user = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
related_name="%(app_label)s_%(class)s", related_name="%(app_label)s_%(class)s",
@ -54,6 +54,10 @@ class Application(AbstractApplication):
app_scopes = set(self.allowed_scope.split()) app_scopes = set(self.allowed_scope.split())
return app_scopes.intersection(all_scopes) return app_scopes.intersection(all_scopes)
# @property
# def available_scopes(self):
def allows_grant_type(self, *grant_types): def allows_grant_type(self, *grant_types):
# Assume, for this example, that self.authorization_grant_type is set to self.GRANT_AUTHORIZATION_CODE # Assume, for this example, that self.authorization_grant_type is set to self.GRANT_AUTHORIZATION_CODE
@ -72,13 +76,13 @@ class Scope(models.Model):
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
application = models.ForeignKey( application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL, oauth2_settings.APPLICATION_MODEL,
models.CASCADE, models.PROTECT,
# This field is nullable because it is only set for scopes created by # This field is nullable because it is only set for scopes created by
# external resource servers, which have a corresponding OAuth application # external resource servers, which have a corresponding OAuth application
# record on the authorisation server # record on the authorisation server
blank=True, null=True, blank=True, null=True,
help_text='The application to which the scope belongs.', help_text='The application to which the scope belongs.',
related_name='scopes' related_name='available_scopes'
) )
resource = models.ForeignKey( resource = models.ForeignKey(
Resource, Resource,
@ -132,11 +136,11 @@ class Scope(models.Model):
""" """
endpoint = settings.RESOURCE_SERVER_REGISTER_SCOPE_URL endpoint = settings.RESOURCE_SERVER_REGISTER_SCOPE_URL
if endpoint: if endpoint:
#  If the endpoint is set, make the callout to the authz server # If the endpoint is set, make the callout to the authz server
token = "Bearer {}".format(oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN) token = "Bearer {}".format(oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN)
#  Let any failures bubble up # Let any failures bubble up
# The idea is to call this method during deployment as a post-migrate # The idea is to call this method during deployment as a post-migrate
#  hook, so we want failures to halt the deployment # hook, so we want failures to halt the deployment
response = requests.post( response = requests.post(
endpoint, endpoint,
json={ json={

View file

@ -1,11 +1,12 @@
from django.urls import re_path from django.urls import re_path, path
from oauth2_provider import views from oauth2_provider import views
from rest_framework import routers from rest_framework import routers
from .views.introspect import IntrospectTokenView, IntrospectApplicationView from .views.introspect import IntrospectTokenView, IntrospectApplicationView
from .views import apis as api_views from .views import apis as api_views
app_name = "oauth2_provider" from .views import pages
app_name = "gooyal_oauth2"
base_urlpatterns = [ base_urlpatterns = [
@ -20,19 +21,19 @@ router = routers.SimpleRouter()
router.register(r'apis/applications', api_views.ApplicationViewSet, basename='application') router.register(r'apis/applications', api_views.ApplicationViewSet, basename='application')
management_urlpatterns = [ management_urlpatterns = [
# Application management views # re_path(r"^applications/$", views.ApplicationList.as_view(), name="application_list"),
re_path(r"^applications/$", views.ApplicationList.as_view(), name="list"), path("applications/", pages.ApplicationListView.as_view(), name="application_list"),
re_path(r"^applications/register/$", views.ApplicationRegistration.as_view(), name="register"), path('applications/register/', pages.ApplicationCreateView.as_view(), name='application_create'),
re_path(r"^applications/(?P<pk>[\w-]+)/$", views.ApplicationDetail.as_view(), name="detail"), path("applications/<pk>/", pages.ApplicationDetailView.as_view(), name="application_detail"),
re_path(r"^applications/(?P<pk>[\w-]+)/delete/$", views.ApplicationDelete.as_view(), name="delete"), # re_path(r"^applications/(?P<pk>[\w-]+)/delete/$", views.ApplicationDelete.as_view(), name="delete"),
re_path(r"^applications/(?P<pk>[\w-]+)/update/$", views.ApplicationUpdate.as_view(), name="update"), path("applications/<pk>/update/", pages.ApplicationUpdateView.as_view(), name="application_update"),
# Token management views # Token management views
re_path(r"^authorized_tokens/$", views.AuthorizedTokensListView.as_view(), name="authorized-token-list"), # re_path(r"^authorized_tokens/$", views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
re_path( # re_path(
r"^authorized_tokens/(?P<pk>[\w-]+)/delete/$", # r"^authorized_tokens/(?P<pk>[\w-]+)/delete/$",
views.AuthorizedTokenDeleteView.as_view(), # views.AuthorizedTokenDeleteView.as_view(),
name="authorized-token-delete", # name="authorized-token-delete",
), # ),
] ]
oidc_urlpatterns = [ oidc_urlpatterns = [

View file

@ -0,0 +1,61 @@
from django.contrib.auth.decorators import login_required
from django.urls import resolve, reverse_lazy
from django.utils.decorators import method_decorator
from django.views.generic import CreateView, DetailView, ListView, UpdateView
from oauth2_provider.models import get_application_model
from apps.gooyal_oauth2.forms import ApplicationCreateForm
from apps.gooyal_oauth2.models import Application
@method_decorator(login_required, name='dispatch')
class ApplicationCreateView(CreateView):
template_name = "gooyal_oauth2/application_create.html"
form_class = ApplicationCreateForm
def get_success_url(self):
return reverse_lazy("gooyal_oauth2:application_detail", kwargs={"pk": self.object.pk})
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.client_type = Application.CLIENT_PUBLIC
# form.instance.client_type = Application.CLIENT_PUBLIC
return super().form_valid(form)
@method_decorator(login_required, name='dispatch')
class ApplicationUpdateView(UpdateView):
template_name = "gooyal_oauth2/application_create.html"
form_class = ApplicationCreateForm
def get_queryset(self):
return Application.objects.filter(user=self.request.user)
def get_success_url(self):
return reverse_lazy("gooyal_oauth2:application_detail", kwargs={"pk": self.object.pk})
def form_valid(self, form):
# form.instance.user = self.request.user
# form.instance.client_type = Application.CLIENT_PUBLIC
return super().form_valid(form)
@method_decorator(login_required, name='dispatch')
class ApplicationDetailView(DetailView):
model = Application
template_name = "gooyal_oauth2/application_create.html"
def get_queryset(self):
return Application.objects.filter(user=self.request.user)
@method_decorator(login_required, name='dispatch')
class ApplicationListView(ListView):
model = Application
template_name = "gooyal_oauth2/application_list.html"
paginate_by = 20
def get_queryset(self):
return Application.objects.filter(user=self.request.user)

View file

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load jalali_tags %}
{% block title %}Application: create{% endblock %}
{% block page_title %}Application: create{% endblock %}
{% block content %}
<form action="{{ request.get_full_path }}" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="col-md-12 col-lg-12" id="formset">
{{ form|crispy }}
</div>
<div class="col-12">
<div class="grid">
<div class="d-flex justify-content-end">
<input class="btn btn-success ms-2" type="submit" value="ثبت" name="save"/>
</div>
</div>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load jalali_tags %}
{% block title %}Application: detail{% endblock %}
{% block page_title %}Application: detail{% endblock %}
{% block content %}
<p>application: {{ object.name }}!</p>
<p>client id: {{ object.client_id }}!</p>
{% endblock %}

View file

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% load static %}
{% load jalali_tags %}
{% load crispy_forms_tags %}
{% block title %}Application: list{% endblock %}
{% block page_title %}Application: list{% endblock %}
{% block page_action %}
{% include "include/filter_action.html" %}
<div class="me-2">
<a href="{% url 'gooyal_oauth2:application_create' %}">
<div class="btn btn-success">
<span class="bi bi-info-circle-fill"></span>
</div>
</a>
</div>
{% endblock %}
{% block content %}
<table class="table">
<tr>
<th>name</th>
<th>client id</th>
<th>actions</th>
</tr>
{% for obj in object_list %}
<tr>
<td>{{ obj.name }}</td>
<td>{{ obj.client_id }}</td>
<td>
<div class="grid">
<div class="d-flex justify-content-end">
<div class="me-2">
<a href="{% url 'gooyal_oauth2:application_detail' pk=obj.pk %}">
<div class="btn btn-success">
<span class="bi bi-info-circle-fill"></span>
</div>
</a>
</div>
<div class="me-2">
<a href="{% url 'gooyal_oauth2:application_update' pk=obj.pk %}">
<div class="btn btn-success">
<span class="bi bi-info-circle-fill"></span>
</div>
</a>
</div>
</div>
</div>
</td>
</tr>
{% endfor %}
</table>
{% include 'include/paginator.html' %}
{% endblock %}

View file

@ -0,0 +1,33 @@
{% load crispy_forms_tags %}
<button type="button" class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#filterModal">
<span class="bi bi-funnel-fill"></span>
</button>
<!-- Modal -->
<div class="modal fade" id="filterModal" tabindex="-1" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<form method="get">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">محدود کردن نتایج</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="بستن"></button>
</div>
<div class="modal-body">
{{ filter.form | crispy }}
</div>
<div class="modal-footer">
{# <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>#}
<input type="submit" class="btn btn-primary" value="اعمال">
{# <button type="button" class="btn btn-primary"></button>#}
</div>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,35 @@
{% load tags %}
<nav class="d-flex justify-content-center" aria-label="Page navigation example">
<ul class="pagination">
{% if is_paginated %}
{% get_proper_elided_page_range paginator page_obj.number as page_range %}
{% for i in page_range %}
{% if page_obj.number == i %}
<li class="page-item"><span class="page-link"><strong>( {{ i }} )</strong></span></li>
{% elif i == '…' %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ i }}{% for fil, fil_value in filter.data.items %}{% if fil != 'page' %}&{{fil}}={{fil_value}}{% endif %}{% endfor %}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% endif %}
<li class="p-2"><span class="fw-bolder text-decoration-underline">{{ paginator.count }}</span> داده یافت شد.</li>
</ul>
</nav>
{# <nav aria-label="Page navigation example">#}
{# <ul class="pagination">#}
{# {% if is_paginated %}#}
{##}
{# <li class="page-item"><span class="page-link">{{ page_obj.number }} از {{ page_obj.paginator.num_pages }}</span></li>#}
{# <li class="page-item"><span class="page-link">{{ page_obj.number }} از {{ page_obj.paginator.num_pages }}</span></li>#}
{# {% elif i > page_obj.number|add:'-5' and i < page_obj.number|add:'5' %}#}
{# <li class="page-item"><a class="page-link" href="?page={{ i }}{{ query_string }}">{{ i }}</a>#}
{# </li>#}
{# {% endif %}#}
{##}
{# {% endif %}#}
{# </ul>#}
{# </nav>#}

View file

@ -16,7 +16,7 @@
</div> </div>
<div class="col d-flex justify-content-center"> <div class="col d-flex justify-content-center">
<div class="bi bi-file-spreadsheet p-1"></div> <div class="bi bi-file-spreadsheet p-1"></div>
<div class="p-1"><a class="text-decoration-none" href="{% url 'swagger-ui' %}">API's</a></div> <div class="p-1"><a class="text-decoration-none" href="{% url 'gooyal_oauth2:application_list' %}">applications</a></div>
</div> </div>
{% endif %} {% endif %}
<div class="col d-flex justify-content-end"> <div class="col d-flex justify-content-end">