Skip to content

Commit 37273ab

Browse files
added search with suggestions, trying mispelling correction
1 parent 49ba3db commit 37273ab

23 files changed

+179
-104
lines changed

TechWebProject/forms.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,16 @@ class UpdateUserForm(forms.ModelForm):
2929
username = forms.CharField(max_length=100,
3030
required=True,
3131
widget=forms.TextInput(attrs={'class': 'form-control'}))
32-
32+
email = forms.EmailField(required=True,
33+
widget=forms.TextInput(attrs={'class': 'form-control'}))
3334
class Meta:
3435
model = User
35-
fields = ['username']
36+
fields = ['username','email']
3637

3738

3839
class UpdateProfileForm(forms.ModelForm):
39-
avatar = forms.ImageField(widget=forms.FileInput(attrs={'class': 'form-control-file'}))
40-
41-
#bio = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5}))
40+
propic = forms.ImageField(widget=forms.FileInput(attrs={'class': 'form-control-file'}))
4241

4342
class Meta:
4443
model = Profile
45-
fields = ['avatar']
44+
fields = ['propic']

TechWebProject/settings.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
For the full list of settings and their values, see
1010
https://docs.djangoproject.com/en/5.0/ref/settings/
1111
"""
12-
12+
from os.path import join
1313
from pathlib import Path
1414
import os
1515

@@ -129,3 +129,7 @@
129129
LOGIN_REDIRECT_URL = "/?login=ok/"
130130
LOGIN_URL = "/login/?auth=notok"
131131
LOGOUT_REDIRECT_URL = '/'
132+
133+
134+
MEDIA_URL = '/media/'
135+
MEDIA_ROOT = join(BASE_DIR, 'media')

TechWebProject/urls.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
1. Import the include() function: from django.urls import include, path
1515
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1616
"""
17+
from django.conf.urls.static import static
1718
from django.contrib import admin
1819
from django.urls import include, path
1920
from django.contrib.auth import views as auth_views
2021
from django.contrib.auth import login,logout, authenticate
21-
from . import views
22-
22+
from . import views, settings
2323

2424
urlpatterns = [
2525
path('', include('movieapp.urls'), name='home'),
@@ -34,3 +34,7 @@
3434

3535
path('dashboard/', include('userdashboard.urls'), name='dashboard'),
3636
]
37+
38+
39+
if settings.DEBUG:
40+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

TechWebProject/views.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def logout_user(request):
4949
def profile(request):
5050
if request.method == 'POST':
5151
user_form = UpdateUserForm(request.POST, instance=request.user)
52-
profile_form = UpdateProfileForm(request.POST, request.FILES, instance=request.user.profile)
52+
profile_form = UpdateProfileForm(request.POST, request.FILES or None, instance=request.user.profile)
5353

5454
if user_form.is_valid() and profile_form.is_valid():
5555
user_form.save()

db.sqlite3

0 Bytes
Binary file not shown.
Loading
Loading

media/profilepictures/qrcode.png

46.3 KB
Loading
File renamed without changes.

movieapp/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class Meta:
6666
class Profile(models.Model):
6767
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
6868
propic = models.ImageField(
69-
upload_to='users_pics',
69+
upload_to='profilepictures/',
7070
default=join('static', 'unknown_user.png'),
7171
blank=True
7272
)

movieapp/templates/movie_list.html

+14
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ <h5 class="card-title text-truncate">{{ movie.title }} - {{ movie.year }}</h5>
3131
</div>
3232
</div>
3333
</div>
34+
{% empty %}
35+
{% if request.GET.q %}
36+
</div>
37+
<div class="alert alert-danger d-flex justify-content-center align-items-center" role="alert">
38+
<span class="text-center font-weight-bold" style="font-size: 1.5em;">
39+
No results for "{{ request.GET.q }}"
40+
</span>
41+
</div>
42+
{% else %}
43+
</div>
44+
<div class="alert alert-success d-flex justify-content-center align-items-center" role="alert">
45+
<span class="text-center font-weight-bold" style="font-size: 1.5em;">Look's like no one's here...</span>
46+
</div>
47+
{% endif %}
3448
{% endfor %}
3549
</div>
3650

movieapp/templates/search_result.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Title</title>
6+
</head>
7+
<body>
8+
9+
</body>
10+
</html>

movieapp/urls.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
path('watchlist/', add_movie_to_watchlist, name='add_movie_to_watchlist'),
1616

1717
path('getrecommended/', title_recommendation, name='get_recommended_titles'),
18-
path('search/', SearchMovieListView.as_view(), name='search_title')
18+
path('search/', SearchMovieListView.as_view(), name='search_title'),
19+
path('autocomplete/', MovieAutocomplete.as_view(), name='movie_autocomplete'),
1920

2021
]

movieapp/views.py

