Book-Center
Database tablolarımızı oluşturalım
- in models.py
from django.db import models
# Create your models here.
class Book(models.Model):
name = models.CharField(max_length = 250)
author = models.CharField(max_length = 250)
description = models.TextField(blank = True, null = True)
published_date = models.DateField()
created_date = models.DateTimeField(auto_now_add = True)
updated_date = models.DateTimeField(auto_now = True)
def __str__(self):
return f" {self.name} - {self.author} "
PositiveIntegerField() için validators kullanıyoruz. Validators için
MinValueValidator
veMaxValueValidator
validatorlarını import ediyoruz.
from django.contrib.auth.models import User
from django.core.validators import(
MinValueValidator,
MaxValueValidator
) # for rating fields
class Comment(models.Model):
book = models.ForeignKey(Book, on_delete = models.CASCADE, related_name = "comments")
user = models.ForeignKey(User, on_delete = models.CASCADE)
comment = models.TextField()
rating = models.PositiveIntegerField(
validators = [MinValueValidator(1), MaxValueValidator(5)],
)
created_date = models.DateTimeField(auto_now_add = True)
updated_date = models.DateTimeField(auto_now = True)
def __str__(self):
return f" {self.rating}"
GenericAPI Views and Mixins
CRUD işlemlerlerinde benzer kodları tekrar tekrar yazmanın önüne geçmek için Django'nun bize sunduğu birtakım metodları kullanabiliriz.
Django_Rest_Framework, APIView class'ından türetilen ve APIView class'ının kabiliyetlerine yenilerini de ekleyen GenericAPIView class'ına ve bazı mixinslere sahip. (GenericAPIView, APIView'ın instance'ı)
Attributes of GenericAPIView
The following attributes control the basic view behavior.
-
queryset
- The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override theget_queryset()
method. If you are overriding a view method, it is important that you call get_queryset() instead of accessing this property directly, as queryset will get evaluated once, and those results will be cached for all subsequent requests. -
serializer_class
- The serializer class that should be used for validating and deserializing input, and for serializing output. Typically, you must either set this attribute, or override theget_serializer_class()
method. -
lookup_field - The model field that should be used for performing object lookup of individual model instances. Defaults to 'pk'. Note that when using hyperlinked APIs you'll need to ensure that both the API views and the serializer classes set the lookup fields if you need to use a custom value. lookup_url_kwarg - The URL keyword argument that should be used for object lookup. The URL conf should include a keyword argument corresponding to this value. If unset this defaults to using the same value as lookup_field.
GenericAPIView'a birtakım yetenekler katmak için Mixins methodlarını kullanabiliriz.
- ListModelMixin
- CreateModelMixin
- RetrieveModelMixin
- UpdateModelMixin
- DestroyModelMixin
- in views.py
from rest_framework.generics import GenericAPIView
from ..models import Book, Comment
from .serializers import CommentSerializer, BookSerializer
from rest_framework.mixins import ListModelMixin, CreateModelMixin
"""
we import `ListModelMixin` from mixins to get list of all books
we import `CreateModelMixin` from mixins to create a new Book instance
"""
class BookListCreateAPIView(ListModelMixin, CreateModelMixin, GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# to list all Book instances
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
# to create a new Book instance
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Concrete Views
Bir concrete view, GenericAPIView ve ilgili mixinlerin birleşmesinden oluşur.
ex.) RetrieveUpdateAPIView, GenericAPIView ile (RetrieveModelMixin ve UpdateModelMixin'in) birlikte kullanılmasıyla elde edilir.
- List ve create işlemleri yapabilen bir concreteView örneği:
from ..models import Book, Comment
from .serializers import CommentSerializer, BookSerializer
from rest_framework import generics
class BookListCreateAPIView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
Concrete Views sayesinde primary_key belirtmeden id özelinde retrieve/update/delete işlemleri yapabiliriz.
class BookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
- urls.py file'ına da detay sayfası için path eklemeliyiz.
urlpatterns = [
path("books/", BookListCreateAPIView.as_view(), name = "book-list"),
path("books/<int:pk>",BookDetailAPIView.as_view(), name = "book-detail"),
]
overwrite the methods of concreteView
ConcreteView'lar birçok işlemi arka planda kendisi hallettiğinden bazı durumlarda concreteview'ın kullandığı methodları override edebiliriz.
ex.) bir comment oluştururken model'imizde belirttiğimiz üzere comment bir book ile ilişkili olmalı
# in models.py/Comment
book = models.ForeignKey(Book, on_delete = models.CASCADE, related_name = "comments")
- Comment oluştururken, book ile comment nasıl eşleşecek? (yüzlerce kitap var)
url'de endpoint'in içerisinde ilgili kitabın id'sini eklemeliyiz ve endpointimiz şu şekilde çalışmalı books/<int:book_pk
>/comment/
Bunun için CreateAPIView'ın parent'larından CreateModelMixin'in içerisindeki
perform_create()
methodunu override ediyoruz.
perform_create()
methodunda serializer.save() komutu çalışıyor ve serializer kaydedilmeden önce biz hangi kitaba yorum yapıldıysa onu önce bulmalıyız ve sonra serializer save edilirken bulduğumuz kitabı kaydedilen object'e eklemeliyiz.
- in views.py
class CommentCreateAPIView(generics.CreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def perform_create(self, serializer):
book_pk = self.kwargs.get("book_pk")
# kitabımızın primary key parametresini `self.kwargs.get()` sayesinde çektik
book = get_object_or_404(Book, pk = book_pk)
serializer.save(book = book)
- in urls.py
path("books/<int:book_pk>/comment", CommentCreateAPIView.as_view(), name = "comment"),
Permissions
Global Permissions
The default permission policy may be set globally, using the DEFAULT_PERMISSION_CLASSES
setting.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
IsAuthenticated -> Sadece login olan kullanıcılara izin vermek istediğimizde kullanacağımız method
AllowAny -> Default'u AllowAny'dir. AllowAny, herkese crud işlemleri için izin veren methodtur.
IsAdminUser
IsAuthenticatedOrReadOnly
doc: https://www.django-rest-framework.org/api-guide/permissions/
Local Permissions
- in permissions.py
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class BasePermission(metaclass=BasePermissionMetaclass):
"""
A base class from which all permission classes should inherit.
"""
def has_permission(self, request, view):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
def has_object_permission(self, request, view, obj):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
class AllowAny(BasePermission):
"""
Allow any access.
This isn't strictly required, since you could use an empty
permission_classes list, but it's useful because it makes the intention
more explicit.
"""
def has_permission(self, request, view):
return True
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
class IsAdminUser(BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_staff)
class IsAuthenticatedOrReadOnly(BasePermission):
"""
The request is authenticated as a user, or is a read-only request.
"""
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_authenticated
)
# permissions'lar boolean değer döndürür --> True or False
Rest_framework'ün url'ini kullanarak browesableAPI üzerinden login/logout olabiliriz.
# in main/urls.py
path("api-auth/",include("rest_framework.urls")),
- in views.py
from rest_framework import permissions
class BookListCreateAPIView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [permissions.IsAuthenticated] # local permission
Custom Permission
- in app/permissions.py
from rest_framework import permissions
class IsAdminUserOrReadOnly(permissions.IsAdminUser):
def has_permission(self, request, view):
is_admin = super().has_permission(request, view) # super() stands for IsAdminUser
return bool(request.method in permissions.SAFE_METHODS or is_admin)
- in views.py
from .permissions import IsAdminUserOrReadOnly # custom permission
class BookListCreateAPIView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAdminUserOrReadOnly] # custom permission
class BookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAdminUserOrReadOnly] # custom permission
Yorum yapan kullanıcıların sadece kendi yorumları üzerinde update ve delete işlemlerinin olması için bir permission yazalım ve 1 user 1 kitaba sadece 1 yorum yazabilsin istiyoruz.
from rest_framework.exceptions import ValidationError # to warn the user that can't comment second times
class CommentCreateAPIView(generics.CreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
book_pk = self.kwargs.get("book_pk")
# kitabımızın primary key parametresini `self.kwargs.get()` sayesinde çektik
book = get_object_or_404(Book, pk = book_pk)
user = self.request.user
# 1 user 1 kitaba sadece 1 yorum yapabilir
comments = Comment.objects.filter(book = book, user = user)
if(comments.exists()):
raise ValidationError("You can comment just one time for the same book")
serializer.save(book = book, user = user)
User sadece kendi yorumları üzerinde update, delete yetkilerine sahip olacak. Bunun için bizim önceki yorumlara erişmemiz ve yorumları yapan user ile login olmuş user'ı eşleştirmemiz lazım.
- in env/permission.py
class BasePermission(metaclass=BasePermissionMetaclass):
"""
A base class from which all permission classes should inherit.
"""
def has_permission(self, request, view):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
def has_object_permission(self, request, view, obj):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
# BasePermission'ın methodlarından has_object_permission() methodunda objectimize erişebiliyoruz.
BasePermission'dan inherit ederek oluşturacağımız custom permission'da has_object_permission() methodunu kullanarak obj.user'ımıza ulaşabiliriz ve kullanıcı durumuna göre permission'ımız True ya da False döner
- in app/permissions.py
class IsUserOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if(request.method in permissions.SAFE_METHODS):
return True
return request.user == obj.user
- in app/views.py
class CommentDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [IsUserOrReadOnly]