Skip to content

Commit b9b4eb9

Browse files
committed
changes to spotlight tab
1 parent 49262e4 commit b9b4eb9

File tree

16 files changed

+1105
-308
lines changed

16 files changed

+1105
-308
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Generated by Django 4.2.20 on 2025-05-24 12:54
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('api', '0001_initial'),
13+
]
14+
15+
operations = [
16+
migrations.AlterModelOptions(
17+
name='note',
18+
options={'ordering': ['-created_at']},
19+
),
20+
migrations.AddField(
21+
model_name='note',
22+
name='edited',
23+
field=models.BooleanField(default=False),
24+
),
25+
migrations.AddField(
26+
model_name='note',
27+
name='parent',
28+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='api.note'),
29+
),
30+
migrations.AddField(
31+
model_name='note',
32+
name='updated_at',
33+
field=models.DateTimeField(auto_now=True),
34+
),
35+
migrations.CreateModel(
36+
name='UserProfile',
37+
fields=[
38+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
39+
('avatar', models.URLField(default='https://api.dicebear.com/7.x/avataaars/svg')),
40+
('bio', models.TextField(blank=True, max_length=500)),
41+
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
42+
],
43+
),
44+
]

backend/api/models.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,64 @@
11
from django.db import models
22
from django.contrib.auth.models import User
3+
from django.utils import timezone
34

45
# Create your models here.
6+
class UserProfile(models.Model):
7+
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
8+
avatar = models.URLField(default='https://api.dicebear.com/7.x/avataaars/svg') # Default avatar URL
9+
bio = models.TextField(max_length=500, blank=True)
10+
11+
def __str__(self):
12+
return f"{self.user.username}'s profile"
13+
14+
def save(self, *args, **kwargs):
15+
if not self.avatar:
16+
# Generate unique avatar URL using username
17+
self.avatar = f'https://api.dicebear.com/7.x/avataaars/svg?seed={self.user.username}'
18+
super().save(*args, **kwargs)
19+
520
class Note(models.Model):
621
title = models.CharField(max_length=100)
722
content = models.TextField()
823
created_at = models.DateTimeField(auto_now_add=True)
24+
updated_at = models.DateTimeField(auto_now=True)
925
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notes")
26+
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='replies')
27+
edited = models.BooleanField(default=False)
28+
29+
class Meta:
30+
ordering = ['-created_at']
1031

1132
def __str__(self):
12-
return self.title
33+
return self.title
34+
35+
def save(self, *args, **kwargs):
36+
if self.pk: # If the note already exists
37+
self.edited = True
38+
super().save(*args, **kwargs)
39+
40+
@property
41+
def is_reply(self):
42+
return self.parent is not None
43+
44+
@property
45+
def time_since_created(self):
46+
now = timezone.now()
47+
diff = now - self.created_at
48+
49+
if diff.days > 365:
50+
years = diff.days // 365
51+
return f"{years}y ago"
52+
elif diff.days > 30:
53+
months = diff.days // 30
54+
return f"{months}mo ago"
55+
elif diff.days > 0:
56+
return f"{diff.days}d ago"
57+
elif diff.seconds > 3600:
58+
hours = diff.seconds // 3600
59+
return f"{hours}h ago"
60+
elif diff.seconds > 60:
61+
minutes = diff.seconds // 60
62+
return f"{minutes}m ago"
63+
else:
64+
return "just now"

backend/api/serializers.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,69 @@
11
from django.contrib.auth.models import User
22
from rest_framework import serializers
3-
from .models import Note
3+
from .models import Note, UserProfile
4+
5+
class UserProfileSerializer(serializers.ModelSerializer):
6+
class Meta:
7+
model = UserProfile
8+
fields = ['avatar', 'bio']
49

510
class UserSerializer(serializers.ModelSerializer):
11+
profile = UserProfileSerializer(read_only=True)
12+
613
class Meta:
714
model = User
8-
fields = ["id", "username", "email", "password"]
15+
fields = ["id", "username", "email", "password", "profile"]
916
extra_kwargs = {"password": {"write_only": True}}
1017

