django-drf-blog-api 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2024 Osiota Samuel Obrozie
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include LICENSE
2
+ include README.rst
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.1
2
+ Name: django-drf-blog-api
3
+ Version: 0.0.1
4
+ Summary: A Django app blog API endpoints.
5
+ Home-page: https://www.example.com/
6
+ Author: Osiota Samuel Obrozie
7
+ Author-email: osiotaobrozie@gmail.com
8
+ License: BSD-3-Clause # Example license
9
+ Platform: UNKNOWN
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Framework :: Django
12
+ Classifier: Framework :: Django :: 5.0
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+
28
+
29
+ # django-drf-blog-api
30
+
31
+
32
+ django-drf-blog-api is a Django blog API app
33
+
34
+
35
+
36
+ ## Quick start
37
+
38
+
39
+ 1. Add "Blog" to your INSTALLED_APPS setting like this::
40
+
41
+ INSTALLED_APPS = [
42
+ ...,
43
+ "django-drf-blog-api",
44
+ ]
45
+ ```
46
+ 2. Include the polls URLconf in your project urls.py like this::
47
+
48
+ path("blog/", include("django-drf-blog-api.urls")),
49
+
50
+ 3. Run ``python manage.py migrate`` to create the models.
51
+
52
+ 4. Start the development server and visit the admin to create a poll.
53
+
54
+ 5. Visit the ``/blog/`` URL to participate in the poll.
55
+
@@ -0,0 +1,27 @@
1
+
2
+ # django-drf-blog-api
3
+
4
+
5
+ django-drf-blog-api is a Django blog API app
6
+
7
+
8
+
9
+ ## Quick start
10
+
11
+
12
+ 1. Add "Blog" to your INSTALLED_APPS setting like this::
13
+
14
+ INSTALLED_APPS = [
15
+ ...,
16
+ "django-drf-blog-api",
17
+ ]
18
+ ```
19
+ 2. Include the polls URLconf in your project urls.py like this::
20
+
21
+ path("blog/", include("django-drf-blog-api.urls")),
22
+
23
+ 3. Run ``python manage.py migrate`` to create the models.
24
+
25
+ 4. Start the development server and visit the admin to create a poll.
26
+
27
+ 5. Visit the ``/blog/`` URL to participate in the poll.
@@ -0,0 +1,38 @@
1
+ from django.contrib import admin
2
+
3
+ from . models import *
4
+ from django.contrib.auth.admin import UserAdmin
5
+ from .models import UserAccount
6
+ from .forms import UserChangeForm, UserCreationForm
7
+
8
+
9
+ class CustomUserAdmin(UserAdmin):
10
+ form = UserChangeForm
11
+ add_form = UserCreationForm
12
+ ordering = ('email',)
13
+ list_display = ('first_name', 'last_name', 'email',
14
+ 'is_featured', 'is_active', 'is_staff', 'is_superuser')
15
+ model = UserAccount
16
+ fieldsets = (
17
+ (None, {'fields': ('image', 'password',)}),
18
+ ('Personal info', {
19
+ 'fields': ('first_name', 'last_name', 'email', 'gender', 'phone_number', 'date_of_birth',)}),
20
+ ('Featured Athlete', {
21
+ 'fields': ('is_featured',)}),
22
+ ('Contact Address', {
23
+ 'fields': ('home_address', 'local_govt', 'state_of_origin', 'nationality',)}),
24
+ ('Permissions', {
25
+ 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
26
+ ('Important dates', {
27
+ 'fields': ('date_joined', 'last_login',)}),
28
+ )
29
+
30
+ add_fieldsets = (
31
+ (None, {
32
+ 'classes': ('wide',),
33
+ 'fields': ('first_name', 'last_name', 'email', 'phone_number', 'password1', 'password2')}
34
+ ),
35
+ )
36
+
37
+
38
+ admin.site.register(UserAccount, CustomUserAdmin)
@@ -0,0 +1,9 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class BlogConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'django_drf_blog_api'
7
+
8
+ def ready(self):
9
+ pass
@@ -0,0 +1,39 @@
1
+ from django import forms
2
+ from django.contrib.auth.forms import ReadOnlyPasswordHashField
3
+
4
+ from .models import UserAccount
5
+
6
+
7
+ class UserCreationForm(forms.ModelForm):
8
+ password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
9
+ password2 = forms.CharField(
10
+ label='Password confirmation', widget=forms.PasswordInput)
11
+
12
+ class Meta:
13
+ model = UserAccount
14
+ fields = ("first_name", "last_name", "phone_number", "email")
15
+
16
+ def clean_password2(self):
17
+ password1 = self.cleaned_data.get("password1")
18
+ password2 = self.cleaned_data.get("password2")
19
+ if password1 and password2 and password1 != password2:
20
+ raise forms.ValidationError("Passwords don't match")
21
+ return password2
22
+
23
+ def save(self, commit=True):
24
+ user = super().save(commit=False)
25
+ user.set_password(self.cleaned_data["password1"])
26
+ if commit:
27
+ user.save()
28
+ return user
29
+
30
+
31
+ class UserChangeForm(forms.ModelForm):
32
+ password = ReadOnlyPasswordHashField()
33
+
34
+ class Meta:
35
+ model = UserAccount
36
+ fields = '__all__'
37
+
38
+ def clean_password(self):
39
+ return self.initial["password"]
@@ -0,0 +1,147 @@
1
+ from django.db import models
2
+ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
3
+ from django.core.validators import RegexValidator
4
+ from django.conf import settings
5
+ from cloudinary.models import CloudinaryField
6
+ from django.contrib.contenttypes.fields import GenericForeignKey
7
+ from django.contrib.contenttypes.models import ContentType
8
+
9
+
10
+ class UserAccountManager(BaseUserManager):
11
+ use_in_migrations = True
12
+
13
+ def _create_user(self, email, password, **extra_fields):
14
+ """
15
+ Creates and saves a User with the given email and password.
16
+ """
17
+ if not email:
18
+ raise ValueError('Users must have an email address')
19
+
20
+ email = self.normalize_email(email)
21
+ user = self.model(email=email, **extra_fields)
22
+ user.set_password(password)
23
+ user.save(using=self._db)
24
+ return user
25
+
26
+ def create_user(self, email, password=None, **extra_fields):
27
+ return self._create_user(email, password, **extra_fields)
28
+
29
+ def create_superuser(self, email, password, **extra_fields):
30
+
31
+ extra_fields.setdefault('is_superuser', True)
32
+ extra_fields.setdefault('is_staff', True)
33
+
34
+ if extra_fields.get('is_superuser') is not True:
35
+ raise ValueError('Superuser must have is_superuser=True.')
36
+
37
+ return self._create_user(email, password, **extra_fields)
38
+
39
+
40
+ class UserAccount(AbstractBaseUser, PermissionsMixin):
41
+ GENDER = [
42
+ ('Male', 'Male'),
43
+ ('Female', 'Female')
44
+ ]
45
+
46
+ USER_TYPE = [
47
+ ('Author', 'Author'),
48
+ ('Public', 'Public')
49
+ ]
50
+
51
+ email = models.EmailField(max_length=255, unique=True)
52
+ first_name = models.CharField(max_length=255)
53
+ last_name = models.CharField(max_length=255)
54
+ account_type = models.CharField(
55
+ max_length=255, blank=True, choices=USER_TYPE, default='Public')
56
+ phone_number = models.CharField(null=True, blank=True, max_length=11, validators=[
57
+ RegexValidator(r'^\d{11}$', 'Enter a valid phone number.')])
58
+ date_of_birth = models.DateField(null=True, blank=True)
59
+ is_active = models.BooleanField(default=True)
60
+ is_staff = models.BooleanField(default=False)
61
+ is_superuser = models.BooleanField(default=False)
62
+ date_joined = models.DateTimeField(null=True, blank=True)
63
+ image = CloudinaryField('image', null=True, blank=True)
64
+ gender = models.CharField(
65
+ max_length=255, null=True, blank=True, choices=GENDER)
66
+ home_address = models.TextField(null=True, blank=True)
67
+ local_govt = models.CharField(max_length=255, null=True, blank=True)
68
+ state_of_origin = models.CharField(max_length=255, null=True, blank=True)
69
+ nationality = models.CharField(max_length=255, null=True, blank=True)
70
+
71
+ objects = UserAccountManager()
72
+
73
+ USERNAME_FIELD = 'email'
74
+ REQUIRED_FIELDS = ['first_name', 'last_name', 'phone_number']
75
+ ordering = ('email',)
76
+
77
+ def get_full_name(self):
78
+ return f"{self.first_name} {self.last_name}"
79
+
80
+ def get_short_name(self):
81
+ return self.first_name
82
+
83
+ def get_image_url(self):
84
+ return (f"https://res.cloudinary.com/dkcjpdk1c/image/upload/{self.image}")
85
+
86
+ @property
87
+ def get_photo_url(self):
88
+ if self.image and hasattr(self.image, 'url'):
89
+ return self.image.url
90
+ else:
91
+ return "https://cdn-icons-png.flaticon.com/512/147/147142.png"
92
+
93
+ def __str__(self):
94
+ return self.email
95
+
96
+
97
+ class Category(models.Model):
98
+ name = models.CharField(max_length=50)
99
+
100
+
101
+ class Tag(models.Model):
102
+ name = models.CharField(max_length=50)
103
+
104
+
105
+ class Keyword(models.Model):
106
+ name = models.CharField(max_length=50)
107
+
108
+
109
+ class Post(models.Model):
110
+ title = models.CharField(max_length=200)
111
+ content = models.TextField()
112
+ pub_date = models.DateTimeField(auto_now_add=True)
113
+ author = models.ForeignKey(
114
+ settings.AUTH_USER_MODEL, related_name='post_author', on_delete=models.CASCADE)
115
+ category = models.ForeignKey(
116
+ Category, related_name='post_category', on_delete=models.SET_NULL, null=True)
117
+ tags = models.ManyToManyField(Tag, related_name='post_tag')
118
+ keywords = models.ManyToManyField(Keyword, related_name='post_keyword')
119
+ slug = models.SlugField(max_length=250, blank=True, null=True)
120
+
121
+ def __str__(self):
122
+ return self.title
123
+
124
+
125
+ class Comment(models.Model):
126
+ post = models.ForeignKey(Post, on_delete=models.CASCADE)
127
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='comment_user',
128
+ on_delete=models.CASCADE)
129
+ comment = models.TextField()
130
+ parent = models.ForeignKey(
131
+ 'self', null=True, blank=True, related_name='replies', on_delete=models.CASCADE)
132
+ created_at = models.DateTimeField(auto_now_add=True)
133
+
134
+ def __str__(self):
135
+ return f"Comment by {self.user} on {self.post} at {self.created_at}"
136
+
137
+
138
+ class Like(models.Model):
139
+ user = models.ForeignKey(settings.AUTH_USER_MODEL,
140
+ on_delete=models.CASCADE)
141
+ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
142
+ object_id = models.PositiveIntegerField()
143
+ content_object = GenericForeignKey('content_type', 'object_id')
144
+ created_at = models.DateTimeField(auto_now_add=True)
145
+
146
+ def __str__(self):
147
+ return f"Like by {self.user} on {self.content_object} at {self.created_at}"
@@ -0,0 +1,76 @@
1
+ from rest_framework import serializers
2
+ from .models import *
3
+ from djoser.serializers import UserCreateSerializer, UserSerializer
4
+ from django.contrib.auth import get_user_model
5
+
6
+ user = get_user_model()
7
+
8
+
9
+ class UserCreateSerializer(UserCreateSerializer):
10
+ class Meta(UserCreateSerializer.Meta):
11
+ model = user
12
+ fields = ('id', 'first_name', 'last_name',
13
+ 'phone_number', 'email', 'password')
14
+
15
+
16
+ class UserInfoSerializer(UserSerializer):
17
+ class Meta(UserSerializer.Meta):
18
+ model = user
19
+ fields = ('id', 'first_name', 'last_name', 'phone_number', 'email', 'get_photo_url', 'date_of_birth',
20
+ 'gender', 'home_address', 'local_govt', 'state_of_origin', 'nationality', 'image',)
21
+
22
+
23
+ class CategorySerializer(serializers.ModelSerializer):
24
+ class Meta:
25
+ model = Category
26
+ fields = "__all__"
27
+
28
+
29
+ class TagSerializer(serializers.ModelSerializer):
30
+ class Meta:
31
+ model = Tag
32
+ fields = "__all__"
33
+
34
+
35
+ class KeywordSerializer(serializers.ModelSerializer):
36
+ class Meta:
37
+ model = Keyword
38
+ fields = "__all__"
39
+
40
+
41
+ class CommentSerializer(serializers.ModelSerializer):
42
+ user = UserInfoSerializer()
43
+ replies = serializers.SerializerMethodField()
44
+
45
+ class Meta:
46
+ model = Comment
47
+ fields = ('id', 'user', 'comment', 'created_at', 'replies')
48
+
49
+ def get_replies(self, obj):
50
+ replies = obj.replies.all()
51
+ return CommentSerializer(replies, many=True).data
52
+
53
+
54
+ class PostSerializer(serializers.ModelSerializer):
55
+ category = CategorySerializer()
56
+ comments = CommentSerializer()
57
+ tags = TagSerializer(many=True)
58
+ Keywords = KeywordSerializer(many=True)
59
+ author = UserInfoSerializer()
60
+
61
+ class Meta:
62
+ model = Post
63
+ fields = ('id', 'content', 'pub_date', 'slug',
64
+ 'category', 'tags', 'keywords', 'comments', 'author')
65
+
66
+
67
+ class LikeSerializer(serializers.ModelSerializer):
68
+ user = UserInfoSerializer()
69
+ content_object = serializers.SerializerMethodField()
70
+
71
+ class Meta:
72
+ model = Like
73
+ fields = ('id', 'user', 'content_object', 'created_at')
74
+
75
+ def get_content_object(self, obj):
76
+ return str(obj.content_object)
@@ -0,0 +1,11 @@
1
+ from django.db.models.signals import pre_save
2
+ from .utils import unique_slug_generator
3
+ from .models import *
4
+
5
+
6
+ def slug_generator(sender, instance, *args, **kwargs):
7
+ if not instance.slug:
8
+ instance.slug = unique_slug_generator(instance)
9
+
10
+
11
+ pre_save.connect(slug_generator, sender=Post)
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,11 @@
1
+ from django.urls import path
2
+ from .views import *
3
+
4
+ urlpatterns = [
5
+ path('category', CategoryView.as_view()),
6
+ path('post-list', PostListView.as_view()), # Public users
7
+ path('post-list/<slug:slug>', PostDetailView.as_view()), # Public users
8
+ path('post', PostView.as_view()), # Authors
9
+ path('comment', CommentView.as_view()),
10
+ path('like', LikeView.as_view()),
11
+ ]
@@ -0,0 +1,32 @@
1
+ import random
2
+ import string
3
+
4
+ from django.utils.text import slugify
5
+
6
+
7
+ def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
8
+ return ''.join(random.choice(chars) for _ in range(size))
9
+
10
+
11
+ def unique_slug_generator(instance, new_slug=None):
12
+ """
13
+ This is for a Django project and it assumes your instance
14
+ has a model with a slug field and a title character (char) field.
15
+ """
16
+ if new_slug is not None:
17
+ slug = new_slug
18
+ else:
19
+ try:
20
+ slug = slugify(instance.title)
21
+ except AttributeError:
22
+ slug = slugify(instance.name)
23
+
24
+ Klass = instance.__class__
25
+ qs_exists = Klass.objects.filter(slug=slug).exists()
26
+ if qs_exists:
27
+ new_slug = "{slug}-{randstr}".format(
28
+ slug=slug,
29
+ randstr=random_string_generator(size=4)
30
+ )
31
+ return unique_slug_generator(instance, new_slug=new_slug)
32
+ return slug
@@ -0,0 +1,157 @@
1
+ from django.shortcuts import render
2
+ from rest_framework import generics
3
+ from .models import *
4
+ from .serializer import *
5
+ from rest_framework.decorators import api_view
6
+ from rest_framework.response import Response
7
+ from rest_framework.authtoken.serializers import AuthTokenSerializer
8
+ from rest_framework.permissions import AllowAny, IsAuthenticated
9
+ from rest_framework_simplejwt.authentication import JWTAuthentication
10
+ from djoser.views import UserViewSet
11
+ from rest_framework.views import APIView
12
+ from rest_framework import status
13
+ from django.contrib.contenttypes.models import ContentType
14
+
15
+
16
+ class PostListView(generics.ListAPIView): # Public users
17
+ serializer_class = PostSerializer
18
+ queryset = Post.objects.all()
19
+ permission_classes = [AllowAny,]
20
+
21
+
22
+ class PostDetailView(generics.RetrieveAPIView):
23
+ lookup_field = 'slug'
24
+ queryset = Post.objects.all()
25
+ serializer_class = PostSerializer
26
+ permission_classes = [AllowAny,]
27
+
28
+
29
+ class CategoryView(generics.ListAPIView):
30
+ queryset = Category.objects.all()
31
+ serializer_class = CategorySerializer
32
+ permission_classes = [AllowAny,]
33
+
34
+
35
+ class PostView(APIView): # Authors
36
+ permission_classes = [IsAuthenticated,]
37
+
38
+ def get(self, request, *args, **kwargs):
39
+ posts = Post.objects.filter(user=self.request.user)
40
+ serializer = PostSerializer(posts, many=True)
41
+ return Response(serializer.data)
42
+
43
+ def put(self, request, *args, **kwargs):
44
+ post_id = request.data.get('id')
45
+
46
+ try:
47
+ post = Post.objects.get(
48
+ id=post_id, user=self.request.user)
49
+ except Post.DoesNotExist:
50
+ return Response({'error': 'Post not found.'}, status=404)
51
+
52
+ serializer = PostSerializer(post, data=request.data)
53
+ if serializer.is_valid():
54
+ serializer.save()
55
+ return Response(serializer.data, status=200)
56
+
57
+ return Response(serializer.errors, status=400)
58
+
59
+ def post(self, request, *args, **kwargs):
60
+ title = request.data.get('title')
61
+ content = request.data.get('content')
62
+ category = request.data.get('category')
63
+ tags = request.data.get('tags')
64
+ keywords = request.data.get('keywords')
65
+
66
+ if not title or not content or not category:
67
+ return Response({'error': 'Title, Content and Category are required fields.'}, status=status.HTTP_400_BAD_REQUEST)
68
+
69
+ try:
70
+ post = Post.objects.create(
71
+ user=request.user, title=title, content=content, category=category, tags=tags, keywords=keywords)
72
+
73
+ serializer = PostSerializer(post)
74
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
75
+ except Exception as e:
76
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
77
+
78
+ def delete(self, request, *args, **kwargs):
79
+ post_id = request.data.get('id')
80
+ try:
81
+ post = Post.objects.get(id=post_id)
82
+ post.delete()
83
+ return Response(status=status.HTTP_204_NO_CONTENT)
84
+ except Post.DoesNotExist:
85
+ return Response({'error': 'Post not found'}, status=status.HTTP_404_NOT_FOUND)
86
+
87
+
88
+ class CommentView(APIView):
89
+ permission_classes = [IsAuthenticated,]
90
+
91
+ def get(self, request, *args, **kwargs):
92
+ # Get the post_id from query parameters
93
+ post_id = request.GET.get('post_id')
94
+ if post_id is None:
95
+ return Response({"error": "Missing 'post_id' query parameter."}, status=400)
96
+
97
+ comments = Comment.objects.filter(post_id=post_id)
98
+ serializer = PostSerializer(comments, many=True)
99
+ return Response(serializer.data)
100
+
101
+ def put(self, request, *args, **kwargs):
102
+ pass
103
+
104
+ def post(self, request, *args, **kwargs):
105
+ comment = request.data.get('comment')
106
+ post_id = request.data.get('post')
107
+ parent_comment_id = request.data.get('parent_comment')
108
+
109
+ if not comment or not parent_comment_id or not post_id:
110
+ return Response({'error': 'Comment and Post_ID are required fields.'}, status=status.HTTP_400_BAD_REQUEST)
111
+
112
+ try:
113
+ post = Post.objects.get(id=post_id)
114
+
115
+ if parent_comment_id:
116
+ parent_comment = Comment.objects.get(id=parent_comment_id)
117
+ comment = Comment.objects.create(
118
+ user=request.user, comment=comment, post=post, parent=parent_comment)
119
+ else:
120
+ comment = Comment.objects.create(
121
+ user=request.user, comment=comment, post=post)
122
+
123
+ serializer = CommentSerializer(comment)
124
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
125
+ except Exception as e:
126
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
127
+
128
+ def delete(self, request, *args, **kwargs):
129
+ pass
130
+
131
+
132
+ class LikeView(APIView):
133
+ permission_classes = [IsAuthenticated]
134
+
135
+ def post(self, request, *args, **kwargs):
136
+ content_type = request.data.get('content_type')
137
+ object_id = request.data.get('object_id')
138
+
139
+ if not content_type or not object_id:
140
+ return Response({'error': 'content_type and object_id are required fields.'}, status=status.HTTP_400_BAD_REQUEST)
141
+
142
+ try:
143
+ content_type = ContentType.objects.get(model=content_type)
144
+ except ContentType.DoesNotExist:
145
+ return Response({'error': 'Invalid content_type.'}, status=status.HTTP_400_BAD_REQUEST)
146
+
147
+ like, created = Like.objects.get_or_create(
148
+ user=request.user,
149
+ content_type=content_type,
150
+ object_id=object_id
151
+ )
152
+
153
+ if created:
154
+ return Response({'message': 'Liked successfully.'}, status=status.HTTP_201_CREATED)
155
+ else:
156
+ like.delete()
157
+ return Response({'message': 'Unliked successfully.'}, status=status.HTTP_200_OK)
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.1
2
+ Name: django-drf-blog-api
3
+ Version: 0.0.1
4
+ Summary: A Django app blog API endpoints.
5
+ Home-page: https://www.example.com/
6
+ Author: Osiota Samuel Obrozie
7
+ Author-email: osiotaobrozie@gmail.com
8
+ License: BSD-3-Clause # Example license
9
+ Platform: UNKNOWN
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Framework :: Django
12
+ Classifier: Framework :: Django :: 5.0
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+
28
+
29
+ # django-drf-blog-api
30
+
31
+
32
+ django-drf-blog-api is a Django blog API app
33
+
34
+
35
+
36
+ ## Quick start
37
+
38
+
39
+ 1. Add "Blog" to your INSTALLED_APPS setting like this::
40
+
41
+ INSTALLED_APPS = [
42
+ ...,
43
+ "django-drf-blog-api",
44
+ ]
45
+ ```
46
+ 2. Include the polls URLconf in your project urls.py like this::
47
+
48
+ path("blog/", include("django-drf-blog-api.urls")),
49
+
50
+ 3. Run ``python manage.py migrate`` to create the models.
51
+
52
+ 4. Start the development server and visit the admin to create a poll.
53
+
54
+ 5. Visit the ``/blog/`` URL to participate in the poll.
55
+
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.rst
4
+ pyproject.toml
5
+ setup.cfg
6
+ setup.py
7
+ django_drf_blog_api/__init__.py
8
+ django_drf_blog_api/admin.py
9
+ django_drf_blog_api/apps.py
10
+ django_drf_blog_api/forms.py
11
+ django_drf_blog_api/models.py
12
+ django_drf_blog_api/serializer.py
13
+ django_drf_blog_api/signals.py
14
+ django_drf_blog_api/tests.py
15
+ django_drf_blog_api/urls.py
16
+ django_drf_blog_api/utils.py
17
+ django_drf_blog_api/views.py
18
+ django_drf_blog_api.egg-info/PKG-INFO
19
+ django_drf_blog_api.egg-info/SOURCES.txt
20
+ django_drf_blog_api.egg-info/dependency_links.txt
21
+ django_drf_blog_api.egg-info/requires.txt
22
+ django_drf_blog_api.egg-info/top_level.txt
23
+ django_drf_blog_api/migrations/__init__.py
@@ -0,0 +1 @@
1
+ django_drf_blog_api
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ['setuptools>=40.8.0']
3
+ build-backend = 'setuptools.build_meta'
@@ -0,0 +1,36 @@
1
+ [metadata]
2
+ name = django-drf-blog-api
3
+ version = 0.0.1
4
+ description = A Django app blog API endpoints.
5
+ long_description = file: README.rst
6
+ url = https://www.example.com/
7
+ author = Osiota Samuel Obrozie
8
+ author_email = osiotaobrozie@gmail.com
9
+ license = BSD-3-Clause # Example license
10
+ classifiers =
11
+ Environment :: Web Environment
12
+ Framework :: Django
13
+ Framework :: Django :: 5.0
14
+ Intended Audience :: Developers
15
+ License :: OSI Approved :: BSD License
16
+ Operating System :: OS Independent
17
+ Programming Language :: Python
18
+ Programming Language :: Python :: 3
19
+ Programming Language :: Python :: 3 :: Only
20
+ Programming Language :: Python :: 3.10
21
+ Programming Language :: Python :: 3.11
22
+ Programming Language :: Python :: 3.12
23
+ Topic :: Internet :: WWW/HTTP
24
+ Topic :: Internet :: WWW/HTTP :: Dynamic Content
25
+
26
+ [options]
27
+ include_package_data = true
28
+ packages = find:
29
+ python_requires = >=3.10
30
+ install_requires =
31
+ Django >= 5.0
32
+
33
+ [egg_info]
34
+ tag_build =
35
+ tag_date = 0
36
+
@@ -0,0 +1,13 @@
1
+ from setuptools import setup
2
+
3
+ # read the contents of your README file
4
+ from pathlib import Path
5
+ this_directory = Path(__file__).parent
6
+ long_description = (this_directory / "README.rst").read_text()
7
+
8
+ setup(
9
+ name='django-drf-blog-api',
10
+ # other arguments omitted
11
+ long_description=long_description,
12
+ long_description_content_type='text/markdown'
13
+ )