From a4d9997580b69973d271841eacdcaa55393042dc Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 13 Jul 2023 19:51:17 +0530 Subject: [PATCH 1/3] web_admin module added, api for article created --- web_admin/__init__.py | 0 web_admin/admin.py | 27 +++++++++++++ web_admin/apps.py | 6 +++ web_admin/migrations/0001_initial.py | 60 ++++++++++++++++++++++++++++ web_admin/migrations/__init__.py | 0 web_admin/models.py | 54 +++++++++++++++++++++++++ web_admin/serializers.py | 55 +++++++++++++++++++++++++ web_admin/tests.py | 3 ++ web_admin/urls.py | 16 ++++++++ web_admin/views.py | 23 +++++++++++ zod_bank/settings.py | 3 ++ zod_bank/urls.py | 1 + 12 files changed, 248 insertions(+) create mode 100644 web_admin/__init__.py create mode 100644 web_admin/admin.py create mode 100644 web_admin/apps.py create mode 100644 web_admin/migrations/0001_initial.py create mode 100644 web_admin/migrations/__init__.py create mode 100644 web_admin/models.py create mode 100644 web_admin/serializers.py create mode 100644 web_admin/tests.py create mode 100644 web_admin/urls.py create mode 100644 web_admin/views.py diff --git a/web_admin/__init__.py b/web_admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/admin.py b/web_admin/admin.py new file mode 100644 index 0000000..b5367ef --- /dev/null +++ b/web_admin/admin.py @@ -0,0 +1,27 @@ +from django.contrib import admin + +from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption + + +@admin.register(Article) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'title', 'description', 'is_published'] + + +@admin.register(ArticleCard) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'article', 'title', 'description', 'image'] + + +@admin.register(ArticleSurvey) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'article', 'question', 'points'] + + +@admin.register(SurveyOption) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'survey', 'option', 'is_answer'] diff --git a/web_admin/apps.py b/web_admin/apps.py new file mode 100644 index 0000000..9701a1f --- /dev/null +++ b/web_admin/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WebAdminConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'web_admin' diff --git a/web_admin/migrations/0001_initial.py b/web_admin/migrations/0001_initial.py new file mode 100644 index 0000000..e652b7c --- /dev/null +++ b/web_admin/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2.2 on 2023-07-13 13:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('is_published', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='ArticleSurvey', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('question', models.CharField(max_length=255)), + ('points', models.IntegerField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ], + ), + migrations.CreateModel( + name='SurveyOption', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('option', models.CharField(max_length=255)), + ('is_answer', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.articlesurvey')), + ], + ), + migrations.CreateModel( + name='ArticleCard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField()), + ('image', models.ImageField(upload_to='card_images/')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ], + ), + ] diff --git a/web_admin/migrations/__init__.py b/web_admin/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/models.py b/web_admin/models.py new file mode 100644 index 0000000..411b0f9 --- /dev/null +++ b/web_admin/models.py @@ -0,0 +1,54 @@ +""" +web_admin model file +""" +# django imports +from django.db import models + + +class Article(models.Model): + title = models.CharField(max_length=255) + description = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_published = models.BooleanField(default=False) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.title}' + + +class ArticleCard(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + title = models.CharField(max_length=255) + description = models.TextField() + image = models.ImageField(upload_to='card_images/') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.title}' + + +class ArticleSurvey(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + question = models.CharField(max_length=255) + points = models.IntegerField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.article}' + + +class SurveyOption(models.Model): + survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE) + option = models.CharField(max_length=255) + is_answer = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.survey}' diff --git a/web_admin/serializers.py b/web_admin/serializers.py new file mode 100644 index 0000000..69fcb00 --- /dev/null +++ b/web_admin/serializers.py @@ -0,0 +1,55 @@ +""" +web_admin serializers file +""" +from rest_framework import serializers + +from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey + + +class ArticleCardSerializer(serializers.ModelSerializer): + class Meta: + model = ArticleCard + fields = ('title', 'description') + + +class SurveyOptionSerializer(serializers.ModelSerializer): + class Meta: + model = SurveyOption + fields = ('option', 'is_answer') + + +class ArticleSurveySerializer(serializers.ModelSerializer): + options = SurveyOptionSerializer(many=True) + + class Meta: + model = ArticleSurvey + fields = ('question', 'points', 'options') + + +class ArticleSerializer(serializers.ModelSerializer): + article_cards = ArticleCardSerializer(many=True) + article_survey = ArticleSurveySerializer(many=True) + + class Meta: + """ + meta class + """ + model = Article + fields = ('title', 'description', 'article_cards', 'article_survey') + + def create(self, validated_data): + article_cards = validated_data.pop('article_cards') + article_survey = validated_data.pop('article_survey') + + article = Article.objects.create(**validated_data) + + for card in article_cards: + ArticleCard.objects.create(article=article, **card) + + for survey in article_survey: + options = survey.pop('options') + survey_obj = ArticleSurvey.objects.create(article=article, **survey) + for option in options: + SurveyOption.objects.create(survey=survey_obj, **option) + + return article diff --git a/web_admin/tests.py b/web_admin/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/web_admin/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/web_admin/urls.py b/web_admin/urls.py new file mode 100644 index 0000000..d79fafa --- /dev/null +++ b/web_admin/urls.py @@ -0,0 +1,16 @@ +""" +web_admin urls file +""" +# django imports +from django.urls import path, include +from rest_framework import routers + +from web_admin.views import ArticleViewSet + +router = routers.SimpleRouter() + +router.register('article', ArticleViewSet, basename='article') + +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/web_admin/views.py b/web_admin/views.py new file mode 100644 index 0000000..7eecd8d --- /dev/null +++ b/web_admin/views.py @@ -0,0 +1,23 @@ +""" +web_admin views file +""" +# django imports +from rest_framework.viewsets import GenericViewSet, mixins +from rest_framework.response import Response +from rest_framework import status + +# local imports +from web_admin.models import Article +from web_admin.serializers import ArticleSerializer + + +class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin): + serializer_class = ArticleSerializer + permission_classes = [] + queryset = Article.objects.all() + + def create(self, request, *args, **kwargs): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response({'message': "created"}, status=status.HTTP_201_CREATED) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index d98d2db..a4c37f5 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'account', 'junior', 'guardian', + 'web_admin', # 'social_django' ] @@ -221,3 +222,5 @@ ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') STATIC_URL = 'static/' STATIC_ROOT = 'static' +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media') diff --git a/zod_bank/urls.py b/zod_bank/urls.py index 274451e..009f1b3 100644 --- a/zod_bank/urls.py +++ b/zod_bank/urls.py @@ -30,4 +30,5 @@ urlpatterns = [ path('', include(('account.urls', 'account'), namespace='account')), path('', include('guardian.urls')), path('', include(('junior.urls', 'junior'), namespace='junior')), + path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')), ] From 45410fa0ca066915cfc4c262430f35ca9c455293 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 14 Jul 2023 18:47:07 +0530 Subject: [PATCH 2/3] list, view, edit api for article --- web_admin/admin.py | 16 +++--- web_admin/migrations/0001_initial.py | 11 ++-- web_admin/models.py | 9 ++-- web_admin/serializers.py | 76 +++++++++++++++++++++++++--- web_admin/utils.py | 13 +++++ web_admin/views.py | 37 ++++++++++++-- 6 files changed, 136 insertions(+), 26 deletions(-) create mode 100644 web_admin/utils.py diff --git a/web_admin/admin.py b/web_admin/admin.py index b5367ef..1adf7e6 100644 --- a/web_admin/admin.py +++ b/web_admin/admin.py @@ -5,23 +5,23 @@ from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption @admin.register(Article) class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" - list_display = ['id', 'title', 'description', 'is_published'] + """Article Admin""" + list_display = ['id', 'title', 'description', 'is_published', 'is_deleted'] @admin.register(ArticleCard) -class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" +class ArticleCardAdmin(admin.ModelAdmin): + """Article Card Admin""" list_display = ['id', 'article', 'title', 'description', 'image'] @admin.register(ArticleSurvey) -class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" +class ArticleSurveyAdmin(admin.ModelAdmin): + """Article Survey Admin""" list_display = ['id', 'article', 'question', 'points'] @admin.register(SurveyOption) -class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" +class SurveyOptionAdmin(admin.ModelAdmin): + """Survey Option Admin""" list_display = ['id', 'survey', 'option', 'is_answer'] diff --git a/web_admin/migrations/0001_initial.py b/web_admin/migrations/0001_initial.py index e652b7c..ad25a50 100644 --- a/web_admin/migrations/0001_initial.py +++ b/web_admin/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.2 on 2023-07-13 13:01 +# Generated by Django 4.2.2 on 2023-07-14 13:15 from django.db import migrations, models import django.db.models.deletion @@ -20,7 +20,8 @@ class Migration(migrations.Migration): ('description', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('is_published', models.BooleanField(default=False)), + ('is_published', models.BooleanField(default=True)), + ('is_deleted', models.BooleanField(default=False)), ], ), migrations.CreateModel( @@ -31,7 +32,7 @@ class Migration(migrations.Migration): ('points', models.IntegerField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_survey', to='web_admin.article')), ], ), migrations.CreateModel( @@ -42,7 +43,7 @@ class Migration(migrations.Migration): ('is_answer', models.BooleanField(default=False)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.articlesurvey')), + ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='survey_options', to='web_admin.articlesurvey')), ], ), migrations.CreateModel( @@ -54,7 +55,7 @@ class Migration(migrations.Migration): ('image', models.ImageField(upload_to='card_images/')), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_cards', to='web_admin.article')), ], ), ] diff --git a/web_admin/models.py b/web_admin/models.py index 411b0f9..0437102 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -10,7 +10,8 @@ class Article(models.Model): description = models.TextField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - is_published = models.BooleanField(default=False) + is_published = models.BooleanField(default=True) + is_deleted = models.BooleanField(default=False) def __str__(self): """Return title""" @@ -18,7 +19,7 @@ class Article(models.Model): class ArticleCard(models.Model): - article = models.ForeignKey(Article, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() image = models.ImageField(upload_to='card_images/') @@ -31,7 +32,7 @@ class ArticleCard(models.Model): class ArticleSurvey(models.Model): - article = models.ForeignKey(Article, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_survey') question = models.CharField(max_length=255) points = models.IntegerField() created_at = models.DateTimeField(auto_now_add=True) @@ -43,7 +44,7 @@ class ArticleSurvey(models.Model): class SurveyOption(models.Model): - survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE) + survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='survey_options') option = models.CharField(max_length=255) is_answer = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 69fcb00..23e69e3 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -4,29 +4,38 @@ web_admin serializers file from rest_framework import serializers from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey +from web_admin.utils import pop_id class ArticleCardSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = ArticleCard - fields = ('title', 'description') + fields = ('id', 'title', 'description') class SurveyOptionSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = SurveyOption - fields = ('option', 'is_answer') + fields = ('id', 'option', 'is_answer') class ArticleSurveySerializer(serializers.ModelSerializer): - options = SurveyOptionSerializer(many=True) + id = serializers.IntegerField(required=False) + survey_options = SurveyOptionSerializer(many=True) class Meta: model = ArticleSurvey - fields = ('question', 'points', 'options') + fields = ('id', 'question', 'points', 'survey_options') class ArticleSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ article_cards = ArticleCardSerializer(many=True) article_survey = ArticleSurveySerializer(many=True) @@ -35,21 +44,76 @@ class ArticleSerializer(serializers.ModelSerializer): meta class """ model = Article - fields = ('title', 'description', 'article_cards', 'article_survey') + fields = ('id', 'title', 'description', 'article_cards', 'article_survey') def create(self, validated_data): + """ + to create article. + ID in post data dict is for update api. + :param validated_data: + :return: success message + """ article_cards = validated_data.pop('article_cards') article_survey = validated_data.pop('article_survey') article = Article.objects.create(**validated_data) for card in article_cards: + card = pop_id(card) ArticleCard.objects.create(article=article, **card) for survey in article_survey: - options = survey.pop('options') + survey = pop_id(survey) + options = survey.pop('survey_options') survey_obj = ArticleSurvey.objects.create(article=article, **survey) for option in options: + option = pop_id(option) SurveyOption.objects.create(survey=survey_obj, **option) return article + + def update(self, instance, validated_data): + article_cards = validated_data.pop('article_cards') + article_survey = validated_data.pop('article_survey') + instance.title = validated_data.get('title', instance.title) + instance.description = validated_data.get('description', instance.description) + instance.save() + + # Update or create cards + for card_data in article_cards: + card_id = card_data.get('id', None) + if card_id: + 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) + card.save() + else: + card_data = pop_id(card_data) + ArticleCard.objects.create(article=instance, **card_data) + + # Update or create survey sections + for survey_data in article_survey: + survey_id = survey_data.get('id', None) + options_data = survey_data.pop('survey_options') + if survey_id: + survey = ArticleSurvey.objects.get(id=survey_id, article=instance) + survey.question = survey_data.get('question', survey.question) + survey.save() + else: + survey_data = pop_id(survey_data) + survey = ArticleSurvey.objects.create(article=instance, **survey_data) + + # Update or create survey options + for option_data in options_data: + option_id = option_data.get('id', None) + if option_id: + option = SurveyOption.objects.get(id=option_id, survey=survey) + option.option = option_data.get('option', option.option) + option.is_answer = option_data.get('is_answer', option.is_answer) + option.save() + else: + option_data = pop_id(option_data) + SurveyOption.objects.create(survey=survey, **option_data) + + return instance diff --git a/web_admin/utils.py b/web_admin/utils.py new file mode 100644 index 0000000..2e09e2f --- /dev/null +++ b/web_admin/utils.py @@ -0,0 +1,13 @@ +""" +web_utils file +""" + + +def pop_id(data): + """ + to pop id, not in use + :param data: + :return: data + """ + data.pop('id') if 'id' in data else data + return data diff --git a/web_admin/views.py b/web_admin/views.py index 7eecd8d..88147a1 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -7,17 +7,48 @@ from rest_framework.response import Response from rest_framework import status # local imports +from account.utils import custom_response, custom_error_response from web_admin.models import Article from web_admin.serializers import ArticleSerializer -class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin): +class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, + mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin): serializer_class = ArticleSerializer permission_classes = [] - queryset = Article.objects.all() + queryset = Article.objects.prefetch_related('article_cards', + 'article_survey', + 'article_survey__survey_options') def create(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return Response({'message': "created"}, status=status.HTTP_201_CREATED) + return custom_response("created") + + def update(self, request, *args, **kwargs): + article = self.queryset.filter(id=kwargs['pk']).first() + serializer = self.serializer_class(article, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response("updated") + + def list(self, request, *args, **kwargs): + # queryset = Article.objects.prefetch_related('article_cards', + # 'article_survey', + # 'article_survey__survey_options') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(self.queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, data=serializer.data) + + def retrieve(self, request, *args, **kwargs): + queryset = self.queryset.filter(id=kwargs['pk']) + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + + def destroy(self, request, *args, **kwargs): + article = self.queryset.filter(id=kwargs['pk']).update(is_deleted=True) + if article: + return custom_response("deleted") + return custom_error_response("article doesn't exist", status.HTTP_400_BAD_REQUEST) From 751af642b8d251eb7249099498476a611a650f4e Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 17 Jul 2023 18:58:09 +0530 Subject: [PATCH 3/3] added admin permission --- base/messages.py | 12 ++++- web_admin/admin.py | 5 ++ web_admin/apps.py | 7 +++ web_admin/models.py | 12 +++++ web_admin/permission.py | 26 +++++++++ web_admin/serializers.py | 45 +++++++++++++++- web_admin/tests.py | 3 ++ web_admin/urls.py | 2 + web_admin/views.py | 112 ++++++++++++++++++++++++++++++++++----- zod_bank/settings.py | 4 ++ 10 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 web_admin/permission.py diff --git a/base/messages.py b/base/messages.py index 82d471a..a13c1d6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -65,7 +65,12 @@ ERROR_CODE = { "2036": "Choose valid user", # log in multiple device msg "2037": "You are already log in another device", - "2038": "Choose valid action for task" + "2038": "Choose valid action for task", + "2039": "Add at least one article card or maximum 6", + "2040": "Add at least 5 article survey or maximum 10", + "2041": "Article with given id doesn't exist.", + "2042": "Article Card with given id doesn't exist.", + "2043": "Article Survey with given id doesn't exist." } """Success message code""" SUCCESS_CODE = { @@ -105,6 +110,11 @@ SUCCESS_CODE = { "3024": "Junior request is rejected successfully", "3025": "Task is approved successfully", "3026": "Task is rejected successfully", + "3027": "Article has been created successfully.", + "3028": "Article has been updated successfully.", + "3029": "Article has been deleted successfully.", + "3030": "Article Card has been removed successfully.", + "3031": "Article Survey has been removed successfully.", } """status code error""" STATUS_CODE_ERROR = { diff --git a/web_admin/admin.py b/web_admin/admin.py index 1adf7e6..b1df483 100644 --- a/web_admin/admin.py +++ b/web_admin/admin.py @@ -1,5 +1,10 @@ +""" +web_admin admin file +""" +# django imports from django.contrib import admin +# local imports from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption diff --git a/web_admin/apps.py b/web_admin/apps.py index 9701a1f..2842e02 100644 --- a/web_admin/apps.py +++ b/web_admin/apps.py @@ -1,6 +1,13 @@ +""" +web_admin app file +""" +# django imports from django.apps import AppConfig class WebAdminConfig(AppConfig): + """ + web admin app config + """ default_auto_field = 'django.db.models.BigAutoField' name = 'web_admin' diff --git a/web_admin/models.py b/web_admin/models.py index 0437102..1e49c6b 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -6,6 +6,9 @@ from django.db import models class Article(models.Model): + """ + Article model + """ title = models.CharField(max_length=255) description = models.TextField() created_at = models.DateTimeField(auto_now_add=True) @@ -19,6 +22,9 @@ class Article(models.Model): class ArticleCard(models.Model): + """ + Article Card model + """ article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() @@ -32,6 +38,9 @@ class ArticleCard(models.Model): class ArticleSurvey(models.Model): + """ + Article Survey model + """ article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_survey') question = models.CharField(max_length=255) points = models.IntegerField() @@ -44,6 +53,9 @@ class ArticleSurvey(models.Model): class SurveyOption(models.Model): + """ + Survey Options model + """ survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='survey_options') option = models.CharField(max_length=255) is_answer = models.BooleanField(default=False) diff --git a/web_admin/permission.py b/web_admin/permission.py new file mode 100644 index 0000000..5ecf33a --- /dev/null +++ b/web_admin/permission.py @@ -0,0 +1,26 @@ +""" +web_admin permission classes +""" +# django imports +from rest_framework import permissions + + +class AdminPermission(permissions.BasePermission): + """ + to check for usertype admin only + """ + def has_permission(self, request, view): + """ + Return True if user_type is admin + """ + if request.user.is_superuser: + return True + return False + + def has_object_permission(self, request, view, obj): + """ + check for object level permission + """ + if request.user.is_superuser: + return True + return False diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 23e69e3..57d4278 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -1,33 +1,55 @@ """ web_admin serializers file """ +# django imports from rest_framework import serializers +from django.conf import settings +# local imports +from base.messages import ERROR_CODE from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey from web_admin.utils import pop_id class ArticleCardSerializer(serializers.ModelSerializer): + """ + Article Card serializer + """ id = serializers.IntegerField(required=False) class Meta: + """ + meta class + """ model = ArticleCard fields = ('id', 'title', 'description') class SurveyOptionSerializer(serializers.ModelSerializer): + """ + survey option serializer + """ id = serializers.IntegerField(required=False) class Meta: + """ + meta class + """ model = SurveyOption fields = ('id', 'option', 'is_answer') class ArticleSurveySerializer(serializers.ModelSerializer): + """ + article survey serializer + """ id = serializers.IntegerField(required=False) survey_options = SurveyOptionSerializer(many=True) class Meta: + """ + meta class + """ model = ArticleSurvey fields = ('id', 'question', 'points', 'survey_options') @@ -46,12 +68,27 @@ class ArticleSerializer(serializers.ModelSerializer): model = Article fields = ('id', 'title', 'description', 'article_cards', 'article_survey') + def validate(self, attrs): + """ + to validate request data + :param attrs: + :return: validated attrs + """ + article_cards = attrs.get('article_cards', None) + article_survey = attrs.get('article_survey', None) + if article_cards is None or len(article_cards) > int(settings.MAX_ARTICLE_CARD): + raise serializers.ValidationError({'details': ERROR_CODE['2039']}) + if article_survey is None or len(article_survey) < int(settings.MIN_ARTICLE_SURVEY) or int( + settings.MAX_ARTICLE_SURVEY) < len(article_survey): + raise serializers.ValidationError({'details': ERROR_CODE['2040']}) + return attrs + def create(self, validated_data): """ to create article. ID in post data dict is for update api. :param validated_data: - :return: success message + :return: article object """ article_cards = validated_data.pop('article_cards') article_survey = validated_data.pop('article_survey') @@ -73,6 +110,12 @@ class ArticleSerializer(serializers.ModelSerializer): return article def update(self, instance, validated_data): + """ + to update article and related table + :param instance: + :param validated_data: + :return: article object + """ article_cards = validated_data.pop('article_cards') article_survey = validated_data.pop('article_survey') instance.title = validated_data.get('title', instance.title) diff --git a/web_admin/tests.py b/web_admin/tests.py index 7ce503c..8800281 100644 --- a/web_admin/tests.py +++ b/web_admin/tests.py @@ -1,3 +1,6 @@ +""" +web_admin test file +""" from django.test import TestCase # Create your tests here. diff --git a/web_admin/urls.py b/web_admin/urls.py index d79fafa..95bc7d9 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -5,8 +5,10 @@ web_admin urls file from django.urls import path, include from rest_framework import routers +# local imports from web_admin.views import ArticleViewSet +# initiate router router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') diff --git a/web_admin/views.py b/web_admin/views.py index 88147a1..8003e2c 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -5,50 +5,138 @@ web_admin views file from rest_framework.viewsets import GenericViewSet, mixins from rest_framework.response import Response from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated # local imports from account.utils import custom_response, custom_error_response -from web_admin.models import Article +from base.messages import SUCCESS_CODE, ERROR_CODE +from web_admin.models import Article, ArticleCard, ArticleSurvey +from web_admin.permission import AdminPermission from web_admin.serializers import ArticleSerializer class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin): + """ + article api + """ serializer_class = ArticleSerializer - permission_classes = [] + permission_classes = [IsAuthenticated, AdminPermission] queryset = Article.objects.prefetch_related('article_cards', 'article_survey', - 'article_survey__survey_options') + 'article_survey__survey_options').order_by('-created_at') + + http_method_names = ['get', 'post', 'put', 'delete'] def create(self, request, *args, **kwargs): + """ + article create api method + :param request: + :param args: + :param kwargs: + :return: success message + """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return custom_response("created") + return custom_response(SUCCESS_CODE["3027"]) def update(self, request, *args, **kwargs): + """ + article update api method + :param request: + :param args: + :param kwargs: + :return: success message + """ article = self.queryset.filter(id=kwargs['pk']).first() serializer = self.serializer_class(article, data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return custom_response("updated") + return custom_response(SUCCESS_CODE["3028"]) def list(self, request, *args, **kwargs): - # queryset = Article.objects.prefetch_related('article_cards', - # 'article_survey', - # 'article_survey__survey_options') + """ + article list api method + :param request: + :param args: + :param kwargs: + :return: list of article + """ + queryset = self.queryset.filter(is_deleted=False) paginator = self.pagination_class() - paginated_queryset = paginator.paginate_queryset(self.queryset, request) + paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) return custom_response(None, data=serializer.data) def retrieve(self, request, *args, **kwargs): - queryset = self.queryset.filter(id=kwargs['pk']) + """ + article detail api method + :param request: + :param args: + :param kwargs: + :return: article detail data + """ + queryset = self.queryset.filter(id=kwargs['pk'], is_deleted=False) serializer = self.serializer_class(queryset, many=True) return custom_response(None, data=serializer.data) def destroy(self, request, *args, **kwargs): + """ + article delete (soft delete) api method + :param request: + :param args: + :param kwargs: + :return: success message + """ article = self.queryset.filter(id=kwargs['pk']).update(is_deleted=True) if article: - return custom_response("deleted") - return custom_error_response("article doesn't exist", status.HTTP_400_BAD_REQUEST) + return custom_response(SUCCESS_CODE["3029"]) + return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) + + @action(methods=['get'], url_name='', url_path='', detail=False) + def search_article(self, request): + """ + article search api method + :param request: + :return: searched article + """ + search = request.GET.get('search') + queryset = self.queryset.filter(title__icontains=search) + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, data=serializer.data) + + @action(methods=['get'], url_name='remove_card', url_path='remove_card', + detail=True, serializer_class=None) + def remove_article_card(self, request, *args, **kwargs): + """ + article card remove (delete) api method + :param request: + :param args: + :param kwargs: + :return: success message + """ + try: + ArticleCard.objects.filter(id=kwargs['pk']).first().delete() + return custom_response(SUCCESS_CODE["3030"]) + except AttributeError: + return custom_error_response(ERROR_CODE["2042"], response_status=status.HTTP_400_BAD_REQUEST) + + @action(methods=['get'], url_name='remove_survey', url_path='remove_survey', + detail=True, serializer_class=None) + def remove_article_survey(self, request, *args, **kwargs): + """ + article survey remove (delete) api method + :param request: + :param args: + :param kwargs: + :return: success message + """ + try: + ArticleSurvey.objects.filter(id=kwargs['pk']).first().delete() + return custom_response(SUCCESS_CODE["3031"]) + except AttributeError: + return custom_error_response(ERROR_CODE["2043"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index a319bbe..b107ffd 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -233,6 +233,10 @@ ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') +MAX_ARTICLE_CARD = os.getenv('MAX_ARTICLE_CARD', 6) +MIN_ARTICLE_SURVEY = os.getenv('MIN_ARTICLE_SURVEY', 5) +MAX_ARTICLE_SURVEY = os.getenv('MAX_ARTICLE_SURVEY', 10) + # define static url STATIC_URL = 'static/' # define static root