Changed work of shelf books
This commit is contained in:
25
backend/apps/authors/migrations/0001_initial.py
Normal file
25
backend/apps/authors/migrations/0001_initial.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-22 12:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Author',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='authors', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
28
backend/apps/books/migrations/0001_initial.py
Normal file
28
backend/apps/books/migrations/0001_initial.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-22 12:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('authors', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Book',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('isbn', models.CharField(max_length=13)),
|
||||
('authors', models.ManyToManyField(related_name='books', to='authors.author')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='books', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,6 +1,5 @@
|
||||
from django.db import models
|
||||
|
||||
from apps.shelves.models import Shelf
|
||||
from apps.authors.models import Author
|
||||
from apps.user.models import User
|
||||
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.shelves.serializers import ShelfSerializer
|
||||
from apps.authors.serializers import AuthorSerializer
|
||||
from apps.books.models import Book
|
||||
from apps.shelves.models import Shelf
|
||||
from apps.authors.models import Author
|
||||
|
||||
class BookSerializer(serializers.ModelSerializer):
|
||||
shelf = ShelfSerializer(read_only=True)
|
||||
shelf_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Shelf.objects.all(),
|
||||
source="shelf",
|
||||
write_only=True
|
||||
)
|
||||
|
||||
authors = AuthorSerializer(many=True, read_only=True)
|
||||
authors_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Author.objects.all(),
|
||||
source="authors",
|
||||
many=True,
|
||||
allow_null=True,
|
||||
write_only=True
|
||||
write_only=True,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -28,27 +19,23 @@ class BookSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"isbn",
|
||||
"shelf",
|
||||
"shelf_id",
|
||||
"isbn",
|
||||
"authors",
|
||||
"authors_id"
|
||||
]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=..., **kwargs):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
request = self.context.get("request")
|
||||
if request and request.user:
|
||||
self.fields["shelf_id"].queryset = Shelf.objects.filter(
|
||||
user=request.user
|
||||
)
|
||||
|
||||
if request and request.user and request.user.is_authenticated:
|
||||
self.fields["authors_id"].queryset = Author.objects.filter(
|
||||
user=request.user
|
||||
)
|
||||
else:
|
||||
self.fields["authors_id"].queryset = Author.objects.none()
|
||||
|
||||
|
||||
def create(self, validated_data):
|
||||
|
||||
@@ -14,6 +14,5 @@ class BooksViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(
|
||||
shelf__user_id=self.request.user.pk
|
||||
).prefetch_related("authors")
|
||||
return self.queryset.filter(user=self.request.user)
|
||||
|
||||
|
||||
5
backend/apps/shelf_books/apps.py
Normal file
5
backend/apps/shelf_books/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ShelfBooksConfig(AppConfig):
|
||||
name = 'apps.shelf_books'
|
||||
29
backend/apps/shelf_books/migrations/0001_initial.py
Normal file
29
backend/apps/shelf_books/migrations/0001_initial.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-22 12:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('books', '0001_initial'),
|
||||
('shelves', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ShelfItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.PositiveIntegerField(default=0)),
|
||||
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shelf_items', to='books.book')),
|
||||
('shelf', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='book_items', to='shelves.shelf')),
|
||||
],
|
||||
options={
|
||||
'constraints': [models.UniqueConstraint(fields=('book', 'shelf'), name='unique_book_per_shelf')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-22 12:53
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('books', '0001_initial'),
|
||||
('shelf_books', '0001_initial'),
|
||||
('shelves', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='ShelfItem',
|
||||
new_name='ShelfBook',
|
||||
),
|
||||
]
|
||||
@@ -4,7 +4,7 @@ from django.db import models
|
||||
from apps.books.models import Book
|
||||
from apps.shelves.models import Shelf
|
||||
|
||||
class ShelfItem(models.Model):
|
||||
class ShelfBook(models.Model):
|
||||
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="shelf_items")
|
||||
shelf = models.ForeignKey(Shelf, on_delete=models.CASCADE, related_name="book_items")
|
||||
|
||||
84
backend/apps/shelf_books/serializers.py
Normal file
84
backend/apps/shelf_books/serializers.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.shelf_books.models import ShelfBook
|
||||
from apps.books.models import Book
|
||||
from apps.shelves.models import Shelf
|
||||
from apps.shelves.serializers import ShelfSerializer
|
||||
from apps.books.serializers import BookSerializer
|
||||
|
||||
class ShelfBookSerializer(serializers.ModelSerializer):
|
||||
shelf = ShelfSerializer(read_only=True)
|
||||
|
||||
book = BookSerializer(read_only=True)
|
||||
book_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Book.objects.all(),
|
||||
source="book",
|
||||
write_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ShelfBook
|
||||
fields = [
|
||||
"id",
|
||||
"shelf",
|
||||
"book",
|
||||
"book_id",
|
||||
"quantity"
|
||||
]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
request = self.context.get("request")
|
||||
if request and request.user and request.user.is_authenticated:
|
||||
self.fields["book_id"].queryset = Book.objects.filter(
|
||||
user=request.user
|
||||
)
|
||||
else:
|
||||
self.fields["book_id"].queryset = Book.objects.none()
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
|
||||
book = attrs.get("book")
|
||||
shelf = self.context.get("shelf")
|
||||
|
||||
if not book:
|
||||
book = self.instance.book
|
||||
|
||||
if not shelf:
|
||||
return attrs
|
||||
|
||||
qs = ShelfBook.objects.filter(
|
||||
book=book,
|
||||
shelf=shelf
|
||||
)
|
||||
|
||||
if self.instance:
|
||||
qs = qs.exclude(pk=self.instance.pk)
|
||||
|
||||
if qs.exists():
|
||||
raise serializers.ValidationError(
|
||||
"This Book already exists in shelf!"
|
||||
)
|
||||
|
||||
if book.user_id != shelf.user_id:
|
||||
raise serializers.ValidationError(
|
||||
"Book and Shelf must be from the same user!"
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
shelf = self.context["shelf"]
|
||||
validated_data["shelf"] = shelf
|
||||
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
shelf = self.context["shelf"]
|
||||
validated_data["shelf"] = shelf
|
||||
|
||||
return super().update(instance, validated_data)
|
||||
10
backend/apps/shelf_books/urls.py
Normal file
10
backend/apps/shelf_books/urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from apps.shelf_books.views import ShelfBooks, SelectedShelfBook
|
||||
|
||||
urlpatterns = [
|
||||
path("shelves/<int:shelf_id>/books/", ShelfBooks.as_view()),
|
||||
path("shelves/<int:shelf_id>/books/<int:book_id>/", SelectedShelfBook.as_view())
|
||||
]
|
||||
|
||||
|
||||
84
backend/apps/shelf_books/views.py
Normal file
84
backend/apps/shelf_books/views.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from rest_framework import generics
|
||||
|
||||
from apps.shelf_books.models import ShelfBook
|
||||
from apps.shelves.models import Shelf
|
||||
from apps.shelf_books.serializers import ShelfBookSerializer
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
get=extend_schema(tags=["Shelf Books"]),
|
||||
post=extend_schema(tags=["Shelf Books"]),
|
||||
)
|
||||
class ShelfBooks(generics.ListCreateAPIView):
|
||||
queryset = ShelfBook.objects.all()
|
||||
serializer_class = ShelfBookSerializer
|
||||
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
shelf_id = self.kwargs["shelf_id"]
|
||||
|
||||
return ShelfBook.objects.filter(
|
||||
shelf__id=shelf_id,
|
||||
shelf__user_id=self.request.user.pk,
|
||||
).select_related("book", "shelf").prefetch_related("book__authors")
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
|
||||
shelf_id = self.kwargs["shelf_id"]
|
||||
|
||||
shelf = Shelf.objects.filter(
|
||||
id=shelf_id,
|
||||
user=self.request.user
|
||||
).first()
|
||||
|
||||
context["shelf"] = shelf
|
||||
return context
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
get=extend_schema(tags=["Shelf Books"]),
|
||||
put=extend_schema(tags=["Shelf Books"]),
|
||||
patch=extend_schema(tags=["Shelf Books"]),
|
||||
delete=extend_schema(tags=["Shelf Books"]),
|
||||
)
|
||||
class SelectedShelfBook(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = ShelfBook.objects.all()
|
||||
serializer_class = ShelfBookSerializer
|
||||
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
lookup_url_kwarg = "book_id"
|
||||
|
||||
def get_queryset(self):
|
||||
return ShelfBook.objects.filter(
|
||||
shelf_id=self.kwargs["shelf_id"],
|
||||
shelf__user=self.request.user,
|
||||
).select_related("book", "shelf").prefetch_related("book__authors")
|
||||
|
||||
def get_object(self):
|
||||
queryset = self.get_queryset()
|
||||
|
||||
return generics.get_object_or_404(
|
||||
queryset,
|
||||
book_id=self.kwargs["book_id"]
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
|
||||
shelf = Shelf.objects.filter(
|
||||
id=self.kwargs["shelf_id"],
|
||||
user=self.request.user
|
||||
).first()
|
||||
|
||||
context["shelf"] = shelf
|
||||
return context
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ShelfItemsConfig(AppConfig):
|
||||
name = 'apps.shelf_items'
|
||||
@@ -1,77 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.shelf_items.models import ShelfItem
|
||||
from apps.books.models import Book
|
||||
from apps.shelves.models import Shelf
|
||||
from apps.shelves.serializers import ShelfSerializer
|
||||
from apps.books.serializers import BookSerializer
|
||||
|
||||
class ShelfItemSerializer(serializers.ModelSerializer):
|
||||
shelf = ShelfSerializer(read_only=True)
|
||||
shelf_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Shelf.objects.all(),
|
||||
source="shelf",
|
||||
write_only=True
|
||||
)
|
||||
|
||||
book = BookSerializer(read_only=True)
|
||||
book_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Book.objects.all(),
|
||||
source="book",
|
||||
write_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ShelfItem
|
||||
fields = [
|
||||
"id",
|
||||
"shelf",
|
||||
"shelf_id",
|
||||
"book",
|
||||
"book_id",
|
||||
"quantity"
|
||||
]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def __init__(self, instance=None, data=..., **kwargs):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
|
||||
request = self.context.get("request")
|
||||
if request and request.user:
|
||||
self.fields["shelf_id"].queryset = Shelf.objects.filter(
|
||||
user=request.user
|
||||
)
|
||||
|
||||
self.fields["book_id"].queryset = Book.objects.filter(
|
||||
user=request.user
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
|
||||
book = attrs.get("book")
|
||||
shelf = attrs.get("shelf")
|
||||
|
||||
if book and shelf:
|
||||
qs = ShelfItem.objects.filter(
|
||||
book=book,
|
||||
shelf=shelf
|
||||
)
|
||||
|
||||
if self.instance:
|
||||
qs = qs.exclude(pk=self.instance.pk)
|
||||
|
||||
if qs.exists():
|
||||
raise serializers.ValidationError(
|
||||
"This Book already exists in shelf!"
|
||||
)
|
||||
|
||||
if book.user_id != shelf.user_id:
|
||||
raise serializers.ValidationError(
|
||||
"Book and Shelf must be from the same user!"
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from apps.shelf_items.views import ShelfItemsViewSet
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register("", viewset=ShelfItemsViewSet)
|
||||
urlpatterns += router.urls
|
||||
@@ -1,20 +0,0 @@
|
||||
from rest_framework import viewsets
|
||||
|
||||
from apps.shelf_items.models import ShelfItem
|
||||
from apps.shelf_items.serializers import ShelfItemSerializer
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
|
||||
|
||||
class ShelfItemsViewSet(viewsets.ModelViewSet):
|
||||
queryset = ShelfItem.objects.all()
|
||||
serializer_class = ShelfItemSerializer
|
||||
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
return ShelfItem.objects.filter(
|
||||
shelf__user_id=self.request.user.pk,
|
||||
).select_related("book", "shelf").prefetch_related("book__authors")
|
||||
@@ -6,6 +6,7 @@ urlpatterns = [
|
||||
path("shelves/", include("apps.shelves.urls")),
|
||||
path("books/", include("apps.books.urls")),
|
||||
path("authors/", include("apps.authors.urls")),
|
||||
path("", include("apps.shelf_books.urls")),
|
||||
|
||||
path('schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from config.env import BASE_DIR, env
|
||||
|
||||
@@ -21,6 +22,7 @@ INSTALLED_APPS = [
|
||||
"apps.shelves.apps.ShelvesConfig",
|
||||
"apps.books.apps.BooksConfig",
|
||||
"apps.authors.apps.AuthorsConfig",
|
||||
"apps.shelf_books.apps.ShelfBooksConfig",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
@@ -112,9 +114,18 @@ SPECTACULAR_SETTINGS = {
|
||||
'DESCRIPTION': 'Library project',
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
"COMPONENT_SPLIT_REQUEST": True,
|
||||
"SCHEMA_PATH_PREFIX": "/api/",
|
||||
}
|
||||
|
||||
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user