1118
def create(self, validated_data):
1219
user = User.objects.create_user(**validated_data)
20+
UserProfile.objects.create(user=user)
1321
return user
14-
22+
1523
class NoteSerializer(serializers.ModelSerializer):
24+
author_username = serializers.CharField(source='author.username', read_only=True)
25+
author_id = serializers.IntegerField(source='author.id', read_only=True)
26+
time_ago = serializers.CharField(source='time_since_created', read_only=True)
27+
replies = serializers.SerializerMethodField()
28+
reply_count = serializers.SerializerMethodField()
29+
author_avatar = serializers.SerializerMethodField()
30+
31+
class Meta:
32+
model = Note
33+
fields = ["id", "title", "content", "created_at", "updated_at", "author",
34+
"author_username", "author_id", "author_avatar", "time_ago",
35+
"edited", "replies", "reply_count", "parent"]
36+
extra_kwargs = {"author": {"read_only": True}}
37+
38+
def get_replies(self, obj):
39+
if obj.parent is None: # Only get replies for parent notes
40+
replies = obj.replies.all()
41+
return NoteReplySerializer(replies, many=True).data
42+
return []
43+
44+
def get_reply_count(self, obj):
45+
if obj.parent is None:
46+
return obj.replies.count()
47+
return 0
48+
49+
def get_author_avatar(self, obj):
50+
try:
51+
return obj.author.profile.avatar
52+
except UserProfile.DoesNotExist:
53+
return 'https://api.dicebear.com/7.x/avataaars/svg'
54+
55+
class NoteReplySerializer(serializers.ModelSerializer):
56+
author_username = serializers.CharField(source='author.username', read_only=True)
57+
author_avatar = serializers.SerializerMethodField()
58+
time_ago = serializers.CharField(source='time_since_created', read_only=True)
59+
1660
class Meta:
1761
model = Note
18-
fields = ["id", "title", "content", "created_at", "author"]
19-
extra_kwargs = {"author": {"read_only": True}}
62+
fields = ["id", "content", "created_at", "updated_at", "author_username",
63+
"author_avatar", "time_ago", "edited"]
64+
65+
def get_author_avatar(self, obj):
66+
try:
67+
return obj.author.profile.avatar
68+
except UserProfile.DoesNotExist:
69+
return 'https://api.dicebear.com/7.x/avataaars/svg'

backend/api/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
urlpatterns = [
55
path("notes/", views.NoteListCreate.as_view(), name="note-list"),
6-
path("notes/delete/<int:pk>", views.NoteDelete.as_view(), name="delete-note"),
6+
path("notes/<int:pk>/", views.NoteDetail.as_view(), name="note-detail"),
7+
path("notes/delete/<int:pk>/", views.NoteDelete.as_view(), name="note-delete"),
8+
path("users/create/", views.CreateUserView.as_view(), name="create-user"),
79
path("profile/", views.UserProfileView.as_view(), name="user-profile"),
810
path("search-movie/", views.SearchOMDbView.as_view(), name="search-movies")
911
]

backend/api/views.py

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,63 @@
1-
from django.shortcuts import render
1+
from django.shortcuts import render, get_object_or_404
22
from django.contrib.auth.models import User
3-
from rest_framework import generics
4-
from .serializers import UserSerializer, NoteSerializer
5-
from rest_framework.permissions import IsAuthenticated, AllowAny
6-
from .models import Note
3+
from rest_framework import generics, permissions, status
4+
from .serializers import UserSerializer, NoteSerializer, UserProfileSerializer
75
from rest_framework.views import APIView
86
from rest_framework.response import Response
97
import requests
8+
from .models import Note, UserProfile
109

1110
class NoteListCreate(generics.ListCreateAPIView):
1211
serializer_class = NoteSerializer
13-
permission_classes = [IsAuthenticated]
12+
permission_classes = [permissions.IsAuthenticated]
1413

