From 1e956bd931ae0c8b65d9cca88d45341972a5a56e Mon Sep 17 00:00:00 2001 From: ivanostapiuk Date: Wed, 5 Jan 2022 00:20:42 +0200 Subject: [PATCH 1/3] add custom model queryset and fields --- django_react_admin/views.py | 81 ++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/django_react_admin/views.py b/django_react_admin/views.py index bfb0467..3df53c1 100644 --- a/django_react_admin/views.py +++ b/django_react_admin/views.py @@ -24,16 +24,29 @@ class CustomPageNumberPagination(PageNumberPagination): page_size_query_param = 'page_size' # items per page -def get_serializer_class(model, model_admin): +# def get_serializer_class(model, model_admin): +# meta_props = { +# "model": model, +# "fields": list(model_admin.get_fields(r)), +# "read_only_fields": model_admin.readonly_fields +# } +# return type( +# f"{model.__name__}Serializer", +# (ModelSerializer,), +# {"Meta": type("Meta", (), meta_props)}, +# ) + +def get_serializer_class(self): meta_props = { - "model": model, - "fields": list(model_admin.get_fields(r)), - "read_only_fields": model_admin.readonly_fields + "model": self.model, + "fields": list(self.model_admin.get_fields(self.request)), + "read_only_fields": self.model_admin.readonly_fields } + return type( f"{model.__name__}Serializer", (ModelSerializer,), - {"Meta": type("Meta", (), meta_props)}, + {"Meta": type("Meta", (), meta_props)} ) def model_views_set_list(self, request, *args, **kwargs): @@ -68,39 +81,69 @@ def get_filterset_fields(model_admin): return filterset_fields - def get_info(model_admin): + # def get_info(model_admin): + # def info(*args): + # basic_params = { + # "fields": list(model_admin.get_fields(r)), + # "list_display": list(model_admin.get_list_display(r)), + # "ordering_fields": list(model_admin.get_sortable_by(r)), + # "filterset_fields": get_filterset_fields(model_admin), + # } + # form = [ + # dict(name=name, **field.widget.__dict__) + # for name, field in model_admin.get_form(r)().fields.items() + # if not hasattr(field.widget, "widget") + # ] + # return Response( + # dict(form=form, **basic_params), + # ) + # return info + + def get_info(self): def info(*args): basic_params = { - "fields": list(model_admin.get_fields(r)), - "list_display": list(model_admin.get_list_display(r)), - "ordering_fields": list(model_admin.get_sortable_by(r)), - "filterset_fields": get_filterset_fields(model_admin), + "fields": list(self.model_admin.get_fields(self.request)), + "list_display": list(model_admin.get_list_display(self.request)), + "ordering_fields": list(self.model_admin.get_sortable_by(self.request)), + "filterset_fields": get_filterset_fields(self.model_admin) } + form = [ dict(name=name, **field.widget.__dict__) - for name, field in model_admin.get_form(r)().fields.items() + for name, field in self.model_admin.get_form(self.request)().fields.items() if not hasattr(field.widget, "widget") ] return Response( - dict(form=form, **basic_params), + dict(form=form, **basic_params) ) + return info + def get_queryset(self): + queryset = self.model_admin.get_queryset(self.request) + + return queryset + if not hasattr(model, 'objects'): continue # Use case: dramatiq.models.Task - queryset = model.objects.all() + # queryset = model_admin.get_queryset(r) if model_admin.list_select_related: queryset = queryset.select_related(*model_admin.list_select_related) params = { - "queryset": queryset, + # "queryset": queryset, + "model": model, + "model_admin": model_admin, + "get_queryset": get_queryset, "filter_backends": [DjangoFilterBackend, OrderingFilter, SearchFilter], - "info": action(methods=["get"], detail=False)(get_info(model_admin)), - "serializer_class": get_serializer_class(model, model_admin), + # "info": action(methods=["get"], detail=False)(get_info(model_admin)), + "info": get_info, + # "serializer_class": get_serializer_class(model, model_admin), + "get_serializer_class": get_serializer_class, "basename": model._meta.model_name, "request": r, - "fields": list(model_admin.get_fields(r)), + # "fields": list(model_admin.get_fields(r)), "filterset_class": getattr(model_admin, 'filterset_class', None), "list_display": list(model_admin.get_list_display(r)), "ordering_fields": list(model_admin.get_sortable_by(r)), @@ -108,14 +151,14 @@ def info(*args): "search_fields": list(model_admin.get_search_fields(r)), "permission_classes": getattr( model_admin, 'permission_classes', - [permissions.IsAdminUser, permissions.DjangoModelPermissions] + [permissions.DjangoModelPermissions] ), "pagination_class": CustomPageNumberPagination, "list": model_views_set_list } viewset = type(f"{model.__name__}ViewSet", (viewsets.ModelViewSet,), params) router.register( - f"{model._meta.app_label}/{model._meta.model_name}", viewset + f"{model._meta.app_label}/{model._meta.model_name}", viewset, model._meta.model_name ) viewpath = f"{model._meta.app_label}/{model._meta.model_name}" # urlpatterns.append( From a3dd714f699892b6dbe805714663e014122405d2 Mon Sep 17 00:00:00 2001 From: ivanostapiuk Date: Tue, 18 Jan 2022 14:39:40 +0200 Subject: [PATCH 2/3] add request methods permissions and add readonly fields --- django_react_admin/views.py | 41 +++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/django_react_admin/views.py b/django_react_admin/views.py index 3df53c1..2f36c5e 100644 --- a/django_react_admin/views.py +++ b/django_react_admin/views.py @@ -14,6 +14,10 @@ from rest_framework.reverse import reverse_lazy from rest_framework.routers import DefaultRouter from rest_framework.serializers import ModelSerializer +from rest_framework.exceptions import APIException +from rest_framework import status +import urllib.parse + router = DefaultRouter() r = Request(HttpRequest()) @@ -36,17 +40,46 @@ class CustomPageNumberPagination(PageNumberPagination): # {"Meta": type("Meta", (), meta_props)}, # ) +class MethodNotAllowed(APIException): + status_code = status.HTTP_403_FORBIDDEN + default_detail = {'error': True, 'message': 'method not allowed'} + default_code = 'method_not_allowed' + + +class IsAllowMethod(permissions.BasePermission): + def has_permission(self, request, view): + if hasattr(view.model_admin, "get_allowed_request_methods") and request.method in view.model_admin.get_allowed_request_methods(request): + return True + raise MethodNotAllowed() + + +def to_representation(self, instance): + data = super(type(self), self).to_representation(instance) + for field in data: + if instance._meta.get_field(field).get_internal_type() in ("FileField", "ImageField"): + if data[field]: + data.update({field: urllib.parse.urlparse(data[field]).path}) + + return data + def get_serializer_class(self): + params = { + "to_representation": to_representation + } + meta_props = { "model": self.model, "fields": list(self.model_admin.get_fields(self.request)), - "read_only_fields": self.model_admin.readonly_fields + "read_only_fields": self.model_admin.get_readonly_fields(self.request) } return type( f"{model.__name__}Serializer", (ModelSerializer,), - {"Meta": type("Meta", (), meta_props)} + { + **params, + "Meta": type("Meta", (), meta_props) + } ) def model_views_set_list(self, request, *args, **kwargs): @@ -103,7 +136,7 @@ def get_info(self): def info(*args): basic_params = { "fields": list(self.model_admin.get_fields(self.request)), - "list_display": list(model_admin.get_list_display(self.request)), + "list_display": list(self.model_admin.get_list_display(self.request)), "ordering_fields": list(self.model_admin.get_sortable_by(self.request)), "filterset_fields": get_filterset_fields(self.model_admin) } @@ -151,7 +184,7 @@ def get_queryset(self): "search_fields": list(model_admin.get_search_fields(r)), "permission_classes": getattr( model_admin, 'permission_classes', - [permissions.DjangoModelPermissions] + [IsAllowMethod] ), "pagination_class": CustomPageNumberPagination, "list": model_views_set_list From 6c9fb4a46cfe2dfe29c990ab98902286bc5812b0 Mon Sep 17 00:00:00 2001 From: ivanostapiuk Date: Tue, 15 Feb 2022 18:17:07 +0200 Subject: [PATCH 3/3] add admin actions --- django_react_admin/serializers.py | 5 ++ django_react_admin/views.py | 118 ++++++++++-------------------- 2 files changed, 44 insertions(+), 79 deletions(-) create mode 100644 django_react_admin/serializers.py diff --git a/django_react_admin/serializers.py b/django_react_admin/serializers.py new file mode 100644 index 0000000..4d2b377 --- /dev/null +++ b/django_react_admin/serializers.py @@ -0,0 +1,5 @@ +from rest_framework import serializers + + +class ActionSerializer(serializers.Serializer): + id = serializers.JSONField() \ No newline at end of file diff --git a/django_react_admin/views.py b/django_react_admin/views.py index 2f36c5e..db73fee 100644 --- a/django_react_admin/views.py +++ b/django_react_admin/views.py @@ -17,9 +17,12 @@ from rest_framework.exceptions import APIException from rest_framework import status import urllib.parse +import json +from .serializers import ActionSerializer router = DefaultRouter() +actions_urlpatterns = [] r = Request(HttpRequest()) r.user = get_user_model()(is_superuser=True) @@ -28,18 +31,6 @@ class CustomPageNumberPagination(PageNumberPagination): page_size_query_param = 'page_size' # items per page -# def get_serializer_class(model, model_admin): -# meta_props = { -# "model": model, -# "fields": list(model_admin.get_fields(r)), -# "read_only_fields": model_admin.readonly_fields -# } -# return type( -# f"{model.__name__}Serializer", -# (ModelSerializer,), -# {"Meta": type("Meta", (), meta_props)}, -# ) - class MethodNotAllowed(APIException): status_code = status.HTTP_403_FORBIDDEN default_detail = {'error': True, 'message': 'method not allowed'} @@ -52,6 +43,12 @@ def has_permission(self, request, view): return True raise MethodNotAllowed() +class IsAllowAction(permissions.BasePermission): + def has_permission(self, request, view): + if hasattr(view.model_admin, "get_allowed_actions") and view.action.__name__ in view.model_admin.get_allowed_actions(request): + return True + raise MethodNotAllowed() + def to_representation(self, instance): data = super(type(self), self).to_representation(instance) @@ -114,24 +111,6 @@ def get_filterset_fields(model_admin): return filterset_fields - # def get_info(model_admin): - # def info(*args): - # basic_params = { - # "fields": list(model_admin.get_fields(r)), - # "list_display": list(model_admin.get_list_display(r)), - # "ordering_fields": list(model_admin.get_sortable_by(r)), - # "filterset_fields": get_filterset_fields(model_admin), - # } - # form = [ - # dict(name=name, **field.widget.__dict__) - # for name, field in model_admin.get_form(r)().fields.items() - # if not hasattr(field.widget, "widget") - # ] - # return Response( - # dict(form=form, **basic_params), - # ) - # return info - def get_info(self): def info(*args): basic_params = { @@ -160,23 +139,18 @@ def get_queryset(self): if not hasattr(model, 'objects'): continue # Use case: dramatiq.models.Task - # queryset = model_admin.get_queryset(r) if model_admin.list_select_related: queryset = queryset.select_related(*model_admin.list_select_related) params = { - # "queryset": queryset, "model": model, "model_admin": model_admin, "get_queryset": get_queryset, "filter_backends": [DjangoFilterBackend, OrderingFilter, SearchFilter], - # "info": action(methods=["get"], detail=False)(get_info(model_admin)), "info": get_info, - # "serializer_class": get_serializer_class(model, model_admin), "get_serializer_class": get_serializer_class, "basename": model._meta.model_name, "request": r, - # "fields": list(model_admin.get_fields(r)), "filterset_class": getattr(model_admin, 'filterset_class', None), "list_display": list(model_admin.get_list_display(r)), "ordering_fields": list(model_admin.get_sortable_by(r)), @@ -184,7 +158,7 @@ def get_queryset(self): "search_fields": list(model_admin.get_search_fields(r)), "permission_classes": getattr( model_admin, 'permission_classes', - [IsAllowMethod] + [permissions.IsAuthenticated, IsAllowMethod] ), "pagination_class": CustomPageNumberPagination, "list": model_views_set_list @@ -194,53 +168,39 @@ def get_queryset(self): f"{model._meta.app_label}/{model._meta.model_name}", viewset, model._meta.model_name ) viewpath = f"{model._meta.app_label}/{model._meta.model_name}" - # urlpatterns.append( - # path( - # r"html/{}/".format(viewpath), - # TemplateView.as_view( - # template_name="django_react_admin/list.html", - # extra_context={ - # "app": model._meta.app_label, - # "model": model._meta.model_name, - # "path": reverse_lazy(model._meta.model_name+"-list"), - # }, - # ), - # ) - # ) - # urlpatterns.append( - # path( - # r"html/{}/add/".format(viewpath), - # TemplateView.as_view( - # template_name="django_react_admin/edit.html", - # extra_context={ - # "create": True, - # "app": model._meta.app_label, - # "model": model._meta.model_name, - # "path": reverse_lazy(model._meta.model_name + "-list"), - # }, - # ), - # ) - # ) - # urlpatterns.append( - # path( - # r"html/{}//".format(viewpath), - # TemplateView.as_view( - # template_name="django_react_admin/edit.html", - # extra_context={ - # "create": False, - # "app": model._meta.app_label, - # "model": model._meta.model_name, - # "path": reverse_lazy(model._meta.model_name + "-list"), - # }, - # ), - # ) - # ) + if model_admin.actions: + for action in model_admin.actions: + action_title = action.__name__.replace("_", " ").title().replace(" ", "") + + def method_post(self, request): + serializer = self.serializer_class(data=json.loads(request.body)) + if serializer.is_valid(): + queryset = self.model_admin.get_queryset(request).filter(id__in=serializer.validated_data["id"]) + self.action(request, queryset) + + return Response("ok") + else: + return Response(serializer.errors) + + params = { + "permission_classes": [permissions.IsAuthenticated, IsAllowAction], + "serializer_class": ActionSerializer, + "model_admin": model_admin, + "action": action, + "post": method_post, + } + apiview = type(f"{model.__name__}Action{action_title}APIView", (views.APIView,), params) + + actions_urlpatterns.append(path( + f"{model._meta.app_label}/{model._meta.model_name}/{action.__name__}/", + apiview.as_view(), + name=f"{model._meta.model_name}-{action.__name__}" + )) class Index(views.APIView): def get(self, request): res = admin.site.get_app_list(request) - # return Response([m['admin_url'].replace(reverse('admin:index'), '') for app in res for m in app['models']]) for app in res: app['app_url'] = app['app_url'].replace(reverse('admin:index'), '') for m in app['models']: @@ -252,4 +212,4 @@ def get(self, request): return Response(res) -urlpatterns = [path('', Index.as_view(), name='react_admin_index')] + router.urls +urlpatterns = [path('', Index.as_view(), name='react_admin_index')] + actions_urlpatterns + router.urls