django-api-admin 1.2.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/PKG-INFO +1 -1
- django_api_admin-1.2.1/django_api_admin/__init__.py +47 -0
- django_api_admin-1.2.1/django_api_admin/actions.py +40 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/admin_api_root.py +23 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/admin_log.py +83 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/app_index.py +62 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/autocomplete.py +155 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/index.py +37 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/language_catalog.py +98 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/obtain_token.py +95 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/password_change.py +77 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/site_context.py +29 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/token_refresh.py +7 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/user_information.py +40 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/admin_site_views/view_on_site.py +122 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/add.py +104 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/change.py +208 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/changelist.py +176 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/delete.py +104 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/detail.py +60 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/handle_action.py +105 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/history.py +52 -0
- django_api_admin-1.2.1/django_api_admin/admin_views/model_admin_views/list.py +36 -0
- django_api_admin-1.2.1/django_api_admin/admins/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/admins/base_admin.py +284 -0
- django_api_admin-1.2.1/django_api_admin/admins/inline_admin.py +87 -0
- django_api_admin-1.2.1/django_api_admin/admins/model_admin.py +593 -0
- django_api_admin-1.2.1/django_api_admin/apps.py +27 -0
- django_api_admin-1.2.1/django_api_admin/changelist.py +490 -0
- django_api_admin-1.2.1/django_api_admin/checks.py +1264 -0
- django_api_admin-1.2.1/django_api_admin/constants/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/constants/field_attributes.py +82 -0
- django_api_admin-1.2.1/django_api_admin/constants/vars.py +12 -0
- django_api_admin-1.2.1/django_api_admin/declarations/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/declarations/functions.py +305 -0
- django_api_admin-1.2.1/django_api_admin/decorators.py +108 -0
- django_api_admin-1.2.1/django_api_admin/exceptions.py +31 -0
- django_api_admin-1.2.1/django_api_admin/filters.py +554 -0
- django_api_admin-1.2.1/django_api_admin/hooks.py +67 -0
- django_api_admin-1.2.1/django_api_admin/migrations/0001_initial.py +42 -0
- django_api_admin-1.2.1/django_api_admin/migrations/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/models.py +194 -0
- django_api_admin-1.2.1/django_api_admin/openapi.py +303 -0
- django_api_admin-1.2.1/django_api_admin/options.py +287 -0
- django_api_admin-1.2.1/django_api_admin/pagination.py +24 -0
- django_api_admin-1.2.1/django_api_admin/permissions.py +10 -0
- django_api_admin-1.2.1/django_api_admin/serializers.py +399 -0
- django_api_admin-1.2.1/django_api_admin/sites.py +510 -0
- django_api_admin-1.2.1/django_api_admin/utils/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/utils/_get_non_gfk_field.py +30 -0
- django_api_admin-1.2.1/django_api_admin/utils/diff_helper.py +46 -0
- django_api_admin-1.2.1/django_api_admin/utils/flatten.py +11 -0
- django_api_admin-1.2.1/django_api_admin/utils/force_login.py +14 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_content_type_for_model.py +6 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_deleted_objects.py +67 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_field_attributes.py +54 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_fields_from_path.py +22 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_form_config.py +13 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_form_fields.py +46 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_inline_by_field_name.py +12 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_inlines.py +51 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_model_from_relation.py +8 -0
- django_api_admin-1.2.1/django_api_admin/utils/get_related_name.py +10 -0
- django_api_admin-1.2.1/django_api_admin/utils/label_for_field.py +70 -0
- django_api_admin-1.2.1/django_api_admin/utils/lookup_field.py +29 -0
- django_api_admin-1.2.1/django_api_admin/utils/lookup_spawns_duplicates.py +28 -0
- django_api_admin-1.2.1/django_api_admin/utils/model_format_dict.py +17 -0
- django_api_admin-1.2.1/django_api_admin/utils/model_ngettext.py +39 -0
- django_api_admin-1.2.1/django_api_admin/utils/nested_objects.py +71 -0
- django_api_admin-1.2.1/django_api_admin/utils/prepare_lookup_value.py +11 -0
- django_api_admin-1.2.1/django_api_admin/utils/quote.py +22 -0
- django_api_admin-1.2.1/django_api_admin/utils/remove_field.py +12 -0
- django_api_admin-1.2.1/django_api_admin/utils/reverse_field_path.py +34 -0
- django_api_admin-1.2.1/django_api_admin/utils/url_params_from_lookup_dict.py +18 -0
- django_api_admin-1.2.1/django_api_admin/utils/validate_bulk_edits.py +105 -0
- django_api_admin-1.2.1/django_api_admin/utils/validate_inline_field_names.py +25 -0
- django_api_admin-1.2.1/django_api_admin/views/__init__.py +0 -0
- django_api_admin-1.2.1/django_api_admin/views/admin_views.py +677 -0
- django_api_admin-1.2.1/django_api_admin/views/site_views.py +251 -0
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/django_api_admin.egg-info/PKG-INFO +1 -1
- django_api_admin-1.2.1/django_api_admin.egg-info/SOURCES.txt +88 -0
- django_api_admin-1.2.1/django_api_admin.egg-info/top_level.txt +2 -0
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/pyproject.toml +4 -2
- django_api_admin-1.2.0/django_api_admin.egg-info/SOURCES.txt +0 -8
- django_api_admin-1.2.0/django_api_admin.egg-info/top_level.txt +0 -1
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/LICENSE +0 -0
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/README.md +0 -0
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/django_api_admin.egg-info/dependency_links.txt +0 -0
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/django_api_admin.egg-info/requires.txt +0 -0
- {django_api_admin-1.2.0 → django_api_admin-1.2.1}/setup.cfg +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from django.utils.module_loading import autodiscover_modules
|
|
2
|
+
|
|
3
|
+
from django_api_admin.decorators import action, display, register
|
|
4
|
+
from django_api_admin.filters import (
|
|
5
|
+
AllValuesFieldListFilter,
|
|
6
|
+
BooleanFieldListFilter,
|
|
7
|
+
ChoicesFieldListFilter,
|
|
8
|
+
DateFieldListFilter,
|
|
9
|
+
EmptyFieldListFilter,
|
|
10
|
+
FieldListFilter,
|
|
11
|
+
ListFilter,
|
|
12
|
+
RelatedFieldListFilter,
|
|
13
|
+
RelatedOnlyFieldListFilter,
|
|
14
|
+
SimpleListFilter,
|
|
15
|
+
)
|
|
16
|
+
from django_api_admin.constants.vars import HORIZONTAL, VERTICAL
|
|
17
|
+
from django_api_admin.admins.model_admin import APIModelAdmin
|
|
18
|
+
from django_api_admin.admins.inline_admin import StackedInlineAPI, TabularInlineAPI
|
|
19
|
+
from django_api_admin.sites import APIAdminSite, site
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"action",
|
|
23
|
+
"display",
|
|
24
|
+
"register",
|
|
25
|
+
"APIModelAdmin",
|
|
26
|
+
"HORIZONTAL",
|
|
27
|
+
"VERTICAL",
|
|
28
|
+
"StackedInlineAPI",
|
|
29
|
+
"TabularInlineAPI",
|
|
30
|
+
"APIAdminSite",
|
|
31
|
+
"site",
|
|
32
|
+
"ListFilter",
|
|
33
|
+
"SimpleListFilter",
|
|
34
|
+
"FieldListFilter",
|
|
35
|
+
"BooleanFieldListFilter",
|
|
36
|
+
"RelatedFieldListFilter",
|
|
37
|
+
"ChoicesFieldListFilter",
|
|
38
|
+
"DateFieldListFilter",
|
|
39
|
+
"AllValuesFieldListFilter",
|
|
40
|
+
"EmptyFieldListFilter",
|
|
41
|
+
"RelatedOnlyFieldListFilter",
|
|
42
|
+
"autodiscover",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def autodiscover():
|
|
47
|
+
autodiscover_modules("admin", register_to=site)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy, gettext as _
|
|
2
|
+
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from rest_framework.exceptions import PermissionDenied
|
|
5
|
+
from rest_framework.response import Response
|
|
6
|
+
|
|
7
|
+
from django_api_admin.utils.model_ngettext import model_ngettext
|
|
8
|
+
from django_api_admin.utils.get_deleted_objects import get_deleted_objects
|
|
9
|
+
from django_api_admin.decorators import action
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@action(
|
|
13
|
+
permissions=['delete'],
|
|
14
|
+
description=gettext_lazy('Delete selected %(verbose_name_plural)s')
|
|
15
|
+
)
|
|
16
|
+
def delete_selected(modeladmin, request, queryset):
|
|
17
|
+
"""
|
|
18
|
+
default api_admin action deletes the selected objects
|
|
19
|
+
no confirmation page
|
|
20
|
+
"""
|
|
21
|
+
_deletable_objects, _model_count, perms_needed, _protected = get_deleted_objects(
|
|
22
|
+
queryset, request, modeladmin.admin_site)
|
|
23
|
+
|
|
24
|
+
# check the permissions
|
|
25
|
+
if perms_needed:
|
|
26
|
+
objects_name = model_ngettext(queryset)
|
|
27
|
+
msg = _("Cannot delete %(name)s") % {"name": objects_name}
|
|
28
|
+
raise PermissionDenied(detail=msg)
|
|
29
|
+
|
|
30
|
+
# log the deletion of all the objects inside the queryset
|
|
31
|
+
n = queryset.count()
|
|
32
|
+
if n:
|
|
33
|
+
for obj in queryset:
|
|
34
|
+
modeladmin.log_deletion(request, obj, str(obj))
|
|
35
|
+
|
|
36
|
+
# delete the queryset
|
|
37
|
+
queryset.delete()
|
|
38
|
+
msg = _("Successfully deleted %s %s.") % (
|
|
39
|
+
n, model_ngettext(modeladmin.opts, n))
|
|
40
|
+
return Response({'detail': msg}, status=status.HTTP_200_OK)
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from rest_framework.response import Response
|
|
5
|
+
from rest_framework.views import APIView
|
|
6
|
+
from rest_framework.reverse import reverse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AdminAPIRootView(APIView):
|
|
10
|
+
"""
|
|
11
|
+
A list of all root urls in django_api_admin
|
|
12
|
+
"""
|
|
13
|
+
root_urls = None
|
|
14
|
+
|
|
15
|
+
def get(self, request, *args, **kwargs):
|
|
16
|
+
namespace = request.resolver_match.namespace
|
|
17
|
+
data = dict()
|
|
18
|
+
|
|
19
|
+
for url in self.root_urls:
|
|
20
|
+
data[url.name] = reverse(
|
|
21
|
+
namespace + ':' + url.name, args=args, kwargs=kwargs, request=request)
|
|
22
|
+
|
|
23
|
+
return Response(data or {}, status=status.HTTP_200_OK)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
|
|
5
|
+
from rest_framework import status
|
|
6
|
+
from rest_framework.response import Response
|
|
7
|
+
from rest_framework.views import APIView
|
|
8
|
+
|
|
9
|
+
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
|
10
|
+
|
|
11
|
+
from django_api_admin.models import LogEntry
|
|
12
|
+
from django_api_admin.openapi import CommonAPIResponses
|
|
13
|
+
from django_api_admin.serializers import LogEntrySerializer, AdminLogRequestSerializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AdminLogView(APIView):
|
|
17
|
+
"""
|
|
18
|
+
Returns a list of actions that were preformed using django admin.
|
|
19
|
+
"""
|
|
20
|
+
serializer_class = None
|
|
21
|
+
pagination_class = None
|
|
22
|
+
permission_classes = []
|
|
23
|
+
ordering_fields = ['action_time', '-action_time']
|
|
24
|
+
admin_site = None
|
|
25
|
+
|
|
26
|
+
@extend_schema(
|
|
27
|
+
methods=['GET'],
|
|
28
|
+
parameters=[AdminLogRequestSerializer],
|
|
29
|
+
responses={
|
|
30
|
+
200: OpenApiResponse(
|
|
31
|
+
response=LogEntrySerializer(many=True),
|
|
32
|
+
description=_('Successfully retrieved admin log entries')
|
|
33
|
+
),
|
|
34
|
+
401: CommonAPIResponses.unauthorized(),
|
|
35
|
+
403: CommonAPIResponses.permission_denied(),
|
|
36
|
+
},
|
|
37
|
+
description=_('Retrieve a list of admin log entries'),
|
|
38
|
+
tags=['admin-log']
|
|
39
|
+
)
|
|
40
|
+
def get(self, request):
|
|
41
|
+
queryset = LogEntry.objects.all()
|
|
42
|
+
|
|
43
|
+
# order the queryset
|
|
44
|
+
try:
|
|
45
|
+
ordering = self.request.query_params.get('o')
|
|
46
|
+
if ordering is not None:
|
|
47
|
+
if ordering not in self.ordering_fields:
|
|
48
|
+
raise KeyError
|
|
49
|
+
queryset = queryset.order_by(ordering)
|
|
50
|
+
except:
|
|
51
|
+
return Response({'detail': _('Wrong ordering field set.')}, status=status.HTTP_400_BAD_REQUEST)
|
|
52
|
+
|
|
53
|
+
# filter the queryset.
|
|
54
|
+
try:
|
|
55
|
+
object_id = self.request.query_params.get('object_id')
|
|
56
|
+
if object_id is not None:
|
|
57
|
+
queryset = queryset.filter(object_id=object_id)
|
|
58
|
+
except:
|
|
59
|
+
return Response({'detail': _('Bad filters.')}, status=status.HTTP_400_BAD_REQUEST)
|
|
60
|
+
|
|
61
|
+
# paginate queryset.
|
|
62
|
+
paginator = self.pagination_class()
|
|
63
|
+
page = paginator.paginate_queryset(queryset, request, view=self)
|
|
64
|
+
|
|
65
|
+
# serialize queryset.
|
|
66
|
+
serializer = self.serializer_class(page, many=True)
|
|
67
|
+
|
|
68
|
+
return Response({
|
|
69
|
+
'action_list': self.serialize_messages(serializer.data),
|
|
70
|
+
'config': self.get_config(page, queryset)},
|
|
71
|
+
status=status.HTTP_200_OK)
|
|
72
|
+
|
|
73
|
+
def serialize_messages(self, data):
|
|
74
|
+
for idx, item in enumerate(data, start=0):
|
|
75
|
+
data[idx]['change_message'] = json.loads(
|
|
76
|
+
item['change_message'] or '[]')
|
|
77
|
+
return data
|
|
78
|
+
|
|
79
|
+
def get_config(self, page, queryset):
|
|
80
|
+
return {
|
|
81
|
+
'result_count': len(page),
|
|
82
|
+
'full_result_count': queryset.count(),
|
|
83
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
from rest_framework.response import Response
|
|
4
|
+
from rest_framework import status
|
|
5
|
+
from rest_framework.exceptions import ParseError
|
|
6
|
+
|
|
7
|
+
from rest_framework.views import APIView
|
|
8
|
+
|
|
9
|
+
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
|
10
|
+
|
|
11
|
+
from django_api_admin.serializers import AppIndexSerializer, AppSerializer
|
|
12
|
+
from django_api_admin.openapi import CommonAPIResponses
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AppIndexView(APIView):
|
|
16
|
+
"""
|
|
17
|
+
Lists models inside a given app.
|
|
18
|
+
"""
|
|
19
|
+
serializer_class = AppIndexSerializer
|
|
20
|
+
permission_classes = []
|
|
21
|
+
admin_site = None
|
|
22
|
+
|
|
23
|
+
@extend_schema(
|
|
24
|
+
request=AppIndexSerializer,
|
|
25
|
+
responses={
|
|
26
|
+
200: OpenApiResponse(
|
|
27
|
+
response=AppSerializer,
|
|
28
|
+
description=_(
|
|
29
|
+
"Successfully constructed the list of registered models")
|
|
30
|
+
),
|
|
31
|
+
403: CommonAPIResponses.permission_denied(),
|
|
32
|
+
401: CommonAPIResponses.unauthorized()
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
def get(self, request, app_label):
|
|
36
|
+
serializer = self.get_serializer(app_label)
|
|
37
|
+
if not serializer.is_valid():
|
|
38
|
+
raise ParseError({"detail": _("invalid app_label")})
|
|
39
|
+
|
|
40
|
+
app_dict = self.admin_site._build_app_dict(request, app_label)
|
|
41
|
+
|
|
42
|
+
if not app_dict:
|
|
43
|
+
return Response({'detail': _('The requested admin page does not exist.')},
|
|
44
|
+
status=status.HTTP_404_NOT_FOUND)
|
|
45
|
+
|
|
46
|
+
# Sort the models alphabetically within each app.
|
|
47
|
+
app_dict['models'].sort(key=lambda x: x['name'])
|
|
48
|
+
|
|
49
|
+
data = {
|
|
50
|
+
'app_label': app_label,
|
|
51
|
+
'app': app_dict,
|
|
52
|
+
}
|
|
53
|
+
return Response(data, status=status.HTTP_200_OK)
|
|
54
|
+
|
|
55
|
+
def get_serializer(self, app_label):
|
|
56
|
+
registered_app_labels = {
|
|
57
|
+
model._meta.app_label for model in self.admin_site._registry.keys()
|
|
58
|
+
}
|
|
59
|
+
return AppIndexSerializer(
|
|
60
|
+
data={"app_label": app_label},
|
|
61
|
+
context={'registered_app_labels': registered_app_labels}
|
|
62
|
+
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
4
|
+
|
|
5
|
+
from rest_framework.exceptions import PermissionDenied, ParseError
|
|
6
|
+
from rest_framework.views import APIView
|
|
7
|
+
from rest_framework.response import Response
|
|
8
|
+
from rest_framework import status
|
|
9
|
+
from rest_framework.views import APIView
|
|
10
|
+
|
|
11
|
+
from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiResponse
|
|
12
|
+
|
|
13
|
+
from django_api_admin.serializers import AutoCompleteSerializer
|
|
14
|
+
from django_api_admin.openapi import CommonAPIResponses
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AutoCompleteView(APIView):
|
|
18
|
+
"""
|
|
19
|
+
API view for handling autocomplete functionality in admin fields.
|
|
20
|
+
"""
|
|
21
|
+
permission_classes = []
|
|
22
|
+
admin_site = None
|
|
23
|
+
|
|
24
|
+
@extend_schema(
|
|
25
|
+
parameters=[AutoCompleteSerializer],
|
|
26
|
+
responses={
|
|
27
|
+
200: OpenApiResponse(
|
|
28
|
+
description=_("Successful autocomplete response"),
|
|
29
|
+
response=AutoCompleteSerializer,
|
|
30
|
+
examples=[
|
|
31
|
+
OpenApiExample(
|
|
32
|
+
name=_("Success Response"),
|
|
33
|
+
summary=_(
|
|
34
|
+
"Example of a successful autocomplete response"),
|
|
35
|
+
description="Returns matching records based on the search term",
|
|
36
|
+
value=[{
|
|
37
|
+
"id": 1,
|
|
38
|
+
"name": "Muhammad",
|
|
39
|
+
"age": 60,
|
|
40
|
+
"is_vip": True,
|
|
41
|
+
"date_joined": "2025-02-02T23:09:31.994853Z",
|
|
42
|
+
"title": None,
|
|
43
|
+
"user": 1,
|
|
44
|
+
"publisher": [1],
|
|
45
|
+
"pk": 1
|
|
46
|
+
}],
|
|
47
|
+
status_codes=["200"],
|
|
48
|
+
)
|
|
49
|
+
]
|
|
50
|
+
),
|
|
51
|
+
403: CommonAPIResponses.permission_denied(),
|
|
52
|
+
401: CommonAPIResponses.unauthorized(),
|
|
53
|
+
},
|
|
54
|
+
description=_(
|
|
55
|
+
"Endpoint for autocomplete functionality on model fields")
|
|
56
|
+
)
|
|
57
|
+
def get(self, request):
|
|
58
|
+
"""
|
|
59
|
+
Process the request to extract search parameters,
|
|
60
|
+
validates user permissions, retrieves the relevant queryset,
|
|
61
|
+
paginates the results, and returns them as a JSON response.
|
|
62
|
+
"""
|
|
63
|
+
(
|
|
64
|
+
self.term,
|
|
65
|
+
self.model_admin,
|
|
66
|
+
self.source_field,
|
|
67
|
+
to_field_name,
|
|
68
|
+
) = self.process_request(request)
|
|
69
|
+
|
|
70
|
+
if not self.has_perm(request):
|
|
71
|
+
raise PermissionDenied
|
|
72
|
+
|
|
73
|
+
self.queryset = self.get_queryset()
|
|
74
|
+
page = self.admin_site.paginate_queryset(
|
|
75
|
+
self.queryset, request, view=self)
|
|
76
|
+
|
|
77
|
+
# serialize data
|
|
78
|
+
serializer_class = self.model_admin.get_serializer_class()
|
|
79
|
+
serializer = serializer_class(page, many=True)
|
|
80
|
+
data = serializer.data
|
|
81
|
+
|
|
82
|
+
return Response(
|
|
83
|
+
data,
|
|
84
|
+
status=status.HTTP_200_OK
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def get_queryset(self):
|
|
88
|
+
"""Return queryset based on model_admin.get_search_results()."""
|
|
89
|
+
qs = self.model_admin.get_queryset()
|
|
90
|
+
qs = qs.complex_filter(self.source_field.get_limit_choices_to())
|
|
91
|
+
qs, search_use_distinct = self.model_admin.get_search_results(
|
|
92
|
+
qs, self.term)
|
|
93
|
+
if search_use_distinct:
|
|
94
|
+
qs = qs.distinct()
|
|
95
|
+
return qs
|
|
96
|
+
|
|
97
|
+
def process_request(self, request):
|
|
98
|
+
"""
|
|
99
|
+
Validate request integrity, extract and return request parameters.
|
|
100
|
+
|
|
101
|
+
Since the subsequent view permission check requires the target model
|
|
102
|
+
admin, which is determined here, raise PermissionDenied if the
|
|
103
|
+
requested app, model or field are malformed.
|
|
104
|
+
|
|
105
|
+
Raise Http404 if the target model admin is not configured properly with
|
|
106
|
+
search_fields.
|
|
107
|
+
"""
|
|
108
|
+
term = request.GET.get("term", "")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
app_label = request.GET["app_label"]
|
|
112
|
+
model_name = request.GET["model_name"]
|
|
113
|
+
field_name = request.GET["field_name"]
|
|
114
|
+
except KeyError:
|
|
115
|
+
raise ParseError(
|
|
116
|
+
{'detail': _('missing values app_label, model_name, and field_name')})
|
|
117
|
+
|
|
118
|
+
# Retrieve objects from parameters.
|
|
119
|
+
try:
|
|
120
|
+
source_model = apps.get_model(app_label, model_name)
|
|
121
|
+
except LookupError:
|
|
122
|
+
raise ParseError({'detail': _('source model not found')})
|
|
123
|
+
try:
|
|
124
|
+
source_field = source_model._meta.get_field(field_name)
|
|
125
|
+
except FieldDoesNotExist:
|
|
126
|
+
raise ParseError(
|
|
127
|
+
{f'detail': _(f'source field not found in source model {source_model._meta.verbose_name}')})
|
|
128
|
+
try:
|
|
129
|
+
remote_model = source_field.remote_field.model
|
|
130
|
+
except AttributeError:
|
|
131
|
+
raise ParseError(
|
|
132
|
+
{'detail': _(f'unable to locate the related model using source field {source_field.name}')})
|
|
133
|
+
try:
|
|
134
|
+
model_admin = self.admin_site._registry[remote_model]
|
|
135
|
+
except KeyError:
|
|
136
|
+
raise ParseError(
|
|
137
|
+
{'detail': _('the remote model is not registered in the admin')})
|
|
138
|
+
|
|
139
|
+
# Validate suitability of objects.
|
|
140
|
+
if not getattr(model_admin, "search_fields"):
|
|
141
|
+
raise ParseError(_('%s must have search_fields for the autocomplete_view."') % type(
|
|
142
|
+
model_admin).__qualname__)
|
|
143
|
+
|
|
144
|
+
to_field_name = getattr(
|
|
145
|
+
source_field.remote_field, "field_name", remote_model._meta.pk.attname
|
|
146
|
+
)
|
|
147
|
+
to_field_name = remote_model._meta.get_field(to_field_name).attname
|
|
148
|
+
if not model_admin.to_field_allowed(to_field_name):
|
|
149
|
+
raise PermissionDenied
|
|
150
|
+
|
|
151
|
+
return term, model_admin, source_field, to_field_name
|
|
152
|
+
|
|
153
|
+
def has_perm(self, request):
|
|
154
|
+
"""Check if user has permission to access the related model."""
|
|
155
|
+
return self.model_admin.has_view_permission(request)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from rest_framework.response import Response
|
|
5
|
+
from rest_framework.reverse import reverse
|
|
6
|
+
from rest_framework.views import APIView
|
|
7
|
+
|
|
8
|
+
from drf_spectacular.utils import extend_schema
|
|
9
|
+
|
|
10
|
+
from django_api_admin.serializers import AppListSerializer
|
|
11
|
+
from django_api_admin.openapi import CommonAPIResponses
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IndexView(APIView):
|
|
15
|
+
"""
|
|
16
|
+
Return json object that lists all the installed
|
|
17
|
+
apps that have been registered by the admin site.
|
|
18
|
+
"""
|
|
19
|
+
permission_classes = []
|
|
20
|
+
admin_site = None
|
|
21
|
+
|
|
22
|
+
@extend_schema(
|
|
23
|
+
responses={
|
|
24
|
+
200: AppListSerializer,
|
|
25
|
+
403: CommonAPIResponses.permission_denied(),
|
|
26
|
+
401: CommonAPIResponses.unauthorized()
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
def get(self, request):
|
|
30
|
+
app_list = self.admin_site.get_app_list(request)
|
|
31
|
+
# add an url to app_index in every app in app_list
|
|
32
|
+
for app in app_list:
|
|
33
|
+
app['url'] = reverse(f'{self.admin_site.name}:app_list', kwargs={
|
|
34
|
+
'app_label': app['app_label']}, request=request)
|
|
35
|
+
data = {'app_list': app_list}
|
|
36
|
+
request.current_app = self.admin_site.name
|
|
37
|
+
return Response(data, status=status.HTTP_200_OK)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from django.views.i18n import JSONCatalog
|
|
6
|
+
|
|
7
|
+
from rest_framework.response import Response
|
|
8
|
+
from rest_framework.views import APIView
|
|
9
|
+
|
|
10
|
+
from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample
|
|
11
|
+
|
|
12
|
+
from django_api_admin.serializers import LanguageCatalogSerializer
|
|
13
|
+
from django_api_admin.openapi import CommonAPIResponses
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LanguageCatalogView(APIView):
|
|
17
|
+
"""
|
|
18
|
+
Returns json object with i18n translation catalog
|
|
19
|
+
to be used by a client site javascript library
|
|
20
|
+
"""
|
|
21
|
+
permission_classes = []
|
|
22
|
+
admin_site = None
|
|
23
|
+
|
|
24
|
+
@extend_schema(
|
|
25
|
+
responses={
|
|
26
|
+
200: OpenApiResponse(
|
|
27
|
+
response=LanguageCatalogSerializer,
|
|
28
|
+
description=_("Successful retrieval of the language catalog."),
|
|
29
|
+
examples=[
|
|
30
|
+
OpenApiExample(
|
|
31
|
+
name=_("Success Response"),
|
|
32
|
+
summary=_(
|
|
33
|
+
"Example of a successful language catalog request"),
|
|
34
|
+
description=_(
|
|
35
|
+
"return the translation string used in this app"),
|
|
36
|
+
value={
|
|
37
|
+
"catalog": {
|
|
38
|
+
"AM": "ص",
|
|
39
|
+
"PM": "م",
|
|
40
|
+
"January": "يناير",
|
|
41
|
+
"February": "فبراير"
|
|
42
|
+
},
|
|
43
|
+
"formats": {
|
|
44
|
+
"DATE_FORMAT": "j F، Y",
|
|
45
|
+
"DATETIME_FORMAT": "N j, Y, P",
|
|
46
|
+
"TIME_FORMAT": "g:i A",
|
|
47
|
+
"YEAR_MONTH_FORMAT": "F Y",
|
|
48
|
+
"MONTH_DAY_FORMAT": "j F",
|
|
49
|
+
"SHORT_DATE_FORMAT": "d/m/Y",
|
|
50
|
+
"SHORT_DATETIME_FORMAT": "m/d/Y P",
|
|
51
|
+
"FIRST_DAY_OF_WEEK": 0,
|
|
52
|
+
"DECIMAL_SEPARATOR": ",",
|
|
53
|
+
"THOUSAND_SEPARATOR": ".",
|
|
54
|
+
"NUMBER_GROUPING": 0,
|
|
55
|
+
"DATE_INPUT_FORMATS": [
|
|
56
|
+
"%Y-%m-%d",
|
|
57
|
+
"%m/%d/%Y",
|
|
58
|
+
"%m/%d/%y",
|
|
59
|
+
"%b %d %Y",
|
|
60
|
+
"%b %d, %Y",
|
|
61
|
+
"%d %b %Y",
|
|
62
|
+
"%d %b, %Y",
|
|
63
|
+
"%B %d %Y",
|
|
64
|
+
"%B %d, %Y",
|
|
65
|
+
"%d %B %Y",
|
|
66
|
+
"%d %B, %Y"
|
|
67
|
+
],
|
|
68
|
+
"TIME_INPUT_FORMATS": [
|
|
69
|
+
"%H:%M:%S",
|
|
70
|
+
"%H:%M:%S.%f",
|
|
71
|
+
"%H:%M"
|
|
72
|
+
],
|
|
73
|
+
"DATETIME_INPUT_FORMATS": [
|
|
74
|
+
"%Y-%m-%d %H:%M:%S",
|
|
75
|
+
"%Y-%m-%d %H:%M:%S.%f",
|
|
76
|
+
"%Y-%m-%d %H:%M",
|
|
77
|
+
"%m/%d/%Y %H:%M:%S",
|
|
78
|
+
"%m/%d/%Y %H:%M:%S.%f",
|
|
79
|
+
"%m/%d/%Y %H:%M",
|
|
80
|
+
"%m/%d/%y %H:%M:%S",
|
|
81
|
+
"%m/%d/%y %H:%M:%S.%f",
|
|
82
|
+
"%m/%d/%y %H:%M"
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
"plural": "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5",
|
|
86
|
+
},
|
|
87
|
+
status_codes=["200"],
|
|
88
|
+
)
|
|
89
|
+
]
|
|
90
|
+
),
|
|
91
|
+
403: CommonAPIResponses.permission_denied(),
|
|
92
|
+
401: CommonAPIResponses.unauthorized(),
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
def get(self, request):
|
|
96
|
+
response = JSONCatalog.as_view(
|
|
97
|
+
packages=['django_api_admin'], domain='django')(request)
|
|
98
|
+
return Response(json.loads(response.content), status=response.status_code)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from rest_framework.response import Response
|
|
5
|
+
from rest_framework.views import APIView
|
|
6
|
+
|
|
7
|
+
from rest_framework_simplejwt.tokens import RefreshToken
|
|
8
|
+
|
|
9
|
+
from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiResponse
|
|
10
|
+
|
|
11
|
+
from django_api_admin.utils.get_form_fields import get_form_fields
|
|
12
|
+
from django_api_admin.openapi import CommonAPIResponses, APIResponseExamples, User
|
|
13
|
+
from django_api_admin.serializers import FormFieldsSerializer, ObtainTokenResponseSerializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ObtainTokenView(APIView):
|
|
17
|
+
"""
|
|
18
|
+
Allow users to login using username and password.
|
|
19
|
+
"""
|
|
20
|
+
serializer_class = None
|
|
21
|
+
permission_classes = []
|
|
22
|
+
admin_site = None
|
|
23
|
+
|
|
24
|
+
@extend_schema(
|
|
25
|
+
responses={
|
|
26
|
+
200: OpenApiResponse(
|
|
27
|
+
description=_(
|
|
28
|
+
"Successfully returned the field attributes list"),
|
|
29
|
+
response=FormFieldsSerializer,
|
|
30
|
+
examples=[
|
|
31
|
+
APIResponseExamples.field_attributes()
|
|
32
|
+
]
|
|
33
|
+
),
|
|
34
|
+
403: CommonAPIResponses.permission_denied(),
|
|
35
|
+
401: CommonAPIResponses.unauthorized()
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
def get(self, request):
|
|
39
|
+
serializer = self.serializer_class()
|
|
40
|
+
form_fields = get_form_fields(serializer)
|
|
41
|
+
return Response({'fields': form_fields}, status=status.HTTP_200_OK)
|
|
42
|
+
|
|
43
|
+
@extend_schema(
|
|
44
|
+
responses={
|
|
45
|
+
200: OpenApiResponse(
|
|
46
|
+
description=_("Successful token obtainment"),
|
|
47
|
+
response=ObtainTokenResponseSerializer,
|
|
48
|
+
examples=[
|
|
49
|
+
OpenApiExample(
|
|
50
|
+
name=_("Success Response"),
|
|
51
|
+
summary=_(
|
|
52
|
+
"Example of a successful token obtain response"),
|
|
53
|
+
description=_(
|
|
54
|
+
"Returns a pair of tokens containing both the refresh and access tokens"),
|
|
55
|
+
value={
|
|
56
|
+
"detail": "you are logged in successfully",
|
|
57
|
+
"user": User,
|
|
58
|
+
"tokens": {
|
|
59
|
+
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczOTIyODYzMywiaWF0IjoxNzM4NjIzODMzLCJqdGkiOiI0NWFmYTUzYzk4MWY0MjdkODQ5ODgwMGRlOTNiNTY3NSIsInVzZXJfaWQiOjF9.ekHLcEXJRzuim0GlIckd4iFfSiljqfdPpIBgK2_a12s",
|
|
60
|
+
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzM4NzEwMjMzLCJpYXQiOjE3Mzg2MjM4MzMsImp0aSI6IjkxNjljOWUxNzliMDQ3MmI4NmY0MTJhYzIyOTRkZThiIiwidXNlcl9pZCI6MX0.Gwb23W-clas-K9VfmDeXRNfEBCFRpxVdMpcp3k-fpXM"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
status_codes=["200"]
|
|
64
|
+
)
|
|
65
|
+
],
|
|
66
|
+
),
|
|
67
|
+
403: CommonAPIResponses.permission_denied(),
|
|
68
|
+
401: CommonAPIResponses.unauthorized()
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
def post(self, request):
|
|
72
|
+
serializer = self.serializer_class(
|
|
73
|
+
data=request.data, context={'request': request})
|
|
74
|
+
if serializer.is_valid():
|
|
75
|
+
user = serializer.get_user()
|
|
76
|
+
tokens = self.get_tokens_for_user(user)
|
|
77
|
+
user_serializer = self.admin_site.user_serializer(user)
|
|
78
|
+
data = {
|
|
79
|
+
'detail': _('you are logged in successfully'),
|
|
80
|
+
'user': user_serializer.data,
|
|
81
|
+
'tokens': tokens
|
|
82
|
+
}
|
|
83
|
+
return Response(data, status=status.HTTP_200_OK)
|
|
84
|
+
|
|
85
|
+
for error in serializer.errors.get('non_field_errors', []):
|
|
86
|
+
if error.code == 'permission_denied':
|
|
87
|
+
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
|
|
88
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
89
|
+
|
|
90
|
+
def get_tokens_for_user(self, user):
|
|
91
|
+
refresh = RefreshToken.for_user(user)
|
|
92
|
+
return {
|
|
93
|
+
'refresh': str(refresh),
|
|
94
|
+
'access': str(refresh.access_token),
|
|
95
|
+
}
|