diff --git a/apps/main/admin.py b/apps/main/admin.py index 315d1c2b..6a12541d 100644 --- a/apps/main/admin.py +++ b/apps/main/admin.py @@ -54,3 +54,16 @@ class PageAdmin(admin.ModelAdmin): list_display = ('id', '__str__', 'advertisement') list_filter = ('advertisement__url', 'source') date_hierarchy = 'created' + + +@admin.register(models.Footer) +class FooterAdmin(admin.ModelAdmin): + """Footer admin.""" + list_display = ('id', 'site', ) + + +@admin.register(models.Panel) +class PanelAdmin(admin.ModelAdmin): + """Panel admin.""" + list_display = ('id', 'created', ) + raw_id_fields = ('user', ) \ No newline at end of file diff --git a/apps/main/models.py b/apps/main/models.py index b39a6037..80fc5a1a 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -6,14 +6,18 @@ from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import JSONField from django.core.validators import EMPTY_VALUES +from django.db import connections, connection from django.db import models from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from rest_framework import exceptions from configuration.models import TranslationSettings from location.models import Country from main import methods from review.models import Review +from utils.exceptions import UnprocessableEntityError +from utils.methods import dictfetchall from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, PlatformMixin) @@ -402,5 +406,85 @@ class Panel(ProjectBaseMixin): def __str__(self): return self.name - def execute_query(self): - pass + def execute_query(self, request): + """Execute query""" + raw = self.query + page = int(request.query_params.get('page', 0)) + page_size = int(request.query_params.get('page_size', 10)) + + if raw: + data = { + "count": 0, + "next": 2, + "previous": None, + "columns": None, + "results": [] + + } + with connections['default'].cursor() as cursor: + count = self._raw_count(raw) + start = page*page_size + cursor.execute(*self.set_limits(start, page_size)) + data["count"] = count + data["next"] = self.get_next_page(count, page, page_size) + data["previous"] = self.get_previous_page(count, page) + data["results"] = dictfetchall(cursor) + data["columns"] = self._raw_columns(cursor) + return data + + def get_next_page(self, count, page, page_size): + max_page = count/page_size-1 + if not 0 <= page <= max_page: + raise exceptions.NotFound('Invalid page.') + if max_page > page: + return page + 1 + return None + + def get_previous_page(self, count, page): + if page > 0: + return page - 1 + return None + + @staticmethod + def _raw_execute(row): + with connections['default'].cursor() as cursor: + try: + cursor.execute(row) + return cursor.execute(row) + except Exception as er: + # TODO: log + raise UnprocessableEntityError() + + def _raw_count(self, subquery): + if ';' in subquery: + subquery = subquery.replace(';', '') + _count_query = f"""SELECT count(*) from ({subquery}) as t;""" + # cursor = self._raw_execute(_count_query) + with connections['default'].cursor() as cursor: + cursor.execute(_count_query) + row = cursor.fetchone() + return row[0] + + @staticmethod + def _raw_columns(cursor): + columns = [col[0] for col in cursor.description] + return columns + + def _raw_page(self, raw, request): + page = request.query_params.get('page', 0) + page_size = request.query_params.get('page_size', 0) + raw = f"""{raw} LIMIT {page_size} OFFSET {page}""" + return raw + + def set_limits(self, start, limit, params=tuple()): + limit_offset = '' + new_params = tuple() + if start > 0: + new_params += (start,) + limit_offset = ' OFFSET %s' + if limit is not None: + new_params = (limit,) + new_params + limit_offset = ' LIMIT %s' + limit_offset + params = params + new_params + query = self.query + limit_offset + return query, params diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 39c51845..0f86daa2 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -296,3 +296,20 @@ class PanelSerializer(serializers.ModelSerializer): 'user', 'user_id' ] + + +class PanelExecuteSerializer(serializers.ModelSerializer): + """Panel execute serializer.""" + class Meta: + model = models.Panel + fields = [ + 'id', + 'name', + 'display', + 'description', + 'query', + 'created', + 'modified', + 'user', + 'user_id' + ] diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 26afd1a6..a2049a42 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -23,8 +23,8 @@ urlpatterns = [ path('page-types/', views.PageTypeListCreateView.as_view(), name='page-types-list-create'), path('panels/', views.PanelsListCreateView.as_view(), name='panels'), - path('panels//', views.PanelsListCreateView.as_view(), name='panels-rud'), - # path('panels//execute/', views.PanelsView.as_view(), name='panels-execute') + path('panels//', views.PanelsRUDView.as_view(), name='panels-rud'), + path('panels//execute/', views.PanelsExecuteView.as_view(), name='panels-execute') ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 0a2b7377..dd898a88 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -1,11 +1,14 @@ from django.contrib.contenttypes.models import ContentType from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions +from rest_framework.generics import get_object_or_404 +from rest_framework.response import Response from main import serializers from main.filters import AwardFilter from main.models import Award, Footer, PageType, Panel from main.views import SiteSettingsView, SiteListView +from utils.pagination import TestPagination class AwardLstView(generics.ListCreateAPIView): @@ -106,4 +109,16 @@ class PanelsRUDView(generics.RetrieveUpdateDestroyAPIView): permissions.IsAdminUser, ) serializer_class = serializers.PanelSerializer - queryset = Panel.objects.all() \ No newline at end of file + queryset = Panel.objects.all() + + +class PanelsExecuteView(generics.ListAPIView): + """Custom panels view.""" + permission_classes = ( + permissions.IsAdminUser, + ) + queryset = Panel.objects.all() + + def list(self, request, *args, **kwargs): + panel = get_object_or_404(Panel, id=self.kwargs['pk']) + return Response(panel.execute_query(request)) diff --git a/apps/transfer/models.py b/apps/transfer/models.py index 8fbb7e97..420e41bc 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -1239,6 +1239,7 @@ class OwnershipAffs(MigrateMixin): managed = False db_table = 'ownership_affs' + class Panels(MigrateMixin): using = 'legacy' diff --git a/apps/utils/exceptions.py b/apps/utils/exceptions.py index c82ff023..08ab433e 100644 --- a/apps/utils/exceptions.py +++ b/apps/utils/exceptions.py @@ -171,3 +171,11 @@ class RemovedBindingObjectNotFound(serializers.ValidationError): """The exception must be thrown if the object not found.""" default_detail = _('Removed binding object not found.') + + +class UnprocessableEntityError(exceptions.APIException): + """ + The exception should be thrown when executing data on server rise error. + """ + status_code = status.HTTP_422_UNPROCESSABLE_ENTITY + default_detail = _('Unprocessable entity valid.') diff --git a/apps/utils/methods.py b/apps/utils/methods.py index 227bd1ee..ef1d6d82 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -132,3 +132,12 @@ def namedtuplefetchall(cursor): desc = cursor.description nt_result = namedtuple('Result', [col[0] for col in desc]) return [nt_result(*row) for row in cursor.fetchall()] + + +def dictfetchall(cursor): + "Return all rows from a cursor as a dict" + columns = [col[0] for col in cursor.description] + return [ + dict(zip(columns, row)) + for row in cursor.fetchall() + ] \ No newline at end of file