1514
def get_queryset(self):
16-
user = self.request.user
17-
return Note.objects.all()
18-
15+
return Note.objects.filter(parent=None).order_by('-created_at')
16+
1917
def perform_create(self, serializer):
20-
if serializer.is_valid():
21-
serializer.save(author=self.request.user)
22-
else:
23-
print(serializer.errors)
18+
serializer.save(author=self.request.user)
19+
20+
class NoteDetail(generics.RetrieveUpdateDestroyAPIView):
21+
queryset = Note.objects.all()
22+
serializer_class = NoteSerializer
23+
permission_classes = [permissions.IsAuthenticated]
24+
25+
def get_queryset(self):
26+
return Note.objects.filter(author=self.request.user)
2427

2528
class NoteDelete(generics.DestroyAPIView):
29+
queryset = Note.objects.all()
2630
serializer_class = NoteSerializer
27-
permission_classes = [IsAuthenticated]
31+
permission_classes = [permissions.IsAuthenticated]
2832

2933
def get_queryset(self):
30-
user = self.request.user
31-
# user can only delete note if they are the author
32-
return Note.objects.filter(author=user)
34+
return Note.objects.filter(author=self.request.user)
3335

3436
# Create your views here.
3537
class CreateUserView(generics.CreateAPIView):
3638
queryset = User.objects.all()
3739
serializer_class = UserSerializer
38-
permission_classes = [AllowAny]
40+
permission_classes = [permissions.AllowAny]
3941

4042
class UserProfileView(APIView):
41-
permission_classes = [IsAuthenticated]
43+
permission_classes = [permissions.IsAuthenticated]
4244

4345
def get(self, request):
4446
serializer = UserSerializer(request.user)
4547
return Response(serializer.data)
46-
47-
class SearchOMDbView(APIView):
48-
permission_classes = [IsAuthenticated]
4948

49+
def put(self, request):
50+
profile = request.user.profile
51+
serializer = UserProfileSerializer(profile, data=request.data, partial=True)
52+
if serializer.is_valid():
53+
serializer.save()
54+
return Response(serializer.data)
55+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
56+
57+
class SearchOMDbView(APIView):
5058
def get(self, request):
51-
query = request.GET.get("q")
52-
if not query:
53-
return Response({"error": "Missing search query"}, status=400)
54-
url = f"http://www.omdbapi.com/?apikey=1e75925c&s={query}"
55-
response = requests.get(url)
56-
57-
if response.status_code != 200:
58-
return Response({"error": "OMDb API request failed."}, status=500)
59+
query = request.GET.get('query', '')
60+
api_key = '1e75925c' # Consider moving this to environment variables
5961

60-
data = response.json()
61-
return Response(data)
62+
response = requests.get(f'http://www.omdbapi.com/?t={query}&apikey={api_key}')
63+
return Response(response.json())

backend/backend/settings.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,21 @@
109109
# }
110110

111111
# local db - uncomment when running locally
112-
# DATABASES = {
113-
# "default": {
114-
# "ENGINE": "django.db.backends.sqlite3",
115-
# "NAME": "mydatabase",
116-
# }
117-
# }
118-
119-
# prod - db -- uncomment when pushing code
120112
DATABASES = {
121-
'default': dj_database_url.config(
122-
default=os.environ.get("DB_URL"),
123-
conn_max_age=600
124-
)
113+
"default": {
114+
"ENGINE": "django.db.backends.sqlite3",
115+
"NAME": "mydatabase",
116+
}
125117
}
126118

119+
# prod - db -- uncomment when pushing code
120+
# DATABASES = {
121+
# 'default': dj_database_url.config(
122+
# default=os.environ.get("DB_URL"),
123+
# conn_max_age=600
124+
# )
125+
# }
126+
127127
# Password validation
128128
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
129129

backend/mydatabase

12 KB
Binary file not shown.

0 commit comments

Comments
 (0)