Changed work of shelf books

This commit is contained in:
maciejrusek
2026-03-22 14:19:17 +01:00
parent 5722f0b7ea
commit aeab43aa8a
23 changed files with 307 additions and 140 deletions

View 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)),
],
),
]

View 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)),
],
),
]

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class ShelfBooksConfig(AppConfig):
name = 'apps.shelf_books'

View 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')],
},
),
]

View File

@@ -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',
),
]

View File

@@ -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")

View 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)

View 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())
]

View 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

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class ShelfItemsConfig(AppConfig):
name = 'apps.shelf_items'

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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'),

View File

@@ -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/