+22-19
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,52 @@
33
import requests
44
from django.contrib.auth.decorators import login_required
55
from django.contrib.auth.forms import UserChangeForm
6+
from django.db.models import Q
67
from django.http import JsonResponse
78
from django.shortcuts import redirect, render, get_object_or_404
89
from django.urls import reverse_lazy, reverse, resolve
910
from django.views.decorators.http import require_POST, require_GET
10-
from django.views.generic import ListView, UpdateView, DetailView
11+
from django.views.generic import ListView, UpdateView, DetailView, View
1112

1213
from .models import Movie, Profile, Request
1314
from django.views.generic import ListView
1415

1516
from .models import Movie
1617

18+
from .workers import *
1719

1820
class MovieListView(ListView):
1921
model = Movie
2022
template_name = 'movie_list.html'
2123
paginate_by = 10
2224

2325

24-
class SearchMovieListView(ListView):
26+
class SearchMovieListView(MovieListView):
2527
def get_queryset(self):
2628
query = self.request.GET.get('q')
29+
res = self.model.objects.filter(Q(title__icontains=query) | Q(description__icontains=query))
30+
if not res: # try to catch mispelled infos
31+
similars = find_similar_movies(Movie.objects.values_list('title'), query)
32+
return Movie.objects.filter(title__in=similars)
2733
if query:
28-
return Movie.objects.filter(title__icontains=query)
34+
return res
2935
else:
3036
return Movie.objects.none()
3137

3238

39+
class MovieAutocomplete(View):
40+
def get(self, request, *args, **kwargs):
41+
term = request.GET.get('term', '')
42+
movies = Movie.objects.filter(Q(title__icontains=term) | Q(description__icontains=term))[:10]
43+
suggestions = []
44+
for movie in movies:
45+
suggestions.append({
46+
'label': f"{movie.title} ({movie.year})",
47+
'value': movie.title
48+
})
49+
return JsonResponse(suggestions, safe=False)
50+
51+
3352
class MovieDetailView(DetailView):
3453
model = Movie
3554
template_name = 'movie_detail.html'
@@ -65,23 +84,7 @@ def get_context_data(self, **kwargs):
6584
return context
6685

6786

68-
def title_recommendation(movie: Movie):
69-
def count_common_genres(list1, list2):
70-
return len(set(list1) & set(list2))
71-
72-
genres = movie.get_genre_as_list()
73-
allmovies_genre_list = [(m, m.get_genre_as_list()) for m in
74-
Movie.objects.all().exclude(tmdb_id=movie.tmdb_id)]
7587

76-
common_genres_list = [[elem[0], count_common_genres(genres, elem[1])] for elem in allmovies_genre_list]
77-
common_genres_list = sorted(common_genres_list, key=lambda x: x[1], reverse=True)
78-
79-
recommended_titles = [elem[0] for elem in common_genres_list if elem[1]][:5]
80-
81-
if len(recommended_titles) > 0:
82-
return recommended_titles
83-
else:
84-
return None
8588

8689

8790
@login_required

movieapp/workers.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import difflib
2+
3+
from django.db.models import QuerySet
4+
5+
from movieapp.models import Movie
6+
7+
8+
def title_recommendation(movie: Movie):
9+
def count_common_genres(list1, list2):
10+
return len(set(list1) & set(list2))
11+
12+
genres = movie.get_genre_as_list()
13+
allmovies_genre_list = [(m, m.get_genre_as_list()) for m in
14+
Movie.objects.all().exclude(tmdb_id=movie.tmdb_id)]
15+
16+
common_genres_list = [[elem[0], count_common_genres(genres, elem[1])] for elem in allmovies_genre_list]
17+
common_genres_list = sorted(common_genres_list, key=lambda x: x[1], reverse=True)
18+
19+
recommended_titles = [elem[0] for elem in common_genres_list if elem[1]][:5]
20+
21+
if len(recommended_titles) > 0:
22+
return recommended_titles
23+
else:
24+
return None
25+
26+
27+
def find_similar_movies(movie_titles: list, query):
28+
print(movie_titles)
29+
similar_words = difflib.get_close_matches(query, [elem for elem in movie_titles], 3, 0.6)
30+
print(similar_words)
31+
return movie_titles

templates/GPT_home.html

+36-7
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@
55
<meta charset="UTF-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1.0">
77
<title>{% block navtitle %}Media Request Service{% endblock %}</title>
8+
<!-- Include jQuery UI CSS -->
9+
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
10+
11+
<!-- Include jQuery -->
12+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
13+
14+
<!-- Include jQuery UI -->
15+
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
16+
817
<!-- Bootstrap CSS -->
918
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
1019
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
11-
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
20+
<!--<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> -->
1221
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script>
1322
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
1423

