Skip to content

Commit 8b59ecd

Browse files
Merge remote-tracking branch 'origin/main' into frontend_backend_link
# Conflicts: # node_modules/.package-lock.json # node_modules/axios/CHANGELOG.md # node_modules/axios/dist/axios.js # node_modules/axios/dist/axios.js.map # node_modules/axios/dist/axios.min.js # node_modules/axios/dist/axios.min.js.map # node_modules/axios/dist/browser/axios.cjs # node_modules/axios/dist/browser/axios.cjs.map # node_modules/axios/dist/esm/axios.js # node_modules/axios/dist/esm/axios.js.map # node_modules/axios/dist/esm/axios.min.js # node_modules/axios/dist/esm/axios.min.js.map # node_modules/axios/dist/node/axios.cjs # node_modules/axios/dist/node/axios.cjs.map # node_modules/axios/lib/adapters/http.js # node_modules/axios/lib/core/buildFullPath.js # node_modules/axios/lib/env/data.js # node_modules/axios/package.json # package-lock.json # package.json
2 parents e892739 + edd1659 commit 8b59ecd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1677
-454
lines changed

backend/apps/lessons/tests.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,76 @@
11
from django.test import TestCase
2+
from django.urls import reverse
3+
from rest_framework.test import APITestCase
4+
from rest_framework import status
5+
from django.contrib.auth.models import User
6+
from apps.lessons.models import Assignment
27

38
# Create your tests here.
9+
class AssignmentAPITests(APITestCase):
10+
def setUp(self):
11+
# Create a superuser for testing
12+
self.user = User.objects.create_superuser(
13+
username='testadmin', password='testpassword', email='test@test.com'
14+
)
15+
16+
# Get access token
17+
response = self.client.post(
18+
reverse('token_obtain_pair'), # Use the actual name of the URL if configured
19+
{'username': 'testadmin', 'password': 'testpassword'},
20+
)
21+
self.access_token = response.data['access']
22+
23+
# Set authorization header
24+
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
25+
26+
# Create an initial assignment for testing
27+
self.assignment = Assignment.objects.create(
28+
title="Initial Assignment",
29+
description="Initial assignment description.",
30+
assignment_type="HW",
31+
deadline="2025-12-01T12:00:00Z",
32+
)
33+
34+
# Define URLs
35+
self.detail_url = reverse('assignment-detail', kwargs={'pk': self.assignment.pk})
36+
self.create_url = reverse('assignment-create')
37+
38+
# hardcode?
39+
# self.list_url = '/lessons/assignments/'
40+
#self.detail_url = f'/lessons/assignments/{self.assignment.pk}/'
41+
#self.create_url = '/lessons/assignments/create/'
42+
43+
# Test GET single assignment
44+
def test_get_assignment(self):
45+
response = self.client.get(self.detail_url)
46+
self.assertEqual(response.status_code, status.HTTP_200_OK)
47+
self.assertEqual(response.data['data']['title'], 'Initial Assignment')
48+
49+
# Test POST create new assignment
50+
def test_create_assignment(self):
51+
data = {
52+
"title": "New Assignment",
53+
"description": "This is a new assignment.",
54+
"assignment_type": "HW",
55+
"deadline": "2025-12-01T12:00:00Z"
56+
}
57+
response = self.client.post(self.create_url, data, format='json')
58+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
59+
self.assertEqual(response.data['data']['title'], 'New Assignment')
60+
61+
# Test PATCH update assignment
62+
def test_update_assignment(self):
63+
data = {"title": "Updated Title"}
64+
response = self.client.patch(self.detail_url, data, format='json')
65+
self.assertEqual(response.status_code, status.HTTP_200_OK)
66+
self.assertEqual(response.data['data']['title'], 'Updated Title')
67+
68+
# Test DELETE assignment
69+
def test_delete_assignment(self):
70+
response = self.client.delete(self.detail_url)
71+
self.assertEqual(response.status_code, status.HTTP_200_OK)
72+
self.assertEqual(response.data['status'], 'success')
73+
74+
# Verify it is deleted
75+
response = self.client.get(self.detail_url)
76+
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

backend/apps/search/serializers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@ class TutorSearchResultSerializer(serializers.ModelSerializer):
77
first_name = serializers.CharField(source="profile.user.first_name")
88
last_name = serializers.CharField(source="profile.user.last_name")
99
image_url = serializers.SerializerMethodField() # Custom field to get the file URL
10+
subjects = serializers.SerializerMethodField()
1011

1112
# Specify the model and fields to be included in the serialized output
1213
class Meta:
1314
model = TutorProfile
14-
fields = ["first_name", "last_name", "bio", "hourly_rate", "state", "city", "rating", "image_url"]
15+
fields = ["first_name", "last_name", "bio", "hourly_rate", "state", "city", "rating", "image_url", "subjects"]
1516

1617
def get_image_url(self, result_data):
1718
# Ensure that the profile and profile_picture exist before accessing them
1819
if hasattr(result_data, "profile") and hasattr(result_data.profile, "upload_record"):
1920
return UploadRecord.objects.build_url(result_data.profile.upload_record)
2021
return None # Return None if there's no profile picture
22+
23+
def get_subjects(self, result_data):
24+
# Extract only the titles of the subjects
25+
return list(result_data.subjects.values_list("title", flat=True))

