Merge pull request 'user-profile-support' (#15) from user-profile-support into main

Reviewed-on: #15
This commit is contained in:
Edward Tirado Jr 2025-07-08 05:42:31 +00:00
commit 80c4639d23
8 changed files with 137 additions and 32 deletions

View file

@ -1,7 +1,6 @@
from django.db import models
from django.contrib.auth.models import User
from django.db import models
from django.db.models import SET_NULL
import datetime
class Movie(models.Model):

View file

@ -10,14 +10,12 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
from pathlib import Path
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
@ -30,13 +28,13 @@ DEBUG = bool(os.environ.get("DEBUG", default=0))
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "127.0.0.1").split(",")
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"corsheaders",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
@ -44,7 +42,7 @@ INSTALLED_APPS = [
"rest_framework.authtoken",
"knox",
"movie_manager",
"corsheaders",
"users",
]
MIDDLEWARE = [
@ -78,7 +76,6 @@ TEMPLATES = [
WSGI_APPLICATION = "movienight.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
@ -95,7 +92,6 @@ DATABASES = {
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
@ -114,7 +110,6 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
@ -131,7 +126,6 @@ OMDB_API_KEY = os.environ.get("OMDB_API_KEY")
# Django Rest Framework
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
#'rest_framework.authentication.SessionAuthentication',
"knox.auth.TokenAuthentication",
],
}

View file

@ -2,14 +2,14 @@
URL configuration for movienight project.
"""
import knox
from knox import views as knox_views
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.routers import DefaultRouter
from movie_db import views as movie_db_views
from movie_manager.viewsets import (
MovieViewset,
MovieListViewset,
@ -17,11 +17,8 @@ from movie_manager.viewsets import (
ShowingViewset,
)
from users import views as user_views
from movie_db import views as movie_db_views
from rest_framework.authtoken.views import obtain_auth_token
from users.viewsets.user import register
from users.viewsets import UserViewSet, GroupViewSet
from users.viewsets.user import register, UserProfileViewSet
router = DefaultRouter()
router.register(r"v1/users", UserViewSet)
@ -30,13 +27,17 @@ router.register(r"v1/movies", MovieViewset)
router.register(r"v1/lists", MovieListViewset)
router.register(r"v1/schedules", ScheduleViewset)
router.register(r"v1/showings", ShowingViewset)
router.register(r"v1/users/profiles/", UserProfileViewSet)
urlpatterns = [
path("", include(router.urls)),
path("admin/", admin.site.urls),
path(r"v1/auth/token/", obtain_auth_token),
path(r"v1/auth/login/", user_views.LoginView.as_view(), name="knox_login"),
path(r"v1/auth/register/", register, name="register"),
path(r"v1/movies/search", movie_db_views.omdb_search, name="omdb_search"),
path(r"v1/auth/", include("knox.urls")),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
path("", include(router.urls)),
path("admin/", admin.site.urls),
path(r"v1/auth/token/", obtain_auth_token),
path(r"v1/auth/login/", user_views.LoginView.as_view(), name="knox_login"),
path(r"v1/auth/register/", register, name="register"),
path(r"v1/movies/search", movie_db_views.omdb_search, name="omdb_search"),
path(r"v1/auth/", include("knox.urls")),
path('v1/users/profile', UserProfileViewSet.as_view({"get": "current_user_profile"}),
name="current_user_profile"),
path('v1/users/profiles/<str:user__username>/', UserProfileViewSet.as_view({"get": "retrieve"}))
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View file

@ -0,0 +1,25 @@
# Generated by Django 5.2.2 on 2025-07-08 02:37
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='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -1,3 +1,12 @@
from django.contrib.auth.models import User
from django.db import models
# Create your models here.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100, null=True, blank=True)
@property
def lists(self):
return self.user.movielist_set.all()

6
users/permissions.py Normal file
View file

@ -0,0 +1,6 @@
from rest_framework import permissions
from rest_framework.permissions import SAFE_METHODS
class ReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
return request.method in SAFE_METHODS

View file

@ -1,12 +1,35 @@
from django.contrib.auth import authenticate
from rest_framework import serializers
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from movie_manager.serializers import MovieListSerializer
from users.models import UserProfile
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ["url", "username", "email", "password", "groups"]
fields = ["url", "username", "email", "groups"]
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
name = serializers.SerializerMethodField()
username = serializers.SerializerMethodField()
date_joined = serializers.SerializerMethodField()
lists = MovieListSerializer(many=True, read_only=True)
class Meta:
model = UserProfile
fields = ["name", "username", "date_joined", "lists"]
def get_name(self, obj):
return obj.name or ""
def get_username(self, obj):
return obj.user.username
def get_date_joined(self, obj):
return obj.user.date_joined
class GroupSerializer(serializers.HyperlinkedModelSerializer):

View file

@ -1,10 +1,13 @@
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import User
from django.http import JsonResponse
from knox.auth import TokenAuthentication
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import api_view
from rest_framework.decorators import api_view, action
from rest_framework.response import Response
from users.serializers import UserSerializer, GroupSerializer
from users.models import UserProfile
from users.permissions import ReadOnly
from users.serializers import UserSerializer, UserProfileSerializer
class UserViewSet(viewsets.ModelViewSet):
@ -15,6 +18,51 @@ class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
class UserProfileViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [permissions.IsAuthenticated | ReadOnly]
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
lookup_field = "user__username"
def retrieve(self, request, pk=None, *args, **kwargs):
try:
username = kwargs.get('user__username')
user = User.objects.get(username=username)
except User.DoesNotExist:
return Response([], status=status.HTTP_404_NOT_FOUND)
try:
user_profile = UserProfile.objects.get(user=user)
except UserProfile.DoesNotExist:
user_profile = UserProfile(
user=user,
)
user_profile.save()
return JsonResponse(UserProfileSerializer(user_profile).data)
@action(detail=False)
def current_user_profile(self, request, *args, **kwargs):
try:
user = request.user
except User.DoesNotExist:
return Response([], status=status.HTTP_404_NOT_FOUND)
try:
user_profile = UserProfile.objects.get(user=user)
except UserProfile.DoesNotExist:
user_profile = UserProfile(
user=user,
)
user_profile.save()
return JsonResponse(UserProfileSerializer(user_profile).data)
@api_view(["POST"])
def register(request):
user_data = UserSerializer(data=request.data)