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 bfb0467..db73fee 100644 --- a/django_react_admin/views.py +++ b/django_react_admin/views.py @@ -14,8 +14,15 @@ 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 +import json +from .serializers import ActionSerializer + router = DefaultRouter() +actions_urlpatterns = [] r = Request(HttpRequest()) r.user = get_user_model()(is_superuser=True) @@ -24,16 +31,52 @@ class CustomPageNumberPagination(PageNumberPagination): page_size_query_param = 'page_size' # items per page -def get_serializer_class(model, model_admin): +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() + +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) + 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": 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.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): @@ -68,39 +111,46 @@ def get_filterset_fields(model_admin): return filterset_fields - def get_info(model_admin): + 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(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) } + 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() 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)), - "serializer_class": get_serializer_class(model, model_admin), + "info": get_info, + "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)), @@ -108,63 +158,49 @@ def info(*args): "search_fields": list(model_admin.get_search_fields(r)), "permission_classes": getattr( model_admin, 'permission_classes', - [permissions.IsAdminUser, permissions.DjangoModelPermissions] + [permissions.IsAuthenticated, IsAllowMethod] ), "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( - # 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']: @@ -176,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