@@ -98,10 +107,12 @@
98107
{% endif %}
99108
</ul>
100109
{% endif %}
101-
<form class="form-inline my-2 my-lg-0" method="POST" action {% url 'movieapp:search_title' %}>
102-
{% csrf_token %}
103-
<input name="search_query" class="form-control mr-sm-2" type="search" placeholder="Search a title..." aria-label="Search">
104-
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
110+
111+
<form class="form-inline my-2 my-lg-0" method="get" action="{% url 'movieapp:search_title' %}">
112+
<input type="text" id="movie-search" name="q" class="form-control mr-sm-2" placeholder="Search for a movie..." aria-label="Search">
113+
<div class="input-group-append">
114+
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
115+
</div>
105116
</form>
106117

107118
<ul class="navbar-nav">
@@ -116,7 +127,7 @@
116127
<div class="dropdown-menu dropdown-menu-right p-3 text-center" aria-labelledby="userDropdown">
117128
{% if request.user.is_authenticated %}
118129
<div class="card text-center" style="width: 18rem;">
119-
<img class="navbar-img" src="{% static 'userdashboard/unknown_user.png' %}" alt="Profile Picture">
130+
<img class="rounded-circle img-thumbnail navbar-img" src="{{ request.user.profile.propic.url }}" alt="profile image...">
120131
<div class="card-body text-center">
121132
<h5 class="card-title">{{ request.user.username }}</h5>
122133
<a href="{% url 'userdashboard:user_dashboard' %}" class="btn btn-primary mb-2">Dashboard</a>
@@ -193,7 +204,25 @@ <h5 class="modal-title">Goodbye {{user.username}} </h5>
193204
</div>
194205
</footer>
195206

196-
<!-- Bootstrap JS and dependencies -->
207+
<script>
208+
$(document).ready(function() {
209+
$("#movie-search").autocomplete({
210+
source: function(request, response) {
211+
$.ajax({
212+
url: "{% url 'movieapp:movie_autocomplete' %}",
213+
dataType: "json",
214+
data: {
215+
term: request.term
216+
},
217+
success: function(data) {
218+
response(data);
219+
}
220+
});
221+
},
222+
minLength: 3
223+
});
224+
});
225+
</script>
197226

198227
</body>
199228
</html>

templates/edit_profile.html

+36-29
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
{% extends "GPT_home.html" %}
22

3-
43
{% block content %}
5-
64
{% if user_form.errors %}
7-
<div class="alert alert-danger alert-dismissible" role="alert">
5+
<div class="alert alert-danger alert-dismissible fade show" role="alert">
86
<div id="form_errors">
97
{% for key, value in user_form.errors.items %}
108
<strong>{{ value }}</strong>
@@ -16,34 +14,43 @@
1614
</div>
1715
{% endif %}
1816
<div class="container mt-5">
19-
<div class="row justify-content-center">
20-
<div class="col-md-6">
21-
<div class="row ">
22-
<img class="rounded-circle account-img" src="{{ user.profile.propic.url }} " style="cursor: pointer;" alt="image..."/>
23-
</div>
24-
<div class="form-content">
25-
<form method="post" enctype="multipart/form-data">
26-
{% csrf_token %}
27-
<div class="form-row">
28-
<div class="col-md-5">
29-
<div class="form-group">
30-
<label class="small mb-1">Username:</label>
31-
{{ user_form.username }}
17+
<div class="row justify-content-center">
18+
<div class="col-md-8">
19+
<div class="card">
20+
<div class="card-header text-center">
21+
<h4>Profile Settings</h4>
3222
</div>
33-
<div class="form-group">
34-
<a href="#">Change Password</a>
35-
<hr>
36-
<label class="small mb-1">Change Avatar:</label>
37-
{{ profile_form.avatar }}
23+
<div class="card-body">
24+
<div class="text-center mb-4">
25+
<img src="{{ user.profile.propic.url }}" class="rounded-circle img-thumbnail" alt="Profile Picture" style="width: 150px; height: 150px;">
26+
</div>
27+
<form method="post" enctype="multipart/form-data">
28+
{% csrf_token %}
29+
<div class="form-row">
30+
<div class="form-group col-md-6">
31+
<label class="small mb-1">Username:</label>
32+
{{ user_form.username }}
33+
</div>
34+
<div class="form-group col-md-6">
35+
<label class="small mb-1">Email:</label>
36+
{{ user_form.email }}
37+
</div>
38+
</div>
39+
<div class="form-group">
40+
<a href="#">Change Password</a>
41+
</div>
42+
<div class="form-group">
43+
<label class="small mb-1">Change Avatar:</label>
44+
{{ profile_form.propic }}
45+
</div>
46+
<div class="text-center">
47+
<button type="submit" class="btn btn-primary">Save Changes</button>
48+
<button type="reset" class="btn btn-secondary">Reset</button>
49+
</div>
50+
</form>
3851
</div>
3952
</div>
4053
</div>
41-
<br><br>
42-
<button type="submit" class="btn btn-primary">Save Changes</button>
43-
<button type="reset" class="btn btn-secondary">Reset</button>
44-
</form>
45-
</div>
46-
</div>
47-
</div>
54+
</div>
4855
</div>
49-
{% endblock content %}
56+
{% endblock content %}

0 commit comments

Comments
 (0)