diff --git a/.gitignore b/.gitignore index d4b69cf..b0f50ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .project .pydevproject poetry.lock - +.env diff --git a/api/settings.py b/api/settings.py index 4bf2927..6dbf441 100644 --- a/api/settings.py +++ b/api/settings.py @@ -11,16 +11,18 @@ https://docs.djangoproject.com/en/5.2/ref/settings/ """ from pathlib import Path +import os +from dotenv import load_dotenv, dotenv_values # Build paths inside the project like this: BASE_DIR / 'subdir'. +load_dotenv() BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-g)a6wc^%c58nxv6x2&&o1tnh=dcpy)jd01nuc(x-6+23&0ay3y' +SECRET_KEY = os.getenv("DJANGO_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -37,7 +39,6 @@ CORS_ALLOWED_ORIGINS = [ # Application definition - INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -49,9 +50,10 @@ INSTALLED_APPS = [ 'rest_framework', 'corsheaders', 'rest_framework_simplejwt', - 'djoser', ] +AUTH_USER_MODEL = 'books.CustomUser' + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -98,11 +100,11 @@ WSGI_APPLICATION = 'api.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'pipi', - 'USER': 'pipi', - 'PASSWORD': 'pipi', - 'HOST': 'localhost', - 'PORT': '5434' + 'NAME': os.getenv("DB_NAME"), + 'USER': os.getenv("DB_USER"), + 'PASSWORD': os.getenv("DB_PASSWORD"), + 'HOST': os.getenv("DB_HOST"), + 'PORT': os.getenv("DB_PORT") } } diff --git a/api/urls.py b/api/urls.py index 8d7d739..6ba79eb 100644 --- a/api/urls.py +++ b/api/urls.py @@ -15,11 +15,17 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path, include, re_path +from django.urls import path +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + +from books import views +from books.views import UserProfileView, BookListCreateView urlpatterns = [ - re_path(r'^auth/', include('djoser.urls')), - re_path(r'^auth/', include('djoser.urls.jwt')), - path('', include('books.urls')), path('admin/', admin.site.urls), + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/profile', UserProfileView.as_view(), name='user_profile'), + path('api/books', BookListCreateView.as_view(), name='book_list_create'), + path('api/users/', views.find_user_by_id, name='user-detail') ] \ No newline at end of file diff --git a/books/admin.py b/books/admin.py index a49d6ae..64c5a2b 100644 --- a/books/admin.py +++ b/books/admin.py @@ -1,4 +1,6 @@ from django.contrib import admin -from books.models import Book +from books.models import Book, CustomUser + # Register your models here. -admin.site.register(Book) \ No newline at end of file +admin.site.register(Book) +admin.site.register(CustomUser) \ No newline at end of file diff --git a/books/migrations/0001_initial.py b/books/migrations/0001_initial.py deleted file mode 100644 index d6e1370..0000000 --- a/books/migrations/0001_initial.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-02 22:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Book', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255)), - ('author', models.CharField(max_length=255)), - ('state', models.CharField(choices=[('PLAN', 'Plan to Read'), ('READING', 'Reading'), ('COMPLETED', 'Completed'), ('DROPPED', 'Dropped')], db_index=True, default='PLAN', max_length=10, verbose_name='state')), - ('added_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ], - ), - ] diff --git a/books/migrations/0002_book_note.py b/books/migrations/0002_book_note.py deleted file mode 100644 index 962cc8c..0000000 --- a/books/migrations/0002_book_note.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-04 17:16 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('books', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='book', - name='note', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(10)], verbose_name='note'), - ), - ] diff --git a/books/migrations/0003_alter_book_note.py b/books/migrations/0003_alter_book_note.py deleted file mode 100644 index 52cb9d6..0000000 --- a/books/migrations/0003_alter_book_note.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-04 17:18 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('books', '0002_book_note'), - ] - - operations = [ - migrations.AlterField( - model_name='book', - name='note', - field=models.PositiveSmallIntegerField(blank=True, default=0, null=True, validators=[django.core.validators.MaxValueValidator(10)], verbose_name='note'), - ), - ] diff --git a/books/migrations/__init__.py b/books/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/books/models.py b/books/models.py index 13352ea..f68d19c 100644 --- a/books/models.py +++ b/books/models.py @@ -1,38 +1,51 @@ +from django.contrib.auth.models import AbstractUser from django.db import models +from django.conf import settings from django.utils.translation import gettext_lazy as _ from django.core.validators import MaxValueValidator -# Create your models here. class Book(models.Model): PLAN_TO_READ = 'PLAN' READING = 'READING' COMPLETED = 'COMPLETED' DROPPED = 'DROPPED' + STATE_CHOICES = [ (PLAN_TO_READ, _('Plan to Read')), (READING, _('Reading')), (COMPLETED, _('Completed')), (DROPPED, _('Dropped')), ] + + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name='books' + ) + note = models.PositiveSmallIntegerField( - _('note'), - validators=[ - MaxValueValidator(10) - ], - default=0, - null=True, - blank=True - ) + _('note'), + validators=[MaxValueValidator(10)], + default=0, + null=True, + blank=True + ) title = models.CharField(max_length=255) author = models.CharField(max_length=255) state = models.CharField( _('state'), max_length=10, - choices=STATE_CHOICES, - default=PLAN_TO_READ, + choices=STATE_CHOICES, + default=PLAN_TO_READ, db_index=True ) added_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - - \ No newline at end of file + + def __str__(self): + return f"{self.title} ({self.user.username})" + + +class CustomUser(AbstractUser): + def __str__(self): + return self.username \ No newline at end of file diff --git a/books/views.py b/books/views.py index 12000f4..eb53357 100644 --- a/books/views.py +++ b/books/views.py @@ -1,9 +1,10 @@ +from rest_framework.decorators import api_view from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated from rest_framework import status -from .models import Book +from .models import Book, CustomUser from .serializers import BookSerializer class BookListCreateView(APIView): @@ -17,7 +18,36 @@ class BookListCreateView(APIView): def post(self, request): serializer = BookSerializer(data=request.data) if serializer.is_valid(): - serializer.save() + serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def find_user_by_id(request, user_id): + try: + user = CustomUser.objects.get(id=user_id) + books_queryset = Book.objects.filter(user=user) + books_serializer = BookSerializer(books_queryset, many=True) + return Response({ + 'username': user.username, + 'email': user.email, + 'books': books_serializer.data, + }) + + except CustomUser.DoesNotExist: + return Response({"message": "Cet utilisateur n'existe pas"}, status=status.HTTP_404_NOT_FOUND) + +class UserProfileView(APIView): + permission_classes = [IsAuthenticated] + + def get(self,request): + user = request.user + books_queryset = Book.objects.filter(user=user) + books_serializer = BookSerializer(books_queryset, many=True) + return Response({ + 'username': user.username, + 'email': user.email, + 'books': books_serializer.data, + }) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 28fc483..6fe59c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: POSTGRES_PASSWORD: pipi POSTGRES_DB: pipi ports: - - "5434:5432" + - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data diff --git a/pyproject.toml b/pyproject.toml index 0ce217a..4f68894 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,9 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.13,<4.0" Django = "^5.2.7" -djoser = "^2.3.3" djangorestframework = ">=3.16.1" djangorestframework_simplejwt = "^5.5.1" django-cors-headers = "^4.9.0" psycopg = "^3.1" -gunicorn = "^22.0" \ No newline at end of file +gunicorn = "^22.0" +dotenv = "^0.9.9"