From 6e8481411725e6b6585a8567091d9b611ada3067 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 25 Jul 2023 14:09:51 +0530 Subject: [PATCH 1/2] modified yml file, default article card image upload api, --- docker-compose.yml | 2 + web_admin/admin.py | 10 +- .../0003_defaultarticlecardimage_and_more.py | 28 ++++ web_admin/models.py | 16 ++- web_admin/serializers.py | 133 ++++++++++++++++-- web_admin/urls.py | 4 +- web_admin/views.py | 84 ++++++++++- 7 files changed, 255 insertions(+), 22 deletions(-) create mode 100644 web_admin/migrations/0003_defaultarticlecardimage_and_more.py diff --git a/docker-compose.yml b/docker-compose.yml index b9bc35b..edd309d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ services: nginx: image: nginx:latest container_name: nginx + restart: always ports: - "8000:8000" volumes: @@ -13,6 +14,7 @@ services: web: build: . container_name: django + restart: always command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" volumes: - .:/usr/src/app diff --git a/web_admin/admin.py b/web_admin/admin.py index b1df483..29114de 100644 --- a/web_admin/admin.py +++ b/web_admin/admin.py @@ -5,7 +5,7 @@ web_admin admin file from django.contrib import admin # local imports -from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption +from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption, DefaultArticleCardImage @admin.register(Article) @@ -17,7 +17,7 @@ class ArticleAdmin(admin.ModelAdmin): @admin.register(ArticleCard) class ArticleCardAdmin(admin.ModelAdmin): """Article Card Admin""" - list_display = ['id', 'article', 'title', 'description', 'image'] + list_display = ['id', 'article', 'title', 'description', 'image_url'] @admin.register(ArticleSurvey) @@ -30,3 +30,9 @@ class ArticleSurveyAdmin(admin.ModelAdmin): class SurveyOptionAdmin(admin.ModelAdmin): """Survey Option Admin""" list_display = ['id', 'survey', 'option', 'is_answer'] + + +@admin.register(DefaultArticleCardImage) +class DefaultArticleCardImagesAdmin(admin.ModelAdmin): + """Default Article Card Images Option Admin""" + list_display = ['image_name', 'image_url'] diff --git a/web_admin/migrations/0003_defaultarticlecardimage_and_more.py b/web_admin/migrations/0003_defaultarticlecardimage_and_more.py new file mode 100644 index 0000000..bf2f66f --- /dev/null +++ b/web_admin/migrations/0003_defaultarticlecardimage_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.2 on 2023-07-24 14:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0002_alter_articlecard_image'), + ] + + operations = [ + migrations.CreateModel( + name='DefaultArticleCardImage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image_name', models.CharField(max_length=20)), + ('image_url', models.URLField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + migrations.RenameField( + model_name='articlecard', + old_name='image', + new_name='image_url', + ), + ] diff --git a/web_admin/models.py b/web_admin/models.py index 30f0550..950eec6 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -28,7 +28,7 @@ class ArticleCard(models.Model): article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() - image = models.URLField(null=True, blank=True, default=None) + image_url = models.URLField(null=True, blank=True, default=None) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -65,3 +65,17 @@ class SurveyOption(models.Model): def __str__(self): """Return title""" return f'{self.id} | {self.survey}' + + +class DefaultArticleCardImage(models.Model): + """ + Default images upload in oss bucket + """ + image_name = models.CharField(max_length=20) + image_url = models.URLField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """return image_name as an object""" + return self.image_name diff --git a/web_admin/serializers.py b/web_admin/serializers.py index b9f6fa9..66bac06 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -3,14 +3,18 @@ web_admin serializers file """ # django imports from rest_framework import serializers +from django.contrib.auth import get_user_model -from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY +from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, \ + USER_TYPE # local imports from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba -from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey +from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage from web_admin.utils import pop_id +USER = get_user_model() + class ArticleCardSerializer(serializers.ModelSerializer): """ @@ -18,23 +22,25 @@ class ArticleCardSerializer(serializers.ModelSerializer): """ id = serializers.IntegerField(required=False) image = serializers.FileField(required=False) + image_url = serializers.URLField(required=False) class Meta: """ meta class """ model = ArticleCard - fields = ('id', 'title', 'description', 'image') + fields = ('id', 'title', 'description', 'image', 'image_url') def create(self, validated_data): - image = validated_data.get('image', '') - filename = f"article/{image.name}" - # upload image on ali baba - validated_data['image'] = upload_image_to_alibaba(image, filename) - + if 'image' in validated_data and validated_data['image'] is not None: + image = validated_data.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + validated_data['image_url'] = upload_image_to_alibaba(image, filename) article_card = ArticleCard.objects.create(article_id='1', **validated_data) return article_card + class SurveyOptionSerializer(serializers.ModelSerializer): """ survey option serializer @@ -81,7 +87,6 @@ class ArticleSerializer(serializers.ModelSerializer): def validate(self, attrs): """ to validate request data - :param attrs: :return: validated attrs """ article_cards = attrs.get('article_cards', None) @@ -97,7 +102,6 @@ class ArticleSerializer(serializers.ModelSerializer): """ to create article. ID in post data dict is for update api. - :param validated_data: :return: article object """ article_cards = validated_data.pop('article_cards') @@ -107,6 +111,11 @@ class ArticleSerializer(serializers.ModelSerializer): for card in article_cards: card = pop_id(card) + if 'image' in card and card['image'] is not None: + image = card.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + card['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=article, **card) for survey in article_survey: @@ -122,8 +131,7 @@ class ArticleSerializer(serializers.ModelSerializer): def update(self, instance, validated_data): """ to update article and related table - :param instance: - :param validated_data: + :param instance: article object, :return: article object """ article_cards = validated_data.pop('article_cards') @@ -139,10 +147,19 @@ class ArticleSerializer(serializers.ModelSerializer): card = ArticleCard.objects.get(id=card_id, article=instance) card.title = card_data.get('title', card.title) card.description = card_data.get('description', card.description) - card.image = card_data.get('image', card.image) + if 'image' in card_data and card_data['image'] is not None: + image = card_data.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + card.image_url = upload_image_to_alibaba(image, filename) card.save() else: card_data = pop_id(card_data) + if 'image' in card_data and card_data['image'] is not None: + image = card_data.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + card_data['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=instance, **card_data) # Update or create survey sections @@ -170,3 +187,93 @@ class ArticleSerializer(serializers.ModelSerializer): SurveyOption.objects.create(survey=survey, **option_data) return instance + + +class DefaultArticleCardImageSerializer(serializers.ModelSerializer): + """ + Article Card serializer + """ + image = serializers.FileField(required=False) + image_url = serializers.URLField(required=False) + + class Meta: + """ + meta class + """ + model = DefaultArticleCardImage + fields = ('image_name', 'image', 'image_url') + + def validate(self, attrs): + """ + to validate data + :return: validated data + """ + if 'image' not in attrs and attrs.get('image') is None: + raise serializers.ValidationError({'details': 'insert image'}) + return attrs + + def create(self, validated_data): + """ + to create and upload image + :return: card_image object + """ + image = validated_data.pop('image') + filename = f"article/{image.name}" + if image and image.size == NUMBER['zero']: + raise serializers.ValidationError(ERROR_CODE['2035']) + # upload image on ali baba + validated_data['image_url'] = upload_image_to_alibaba(image, filename) + + card_image = DefaultArticleCardImage.objects.create(**validated_data) + return card_image + + +class UserManagementListSerializer(serializers.ModelSerializer): + """ + user management serializer + """ + name = serializers.SerializerMethodField() + phone_number = serializers.SerializerMethodField() + user_type = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('name', 'email', 'phone_number', 'user_type', 'is_active') + + @staticmethod + def get_name(obj): + """ + :param obj: user object + :return: full name + """ + return (obj.first_name + obj.last_name) if obj.last_name else obj.first_name + + @staticmethod + def get_phone_number(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return profile.phone + elif profile := obj.junior_profile.all().first(): + return profile.phone + else: + return None + + @staticmethod + def get_user_type(obj): + """ + :param obj: user object + :return: user type + """ + if obj.guardian_profile.all().first(): + return dict(USER_TYPE).get('2') + elif obj.junior_profile.all().first(): + return dict(USER_TYPE).get('1') + else: + return None + diff --git a/web_admin/urls.py b/web_admin/urls.py index 95bc7d9..3fe7eb2 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,12 +6,14 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views import ArticleViewSet +from web_admin.views import ArticleViewSet, DefaultArticleCardImagesViewSet, UserManagementViewSet # initiate router router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') +router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') +router.register('user', UserManagementViewSet, basename='user') urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/web_admin/views.py b/web_admin/views.py index b387a97..c40cd54 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -3,18 +3,22 @@ web_admin views file """ # django imports from rest_framework.viewsets import GenericViewSet, mixins -from rest_framework.response import Response from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework import status from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny +from django.contrib.auth import get_user_model # local imports from account.utils import custom_response, custom_error_response +from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE -from web_admin.models import Article, ArticleCard, ArticleSurvey +from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission -from web_admin.serializers import ArticleSerializer, ArticleCardSerializer +from web_admin.serializers import (ArticleSerializer, ArticleCardSerializer, DefaultArticleCardImageSerializer, + UserManagementListSerializer) + +USER = get_user_model() class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, @@ -137,7 +141,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_error_response(ERROR_CODE["2043"], response_status=status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='test-add-card', url_path='test-add-card', - detail=False, serializer_class=ArticleCardSerializer) + detail=False, serializer_class=ArticleCardSerializer, permission_classes=[AllowAny]) def add_card(self, request): """ :param request: @@ -147,3 +151,73 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel serializer.is_valid(raise_exception=True) serializer.save() return custom_response(SUCCESS_CODE["3000"]) + + @action(methods=['get'], url_name='test-list-card', url_path='test-list-card', + detail=False, serializer_class=ArticleCardSerializer, permission_classes=[AllowAny]) + def list_card(self, request): + """ + :param request: + :return: + """ + queryset = ArticleCard.objects.all() + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, serializer.data) + + +class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.ListModelMixin): + """ + api to upload and list default article card images + """ + serializer_class = DefaultArticleCardImageSerializer + permission_classes = [IsAuthenticated, AdminPermission] + queryset = DefaultArticleCardImage.objects.all() + + def create(self, request, *args, **kwargs): + """ + api method to upload default article card images + :param request: + :return: success message + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE["3000"]) + + def list(self, request, *args, **kwargs): + """ + api method to list default article card images + :param request: + :return: default article card images + """ + queryset = self.queryset + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + + +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin): + """ + api to manage (list, view, edit) user + """ + serializer_class = UserManagementListSerializer + permission_classes = [] + queryset = USER.objects.prefetch_related( + 'guardian_profile', 'junior_profile') + + def get_queryset(self): + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + return self.queryset.filter(junior_profile__isnull=True) + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + return self.queryset.filter(guardian_profile__isnull=True) + else: + return self.queryset + + def list(self, request, *args, **kwargs): + """ + api method to list all the user + :param request: + :return: + """ + queryset = self.get_queryset() + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + From 32476149bce05657473f7a922d7517183fefef1a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 25 Jul 2023 15:33:22 +0530 Subject: [PATCH 2/2] article card folder name change --- base/constants.py | 2 ++ web_admin/serializers.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/base/constants.py b/base/constants.py index b351cde..3c1501c 100644 --- a/base/constants.py +++ b/base/constants.py @@ -107,3 +107,5 @@ MAX_ARTICLE_SURVEY = 10 # real time url time_url = "http://worldtimeapi.org/api/timezone/Asia/Riyadh" + +ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 7027c47..185482f 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -5,8 +5,8 @@ web_admin serializers file from rest_framework import serializers from django.contrib.auth import get_user_model -from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, \ - USER_TYPE +from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, + USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER) # local imports from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba @@ -34,7 +34,7 @@ class ArticleCardSerializer(serializers.ModelSerializer): def create(self, validated_data): if 'image' in validated_data and validated_data['image'] is not None: image = validated_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba validated_data['image_url'] = upload_image_to_alibaba(image, filename) article_card = ArticleCard.objects.create(article_id='1', **validated_data) @@ -113,7 +113,7 @@ class ArticleSerializer(serializers.ModelSerializer): card = pop_id(card) if 'image' in card and card['image'] is not None: image = card.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba card['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=article, **card) @@ -149,7 +149,7 @@ class ArticleSerializer(serializers.ModelSerializer): card.description = card_data.get('description', card.description) if 'image' in card_data and card_data['image'] is not None: image = card_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba card.image_url = upload_image_to_alibaba(image, filename) card.save() @@ -157,7 +157,7 @@ class ArticleSerializer(serializers.ModelSerializer): card_data = pop_id(card_data) if 'image' in card_data and card_data['image'] is not None: image = card_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba card_data['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=instance, **card_data) @@ -218,7 +218,7 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): :return: card_image object """ image = validated_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" if image and image.size == NUMBER['zero']: raise serializers.ValidationError(ERROR_CODE['2035']) # upload image on ali baba @@ -276,4 +276,3 @@ class UserManagementListSerializer(serializers.ModelSerializer): return dict(USER_TYPE).get('1') else: return None -