From 562d8689a775b2b4a73789e68259ff007ff4a8e6 Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 10:55:10 +0500 Subject: [PATCH 1/9] Added filters for title and content --- articles/views.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/articles/views.py b/articles/views.py index 53b4395..d27e984 100644 --- a/articles/views.py +++ b/articles/views.py @@ -1,3 +1,4 @@ +from django.db.models import Q from rest_framework import generics from articles.models import Article @@ -8,6 +9,25 @@ class ArticleListCreateAPIView(generics.ListCreateAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer + def get_queryset(self): + """ + added params filter for 'title' and 'content' + """ + title = self.request.query_params.get('title') + content = self.request.query_params.get('content') + + if title is not None and content is None: + queryset = self.queryset.filter(title__exact=title) + return queryset + elif content is not None and title is None: + queryset = self.queryset.filter(content__exact=content) + return queryset + elif content and title: + queryset = self.queryset.filter(Q(content__exact=content) & Q(title__exact=title)) + return queryset + + return self.queryset.all() + class ArticleDetailAPIView(generics.RetrieveUpdateDestroyAPIView): queryset = Article.objects.all() From 04a8f4f55ef391ea212f5810da59a467d4cdca78 Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 11:01:46 +0500 Subject: [PATCH 2/9] Added ordering_fields --- articles/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/articles/views.py b/articles/views.py index d27e984..9d79bbc 100644 --- a/articles/views.py +++ b/articles/views.py @@ -8,6 +8,7 @@ class ArticleListCreateAPIView(generics.ListCreateAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer + ordering_fields = ['title', 'created_at'] def get_queryset(self): """ From b041d26356b6759317a59c8b48151aba4fcb3097 Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 11:08:30 +0500 Subject: [PATCH 3/9] added test cases for filtering article and content --- articles/tests/factories.py | 1 + articles/tests/test_views.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/articles/tests/factories.py b/articles/tests/factories.py index 14a7a65..ee1d86e 100644 --- a/articles/tests/factories.py +++ b/articles/tests/factories.py @@ -6,6 +6,7 @@ class ArticleFactory(factory.django.DjangoModelFactory): title = factory.Sequence(lambda n: f"Article {n}") slug = factory.Sequence(lambda n: f"article-{n}") + content = factory.Sequence(lambda n: f"content-{n}") class Meta: model = Article diff --git a/articles/tests/test_views.py b/articles/tests/test_views.py index 3a8612b..f1ccdf6 100644 --- a/articles/tests/test_views.py +++ b/articles/tests/test_views.py @@ -28,6 +28,27 @@ def test_create(self): article = Article.objects.get(id=response.data["id"]) # newly-created self.assertEqual(article.slug, "test-article") + def test_title_filter(self): + article1, article2 = ArticleFactory.create_batch(2) + response = self.client.get(f'{reverse("article_list")}?title={article1.title}') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0].get('title'), article1.title) + + def test_content_filter(self): + article1, article2 = ArticleFactory.create_batch(2) + response = self.client.get(f'{reverse("article_list")}?content={article1.content}') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0].get('content'), article1.content) + + def test_content_and_title_filter(self): + article1, article2 = ArticleFactory.create_batch(2) + response = self.client.get(f'{reverse("article_list")}?content={article1.content}&title={article1.title}') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0].get('content'), article1.content) + class ArticleDetailAPIViewTest(APITestCase): def test_update(self): From 66b281f8c6c231afbdf7694ddcbf91ca63b34b20 Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 11:32:39 +0500 Subject: [PATCH 4/9] added more test scenarios --- articles/tests/test_views.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/articles/tests/test_views.py b/articles/tests/test_views.py index f1ccdf6..9149d0c 100644 --- a/articles/tests/test_views.py +++ b/articles/tests/test_views.py @@ -28,6 +28,18 @@ def test_create(self): article = Article.objects.get(id=response.data["id"]) # newly-created self.assertEqual(article.slug, "test-article") + def test_not_found_title_filter(self): + article1, article2 = ArticleFactory.create_batch(2) + response = self.client.get(f'{reverse("article_list")}?title=xyz') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_not_found_content_filter(self): + article1, article2 = ArticleFactory.create_batch(2) + response = self.client.get(f'{reverse("article_list")}?content=xyz') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + def test_title_filter(self): article1, article2 = ArticleFactory.create_batch(2) response = self.client.get(f'{reverse("article_list")}?title={article1.title}') @@ -42,6 +54,12 @@ def test_content_filter(self): self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0].get('content'), article1.content) + def test_not_found_content_and_title_filter(self): + article1, article2 = ArticleFactory.create_batch(2) + response = self.client.get(f'{reverse("article_list")}?content=xyz&title=xyz') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + def test_content_and_title_filter(self): article1, article2 = ArticleFactory.create_batch(2) response = self.client.get(f'{reverse("article_list")}?content={article1.content}&title={article1.title}') From c432bb5d8cd8daa98c125f3fed8b3bc82c4ceda5 Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 11:39:05 +0500 Subject: [PATCH 5/9] added tag and api to create and list --- articles/admin.py | 3 +- .../migrations/0002_auto_20210510_0638.py | 28 +++++++++++++++++++ articles/models.py | 10 +++++++ articles/serializers.py | 8 +++++- articles/tests/test_views.py | 14 ++++++---- articles/views.py | 22 ++++++++++----- project/urls.py | 7 ++++- 7 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 articles/migrations/0002_auto_20210510_0638.py diff --git a/articles/admin.py b/articles/admin.py index 16546e4..721ea45 100644 --- a/articles/admin.py +++ b/articles/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from articles.models import Article +from articles.models import Article, Tag admin.site.register(Article) +admin.site.register(Tag) diff --git a/articles/migrations/0002_auto_20210510_0638.py b/articles/migrations/0002_auto_20210510_0638.py new file mode 100644 index 0000000..b203571 --- /dev/null +++ b/articles/migrations/0002_auto_20210510_0638.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.2 on 2021-05-10 06:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('articles', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('name', models.CharField(max_length=25)), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='articles.tag')), + ], + ), + migrations.AddField( + model_name='article', + name='tag', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='articles.tag'), + ), + ] diff --git a/articles/models.py b/articles/models.py index 0a6c8dc..e25c697 100644 --- a/articles/models.py +++ b/articles/models.py @@ -7,9 +7,19 @@ class Article(models.Model): title = models.CharField(max_length=32) slug = models.CharField(max_length=32, unique=True) content = models.TextField() + tag = models.ForeignKey("Tag", on_delete=models.CASCADE, blank=True, null=True) class Meta: ordering = ["id"] def __str__(self): return self.title + + +class Tag(models.Model): + parent = models.ForeignKey("Tag", on_delete=models.CASCADE, blank=True, null=True) + slug = models.SlugField() + name = models.CharField(max_length=25) + + def __str__(self): + return f"name:<{self.name}>-slug<{self.slug}>" diff --git a/articles/serializers.py b/articles/serializers.py index 291c709..49386b9 100644 --- a/articles/serializers.py +++ b/articles/serializers.py @@ -1,9 +1,15 @@ from rest_framework import serializers -from articles.models import Article +from articles.models import Article, Tag class ArticleSerializer(serializers.ModelSerializer): class Meta: model = Article fields = "__all__" + + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + fields = "__all__" diff --git a/articles/tests/test_views.py b/articles/tests/test_views.py index 9149d0c..bed288e 100644 --- a/articles/tests/test_views.py +++ b/articles/tests/test_views.py @@ -45,14 +45,16 @@ def test_title_filter(self): response = self.client.get(f'{reverse("article_list")}?title={article1.title}') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0].get('title'), article1.title) + self.assertEqual(response.data[0].get("title"), article1.title) def test_content_filter(self): article1, article2 = ArticleFactory.create_batch(2) - response = self.client.get(f'{reverse("article_list")}?content={article1.content}') + response = self.client.get( + f'{reverse("article_list")}?content={article1.content}' + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0].get('content'), article1.content) + self.assertEqual(response.data[0].get("content"), article1.content) def test_not_found_content_and_title_filter(self): article1, article2 = ArticleFactory.create_batch(2) @@ -62,10 +64,12 @@ def test_not_found_content_and_title_filter(self): def test_content_and_title_filter(self): article1, article2 = ArticleFactory.create_batch(2) - response = self.client.get(f'{reverse("article_list")}?content={article1.content}&title={article1.title}') + response = self.client.get( + f'{reverse("article_list")}?content={article1.content}&title={article1.title}' + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0].get('content'), article1.content) + self.assertEqual(response.data[0].get("content"), article1.content) class ArticleDetailAPIViewTest(APITestCase): diff --git a/articles/views.py b/articles/views.py index 9d79bbc..a47adf4 100644 --- a/articles/views.py +++ b/articles/views.py @@ -1,21 +1,22 @@ -from django.db.models import Q from rest_framework import generics -from articles.models import Article -from articles.serializers import ArticleSerializer +from django.db.models import Q + +from articles.models import Article, Tag +from articles.serializers import ArticleSerializer, TagSerializer class ArticleListCreateAPIView(generics.ListCreateAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer - ordering_fields = ['title', 'created_at'] + ordering_fields = ["title", "created_at"] def get_queryset(self): """ added params filter for 'title' and 'content' """ - title = self.request.query_params.get('title') - content = self.request.query_params.get('content') + title = self.request.query_params.get("title") + content = self.request.query_params.get("content") if title is not None and content is None: queryset = self.queryset.filter(title__exact=title) @@ -24,7 +25,9 @@ def get_queryset(self): queryset = self.queryset.filter(content__exact=content) return queryset elif content and title: - queryset = self.queryset.filter(Q(content__exact=content) & Q(title__exact=title)) + queryset = self.queryset.filter( + Q(content__exact=content) & Q(title__exact=title) + ) return queryset return self.queryset.all() @@ -33,3 +36,8 @@ def get_queryset(self): class ArticleDetailAPIView(generics.RetrieveUpdateDestroyAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer + + +class TagListCreateAPIView(generics.ListCreateAPIView): + queryset = Tag.objects.all() + serializer_class = TagSerializer diff --git a/project/urls.py b/project/urls.py index 6b47691..666d46e 100644 --- a/project/urls.py +++ b/project/urls.py @@ -16,7 +16,11 @@ from django.contrib import admin from django.urls import path -from articles.views import ArticleDetailAPIView, ArticleListCreateAPIView +from articles.views import ( + ArticleDetailAPIView, + ArticleListCreateAPIView, + TagListCreateAPIView, +) urlpatterns = [ path("admin/", admin.site.urls), @@ -24,4 +28,5 @@ path( "api/articles//", ArticleDetailAPIView.as_view(), name="article_detail" ), + path("api/tag/", TagListCreateAPIView.as_view(), name="article_list_create"), ] From a796b0274bb5dc63e8a372bacd3fe17ed5b56a9b Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 12:21:37 +0500 Subject: [PATCH 6/9] added API to be able to add/remove Tag(s) to Article(s). --- .../migrations/0002_auto_20210510_0638.py | 39 ++++++++++++++----- articles/serializers.py | 6 +++ articles/views.py | 24 +++++++++++- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/articles/migrations/0002_auto_20210510_0638.py b/articles/migrations/0002_auto_20210510_0638.py index b203571..3a4a91e 100644 --- a/articles/migrations/0002_auto_20210510_0638.py +++ b/articles/migrations/0002_auto_20210510_0638.py @@ -7,22 +7,43 @@ class Migration(migrations.Migration): dependencies = [ - ('articles', '0001_initial'), + ("articles", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Tag', + name="Tag", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('slug', models.SlugField()), - ('name', models.CharField(max_length=25)), - ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='articles.tag')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("slug", models.SlugField()), + ("name", models.CharField(max_length=25)), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="articles.tag", + ), + ), ], ), migrations.AddField( - model_name='article', - name='tag', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='articles.tag'), + model_name="article", + name="tag", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="articles.tag", + ), ), ] diff --git a/articles/serializers.py b/articles/serializers.py index 49386b9..4dc1f25 100644 --- a/articles/serializers.py +++ b/articles/serializers.py @@ -8,6 +8,12 @@ class Meta: model = Article fields = "__all__" + def perform_delete_on_tag(self): + tag = self.instance.tag + if tag: + self.instance.tag = None + self.instance.save() + class TagSerializer(serializers.ModelSerializer): class Meta: diff --git a/articles/views.py b/articles/views.py index a47adf4..6c9d584 100644 --- a/articles/views.py +++ b/articles/views.py @@ -1,4 +1,5 @@ -from rest_framework import generics +from rest_framework import generics, status +from rest_framework.response import Response from django.db.models import Q @@ -37,6 +38,27 @@ class ArticleDetailAPIView(generics.RetrieveUpdateDestroyAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer + def destroy(self, request, pk): + # Note the use of `get_queryset()` instead of `self.queryset` + remove_tag = request.query_params.get("removeTag") + instance = self.get_object() + + try: + if remove_tag and self.validate_remove_tag(remove_tag=remove_tag): + self.serializer_class(instance=instance).perform_delete_on_tag() + return Response(status=status.HTTP_204_NO_CONTENT) + except ValueError as error: + return Response( + data=dict(error=str(error)), status=status.HTTP_400_BAD_REQUEST + ) + self.perform_destroy(instance) + return Response(status=status.HTTP_204_NO_CONTENT) + + def validate_remove_tag(self, remove_tag): + if remove_tag.lower() != "true": + raise ValueError("specify 'removeTag' to be true") + return True + class TagListCreateAPIView(generics.ListCreateAPIView): queryset = Tag.objects.all() From 3ce02dba8ca725a0d8bbf1e34637601aa5147e2a Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 12:40:47 +0500 Subject: [PATCH 7/9] added API to list Articles by Tag. --- articles/serializers.py | 1 + articles/views.py | 12 ++++++++++++ project/urls.py | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/articles/serializers.py b/articles/serializers.py index 4dc1f25..51fe34d 100644 --- a/articles/serializers.py +++ b/articles/serializers.py @@ -7,6 +7,7 @@ class ArticleSerializer(serializers.ModelSerializer): class Meta: model = Article fields = "__all__" + depth = 2 def perform_delete_on_tag(self): tag = self.instance.tag diff --git a/articles/views.py b/articles/views.py index 6c9d584..fa91bb6 100644 --- a/articles/views.py +++ b/articles/views.py @@ -63,3 +63,15 @@ def validate_remove_tag(self, remove_tag): class TagListCreateAPIView(generics.ListCreateAPIView): queryset = Tag.objects.all() serializer_class = TagSerializer + + +class ArticleTagRetrieveAPIView(generics.RetrieveAPIView): + queryset = Article.objects.all() + serializer_class = ArticleSerializer + lookup_url_kwarg = "tag" + + def get(self, request, *args, **kwargs): + key = self.kwargs[self.lookup_url_kwarg] + article = Article.objects.filter(tag_id=key) + serializer = self.serializer_class(article, many=True) + return Response(serializer.data) diff --git a/project/urls.py b/project/urls.py index 666d46e..81f887c 100644 --- a/project/urls.py +++ b/project/urls.py @@ -19,6 +19,7 @@ from articles.views import ( ArticleDetailAPIView, ArticleListCreateAPIView, + ArticleTagRetrieveAPIView, TagListCreateAPIView, ) @@ -29,4 +30,9 @@ "api/articles//", ArticleDetailAPIView.as_view(), name="article_detail" ), path("api/tag/", TagListCreateAPIView.as_view(), name="article_list_create"), + path( + "api/articles/tag//", + ArticleTagRetrieveAPIView.as_view(), + name="article_tag_retrieve", + ), ] From 08f7d2b60651789a45d31e9ba9ab38ac77ba43fa Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 12:44:55 +0500 Subject: [PATCH 8/9] added Tag update/delete functionality (optional) --- articles/apps.py | 3 +++ articles/signals.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 articles/signals.py diff --git a/articles/apps.py b/articles/apps.py index 9baf7c9..fea54c3 100644 --- a/articles/apps.py +++ b/articles/apps.py @@ -4,3 +4,6 @@ class ArticlesConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "articles" + + def ready(self): + import articles.signals diff --git a/articles/signals.py b/articles/signals.py new file mode 100644 index 0000000..9cb20b7 --- /dev/null +++ b/articles/signals.py @@ -0,0 +1,26 @@ +from rest_framework.exceptions import NotAcceptable + +from django.db.models.signals import pre_delete, pre_save +from django.dispatch import receiver + +from articles.models import Article, Tag + + +@receiver(pre_delete, sender=Tag) +def check_tag_before_del(sender, instance, using, **kwargs): + tag_present = Article.objects.filter(tag_id=instance.id) + if tag_present: + raise NotAcceptable(detail="This tag is used by article", code=400) + + +@receiver(pre_save, sender=Tag) +def check_tag_before_del(sender, instance, using, update_fields, **kwargs): + tag_present = Article.objects.filter(tag_id=instance.id) + if tag_present: + updated_slug = instance.slug + previous_slug = tag_present[0].slug + if updated_slug != previous_slug: + raise NotAcceptable( + detail="This tag is used by article and 'slug' cannot be updated", + code=400, + ) From 482a76be88fd275123026d7ba6f5653285339721 Mon Sep 17 00:00:00 2001 From: Wajahat Date: Mon, 10 May 2021 12:46:42 +0500 Subject: [PATCH 9/9] added postman test collection --- testing dewlea.postman_collection.json | 247 +++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 testing dewlea.postman_collection.json diff --git a/testing dewlea.postman_collection.json b/testing dewlea.postman_collection.json new file mode 100644 index 0000000..8d157d3 --- /dev/null +++ b/testing dewlea.postman_collection.json @@ -0,0 +1,247 @@ +{ + "info": { + "_postman_id": "e52034b4-5547-4839-bfb1-c569428f7326", + "name": "testing dewlea", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "get all articles", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://127.0.0.1:8000/api/articles/", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "articles", + "" + ] + } + }, + "response": [] + }, + { + "name": "get articles on pk", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://127.0.0.1:8000/api/articles/2", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "articles", + "2" + ] + } + }, + "response": [] + }, + { + "name": "add tag to article", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \n \"title\": \"csacas\",\n \"slug\": \"cascasc\",\n \"content\": \"ascascscc\",\n \"tag\": 1\n }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://127.0.0.1:8000/api/articles/1/", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "articles", + "1", + "" + ] + } + }, + "response": [] + }, + { + "name": "remove tag from article", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://127.0.0.1:8000/api/articles/1/?removeTag=true", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "articles", + "1", + "" + ], + "query": [ + { + "key": "removeTag", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "get article on title and content", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://127.0.0.1:8000/api/articles/?title=dwaawedw&content=edww", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "articles", + "" + ], + "query": [ + { + "key": "title", + "value": "dwaawedw" + }, + { + "key": "content", + "value": "edww" + } + ] + } + }, + "response": [] + }, + { + "name": "get all tags", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://127.0.0.1:8000/api/tag", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "tag" + ] + } + }, + "response": [] + }, + { + "name": "create tags", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \n \"slug\": \"test12\",\n \"name\": \"what is\",\n \"parent\": 1\n }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://127.0.0.1:8000/api/tag/", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "tag", + "" + ] + } + }, + "response": [] + }, + { + "name": "list Articles by Tag", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://127.0.0.1:8000/api/articles/tag/4/", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8000", + "path": [ + "api", + "articles", + "tag", + "4", + "" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file