backend/apps/search/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def get(self, request, format=None):
9696
# Perform search with 'what' term
9797
search_results = TutorProfile.objects.search(filtered_tutors, what)
9898
# Combine with partial_tutor_matches
99-
partial_tutor_matches = TutorProfile.objects.filter(lookup_tutors_query)
99+
partial_tutor_matches = filtered_tutors.filter(lookup_tutors_query) #changed now
100100
search_results = (search_results | partial_tutor_matches)
101101

102102
try:

backend/apps/users/managers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.db import models
22
from watson import search
3+
from django.db.models import Prefetch
4+
from apps.search.models import Subject
35

46
class ProfileManager(models.Manager):
57

@@ -76,7 +78,9 @@ def search(self, filtered_tutors, what):
7678

7779
def get_result_data(self, search_results):
7880
# Use select_related to reduce queries and retrieve only the required fields
79-
search_results = search_results.select_related('profile__user', 'profile').only(
81+
search_results = search_results.select_related('profile__user', 'profile').prefetch_related(
82+
Prefetch('subjects', queryset=Subject.objects.only('title'))
83+
).only(
8084
'profile__user__first_name',
8185
'profile__user__last_name',
8286
'bio',

frontend/src/Components/Booking.jsx

Lines changed: 35 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,21 @@
11
import { useState } from "react";
2+
import { useLocation } from "react-router-dom";
23
import Calendar from "react-calendar";
34
import "react-calendar/dist/Calendar.css"; // npm install react-calendar
45
import "../Styles/Booking.css";
56

67
const TUTOR_DATA = {
7-
id: 1,
8-
name: "Mr. Tom Cook",
9-
yearsOfExperience: 20,
10-
specialty: "Class Subjects Place Holder",
11-
location: "547 Carrington Trace Drive, Cornelius",
12-
aboutMe:
13-
"Hi! I'm a passionate coding tutor with a strong background in computer science and programming. I specialize in helping students and professionals grasp complex coding concepts, improve their problem-solving skills, and build real-world projects.\n" +
14-
"\n" +
15-
"With a Bachelor’s in Computer Science and currently pursuing my Master’s in Computer Science, I have a deep understanding of data structures, algorithms, AI, and cybersecurity. I also work as a researcher, which keeps me up-to-date with the latest advancements in technology.\n" +
16-
"\n" +
17-
"Whether you're a beginner looking to learn the basics or an advanced coder aiming to refine your skills, I provide clear explanations, hands-on exercises, and personalized guidance to help you succeed. My goal is to make coding fun, accessible, and rewarding for everyone.\n" +
18-
"\n" +
19-
"Let’s learn, code, and create together! ",
20-
profileImage: "assets/images/CodingTutor.png",
21-
socialLinks: {
22-
youtube: "https://youtube.com",
23-
linkedin: "https://www.linkedin.com",
24-
twitter: "https://twitter.com",
25-
facebook: "https://facebook.com",
26-
},
27-
// Mock availability: 'YYYY-MM-DD': [timeSlot, ...]
8+
// Mock availability: 'YYYY-MM-DD': [timeSlot, ...] Delete when back end is integrated
289
availableSlots: {
2910
"2025-03-20": ["10:00 AM", "2:00 PM", "4:00 PM"],
3011
"2025-03-21": ["9:00 AM", "11:00 AM", "3:00 PM"],
3112
"2025-03-22": ["8:00 AM", "1:00 PM", "5:00 PM"],
3213
},
3314
};
3415

35-
// Example "Suggested Tutors"
36-
const SUGGESTED_TUTORS = [
37-
{
38-
id: 2,
39-
name: "Ms. Jane Smith",
40-
specialty: "Software Engineer",
41-
profileImage: "assets/images/coding.jpg",
42-
},
43-
{
44-
id: 3,
45-
name: "Mr. Alex Johnson",
46-
specialty: "Full-Stack Developer",
47-
profileImage: "assets/images/coding.jpg",
48-
},
49-
];
50-
5116
export default function Booking() {
17+
const { state } = useLocation(); // Retrieve tutor details passed from the previous page
18+
const tutor = state?.tutor;
5219
const [selectedDate, setSelectedDate] = useState(null);
5320
const [timeOptions, setTimeOptions] = useState([]);
5421
const [selectedTime, setSelectedTime] = useState("");
@@ -72,8 +39,8 @@ export default function Booking() {
7239
setBookingConfirmed(false);
7340

7441
const dateKey = formatDateKey(date);
75-
if (TUTOR_DATA.availableSlots[dateKey]) {
76-
setTimeOptions(TUTOR_DATA.availableSlots[dateKey]);
42+
if (TUTOR_DATA.availableSlots[dateKey]) { // replace with tutor?.availableSlots? when backend is integrated
43+
setTimeOptions(TUTOR_DATA.availableSlots[dateKey]); // replace with tutor?.availableSlots? when backend is integrated
7744
} else {
7845
setTimeOptions([]);
7946
}
@@ -97,52 +64,36 @@ export default function Booking() {
9764
<div className="details-header">
9865
<div className="details-image-wrapper">
9966
<img
100-
src={TUTOR_DATA.profileImage}
101-
alt={TUTOR_DATA.name}
67+
src={tutor?.image_url || "assets/images/default_tutor.png"}
68+
alt={tutor?.first_name}
10269
className="details-image"
10370
/>
10471
</div>
10572
<div className="details-info">
106-
<h1>{TUTOR_DATA.name}</h1>
107-
<p className="experience">
108-
{TUTOR_DATA.yearsOfExperience} Years of Experience
109-
</p>
110-
<p className="location">{TUTOR_DATA.location}</p>
111-
<span className="specialty-tag">{TUTOR_DATA.specialty}</span>
112-
<div className="social-icons">
113-
<a
114-
href={TUTOR_DATA.socialLinks.youtube}
115-
target="_blank"
116-
rel="noreferrer"
117-
>
118-
<i className="fab fa-youtube" />
119-
</a>
120-
<a
121-
href={TUTOR_DATA.socialLinks.linkedin}
122-
target="_blank"
123-
rel="noreferrer"
124-
>
125-
<i className="fab fa-linkedin" />
126-
</a>
127-
<a
128-
href={TUTOR_DATA.socialLinks.twitter}
129-
target="_blank"
130-
rel="noreferrer"
131-
>
132-
<i className="fab fa-twitter" />
133-
</a>
134-
<a
135-
href={TUTOR_DATA.socialLinks.facebook}
136-
target="_blank"
137-
rel="noreferrer"
138-
>
139-
<i className="fab fa-facebook" />
140-
</a>
73+
<h1>{tutor?.first_name} {tutor?.last_name}</h1>
74+
{/* Add the rating badge here */}
75+
{tutor?.rating && (
76+
<div className="rating-badge-booking">
77+
<i className="bi bi-star-fill"></i>
78+
<span>{tutor.rating}</span>
79+
</div>
80+
)}
81+
<p className="text-muted">{tutor.city}, {tutor.state}</p>
82+
<div className="subjects-container">
83+
{Array.isArray(tutor.subjects)
84+
? tutor.subjects.map((subject, index) => (
85+
<span key={index} className="subject-tag">
86+
{subject}
87+
</span>
88+
))
89+
: tutor.subjects?.split(',').map((subject, index) => (
90+
<span key={index} className="subject-tag">
91+
{subject.trim()}
92+
</span>
93+
))}
14194
</div>
142-
<button
143-
className="appointment-btn"
144-
onClick={() => setShowModal(true)}
145-
>
95+
<br/>
96+
<button className="appointment-btn" onClick={() => setShowModal(true)}>
14697
Book Appointment
14798
</button>
14899
</div>
@@ -151,29 +102,12 @@ export default function Booking() {
151102
{/* ====== About Me Section ====== */}
152103
<section className="about-me-section">
153104
<h2>About Me</h2>
154-
<p>{TUTOR_DATA.aboutMe}</p>
105+
<p>{tutor?.bio}</p>
155106
</section>
156107
</section>
157-
158-
{/* ====== Suggestions Section ====== */}
159-
<aside className="suggestions-section">
160-
<h3>Suggested Tutors</h3>
161-
{SUGGESTED_TUTORS.map((tutor) => (
162-
<div key={tutor.id} className="suggested-tutor-card">
163-
<img
164-
src={tutor.profileImage}
165-
alt={tutor.name}
166-
className="suggested-tutor-image"
167-
/>
168-
<div className="suggested-tutor-info">
169-
<p className="suggested-tutor-name">{tutor.name}</p>
170-
<p className="suggested-tutor-specialty">{tutor.specialty}</p>
171-
</div>
172-
</div>
173-
))}
174-
</aside>
175108
</div>
176109

110+
177111
{/* ====== Modal for Calendar & Time Slots ====== */}
178112
{showModal && (
179113
<div className="modal-overlay">
@@ -188,7 +122,6 @@ export default function Booking() {
188122
<>
189123
<h2>Select a Date</h2>
190124
<Calendar onChange={handleDateChange} value={selectedDate} />
191-
192125
{/* Time Slots */}
193126
<div className="time-slot-section">
194127
<h3>Available Time Slots</h3>
@@ -211,8 +144,7 @@ export default function Booking() {
211144
</div>
212145
) : (
213146
<p className="no-slots">
214-
No available slots on{" "}
215-
{formatDisplayDate(selectedDate)}.
147+
No available slots on {formatDisplayDate(selectedDate)}.
216148
</p>
217149
)
218150
) : (
@@ -236,7 +168,7 @@ export default function Booking() {
236168
<div className="booking-confirmation">
237169
<h3>Booking Confirmation</h3>
238170
<p>
239-
<strong>Tutor:</strong> {TUTOR_DATA.name}
171+
<strong>Tutor:</strong> {tutor?.first_name} {tutor?.last_name}
240172
</p>
241173
<p>
242174
<strong>Date:</strong>{" "}

0 commit comments

Comments
 (0)