|
@@ -0,0 +1,251 @@
|
|
|
|
|
+# mysite/views.py
|
|
|
|
|
+from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView
|
|
|
|
|
+from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
|
|
|
|
+from django.contrib.auth import logout
|
|
|
|
|
+from django.shortcuts import render, redirect, get_object_or_404
|
|
|
|
|
+from django.urls import reverse_lazy
|
|
|
|
|
+from django.db.models import Avg, Count
|
|
|
|
|
+from django.contrib import messages
|
|
|
|
|
+from .models import Musician, Song, Meloman, Rating, Album, Genre
|
|
|
|
|
+from .forms import MusicianForm, SongForm, RatingForm, RegistrationForm
|
|
|
|
|
+from django.views.generic import TemplateView
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# Регистрация
|
|
|
|
|
+def register(request):
|
|
|
|
|
+ if request.method == 'POST':
|
|
|
|
|
+ form = RegistrationForm(request.POST)
|
|
|
|
|
+ if form.is_valid():
|
|
|
|
|
+ form.save()
|
|
|
|
|
+ messages.success(request, 'Регистрация прошла успешно! Теперь вы можете войти в систему.')
|
|
|
|
|
+ return redirect('mysite:login')
|
|
|
|
|
+ else:
|
|
|
|
|
+ form = RegistrationForm()
|
|
|
|
|
+ return render(request, 'mysite/register.html', {'form': form})
|
|
|
|
|
+
|
|
|
|
|
+# Выход из системы
|
|
|
|
|
+def custom_logout(request):
|
|
|
|
|
+ logout(request)
|
|
|
|
|
+ messages.success(request, 'Вы успешно вышли из системы')
|
|
|
|
|
+ return redirect('mysite:login')
|
|
|
|
|
+
|
|
|
|
|
+# Главная страница со списком песен
|
|
|
|
|
+class SongListView(ListView):
|
|
|
|
|
+ model = Song
|
|
|
|
|
+ template_name = 'mysite/song_list.html'
|
|
|
|
|
+ context_object_name = 'songs'
|
|
|
|
|
+
|
|
|
|
|
+ def get_queryset(self):
|
|
|
|
|
+ return Song.objects.select_related('album__musician', 'genre').annotate(
|
|
|
|
|
+ avg_rating=Avg('rating__score'),
|
|
|
|
|
+ ratings_count=Count('rating')
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ def get_context_data(self, **kwargs):
|
|
|
|
|
+ context = super().get_context_data(**kwargs)
|
|
|
|
|
+ search_query = self.request.GET.get('search', '')
|
|
|
|
|
+ if search_query:
|
|
|
|
|
+ context['songs'] = context['songs'].filter(
|
|
|
|
|
+ title__icontains=search_query
|
|
|
|
|
+ ) | context['songs'].filter(
|
|
|
|
|
+ album__musician__name__icontains=search_query
|
|
|
|
|
+ )
|
|
|
|
|
+ context['search_query'] = search_query
|
|
|
|
|
+ return context
|
|
|
|
|
+
|
|
|
|
|
+# Список исполнителей
|
|
|
|
|
+class MusicianListView(LoginRequiredMixin, ListView):
|
|
|
|
|
+ model = Musician
|
|
|
|
|
+ template_name = 'mysite/musician_list.html'
|
|
|
|
|
+ context_object_name = 'musicians'
|
|
|
|
|
+ paginate_by = 10
|
|
|
|
|
+
|
|
|
|
|
+ def get_queryset(self):
|
|
|
|
|
+ return Musician.objects.annotate(albums_count=Count('album'))
|
|
|
|
|
+
|
|
|
|
|
+class MusicianCreateView(LoginRequiredMixin, CreateView):
|
|
|
|
|
+ model = Musician
|
|
|
|
|
+ form_class = MusicianForm
|
|
|
|
|
+ template_name = 'mysite/musician_form.html'
|
|
|
|
|
+ success_url = reverse_lazy('mysite:musician_list')
|
|
|
|
|
+
|
|
|
|
|
+ def form_valid(self, form):
|
|
|
|
|
+ messages.success(self.request, 'Исполнитель успешно добавлен')
|
|
|
|
|
+ return super().form_valid(form)
|
|
|
|
|
+
|
|
|
|
|
+class MusicianUpdateView(LoginRequiredMixin, UpdateView):
|
|
|
|
|
+ model = Musician
|
|
|
|
|
+ form_class = MusicianForm
|
|
|
|
|
+ template_name = 'mysite/musician_form.html'
|
|
|
|
|
+ success_url = reverse_lazy('mysite:musician_list')
|
|
|
|
|
+
|
|
|
|
|
+ def form_valid(self, form):
|
|
|
|
|
+ messages.success(self.request, 'Данные исполнителя обновлены')
|
|
|
|
|
+ return super().form_valid(form)
|
|
|
|
|
+
|
|
|
|
|
+class MusicianDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
|
|
|
|
+ model = Musician
|
|
|
|
|
+ template_name = 'mysite/musician_confirm_delete.html'
|
|
|
|
|
+ success_url = reverse_lazy('mysite:musician_list')
|
|
|
|
|
+
|
|
|
|
|
+ def test_func(self):
|
|
|
|
|
+ return self.request.user.is_superuser
|
|
|
|
|
+
|
|
|
|
|
+ def delete(self, request, *args, **kwargs):
|
|
|
|
|
+ messages.success(request, 'Исполнитель удален')
|
|
|
|
|
+ return super().delete(request, *args, **kwargs)
|
|
|
|
|
+
|
|
|
|
|
+# Список песен для CRUD
|
|
|
|
|
+class SongListDataView(LoginRequiredMixin, ListView):
|
|
|
|
|
+ model = Song
|
|
|
|
|
+ template_name = 'mysite/song_list_data.html'
|
|
|
|
|
+ context_object_name = 'songs'
|
|
|
|
|
+
|
|
|
|
|
+ def get_queryset(self):
|
|
|
|
|
+ return Song.objects.select_related('album__musician', 'genre').annotate(
|
|
|
|
|
+ avg_rating=Avg('rating__score')
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+class SongCreateView(LoginRequiredMixin, CreateView):
|
|
|
|
|
+ model = Song
|
|
|
|
|
+ form_class = SongForm
|
|
|
|
|
+ template_name = 'mysite/song_form.html'
|
|
|
|
|
+ success_url = reverse_lazy('mysite:song_list_data')
|
|
|
|
|
+
|
|
|
|
|
+ def form_valid(self, form):
|
|
|
|
|
+ messages.success(self.request, 'Песня успешно добавлена')
|
|
|
|
|
+ return super().form_valid(form)
|
|
|
|
|
+
|
|
|
|
|
+class SongUpdateView(LoginRequiredMixin, UpdateView):
|
|
|
|
|
+ model = Song
|
|
|
|
|
+ form_class = SongForm
|
|
|
|
|
+ template_name = 'mysite/song_form.html'
|
|
|
|
|
+ success_url = reverse_lazy('mysite:song_list_data')
|
|
|
|
|
+
|
|
|
|
|
+ def form_valid(self, form):
|
|
|
|
|
+ messages.success(self.request, 'Данные песни обновлены')
|
|
|
|
|
+ return super().form_valid(form)
|
|
|
|
|
+
|
|
|
|
|
+class SongDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
|
|
|
|
+ model = Song
|
|
|
|
|
+ template_name = 'mysite/song_confirm_delete.html'
|
|
|
|
|
+ success_url = reverse_lazy('mysite:song_list_data')
|
|
|
|
|
+
|
|
|
|
|
+ def test_func(self):
|
|
|
|
|
+ return self.request.user.is_superuser
|
|
|
|
|
+
|
|
|
|
|
+# Список меломанов
|
|
|
|
|
+class MelomanListView(LoginRequiredMixin, ListView):
|
|
|
|
|
+ model = Meloman
|
|
|
|
|
+ template_name = 'mysite/meloman_list.html'
|
|
|
|
|
+ context_object_name = 'melomans'
|
|
|
|
|
+
|
|
|
|
|
+ def get_queryset(self):
|
|
|
|
|
+ return Meloman.objects.annotate(
|
|
|
|
|
+ ratings_count=Count('rating'),
|
|
|
|
|
+ avg_score=Avg('rating__score')
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ def dispatch(self, request, *args, **kwargs):
|
|
|
|
|
+ if not request.user.is_superuser:
|
|
|
|
|
+ return redirect('mysite:song_list')
|
|
|
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
+
|
|
|
|
|
+# Профиль меломана
|
|
|
|
|
+class MelomanProfileView(LoginRequiredMixin, DetailView):
|
|
|
|
|
+ model = Meloman
|
|
|
|
|
+ template_name = 'mysite/profile.html'
|
|
|
|
|
+ context_object_name = 'meloman'
|
|
|
|
|
+
|
|
|
|
|
+ def get_object(self):
|
|
|
|
|
+ try:
|
|
|
|
|
+ return Meloman.objects.get(user=self.request.user)
|
|
|
|
|
+ except Meloman.DoesNotExist:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ def get_context_data(self, **kwargs):
|
|
|
|
|
+ context = super().get_context_data(**kwargs)
|
|
|
|
|
+ meloman = self.get_object()
|
|
|
|
|
+ if meloman:
|
|
|
|
|
+ context['total_ratings'] = meloman.rating_set.count()
|
|
|
|
|
+ context['avg_user_rating'] = meloman.rating_set.aggregate(Avg('score'))['score__avg']
|
|
|
|
|
+ context['favorite_genres'] = meloman.preference_set.select_related('genre').all()
|
|
|
|
|
+ context['age'] = meloman.age
|
|
|
|
|
+ context['recent_ratings'] = meloman.rating_set.select_related('song').order_by('-rating_date')[:5]
|
|
|
|
|
+ return context
|
|
|
|
|
+
|
|
|
|
|
+# Оценка песни
|
|
|
|
|
+def rate_song(request, song_id):
|
|
|
|
|
+ if not request.user.is_authenticated:
|
|
|
|
|
+ return redirect('mysite:login')
|
|
|
|
|
+
|
|
|
|
|
+ song = get_object_or_404(Song, pk=song_id)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ meloman = Meloman.objects.get(user=request.user)
|
|
|
|
|
+ except Meloman.DoesNotExist:
|
|
|
|
|
+ messages.error(request, 'У вас нет профиля меломана. Обратитесь к администратору.')
|
|
|
|
|
+ return redirect('mysite:song_list')
|
|
|
|
|
+
|
|
|
|
|
+ rating, created = Rating.objects.get_or_create(
|
|
|
|
|
+ meloman=meloman,
|
|
|
|
|
+ song=song,
|
|
|
|
|
+ defaults={'score': 5}
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if request.method == 'POST':
|
|
|
|
|
+ form = RatingForm(request.POST, instance=rating)
|
|
|
|
|
+ if form.is_valid():
|
|
|
|
|
+ form.save()
|
|
|
|
|
+ messages.success(request, f'Оценка для "{song.title}" сохранена!')
|
|
|
|
|
+ return redirect('mysite:song_list')
|
|
|
|
|
+ else:
|
|
|
|
|
+ form = RatingForm(instance=rating)
|
|
|
|
|
+
|
|
|
|
|
+ return render(request, 'mysite/rate_song.html', {'form': form, 'song': song})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+from django.db import connection
|
|
|
|
|
+
|
|
|
|
|
+class SongDetailsView(ListView):
|
|
|
|
|
+ """Использование представления song_details_view"""
|
|
|
|
|
+ template_name = 'mysite/song_details.html'
|
|
|
|
|
+ context_object_name = 'songs'
|
|
|
|
|
+
|
|
|
|
|
+ def get_queryset(self):
|
|
|
|
|
+ with connection.cursor() as cursor:
|
|
|
|
|
+ cursor.execute("""
|
|
|
|
|
+ SELECT song_id, title, musician_name, album_name,
|
|
|
|
|
+ genre_name, duration_sec, avg_rating, ratings_count
|
|
|
|
|
+ FROM song_details_view
|
|
|
|
|
+ ORDER BY avg_rating DESC NULLS LAST
|
|
|
|
|
+ """)
|
|
|
|
|
+ columns = [col[0] for col in cursor.description]
|
|
|
|
|
+ return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class RecommendationsView(LoginRequiredMixin, TemplateView):
|
|
|
|
|
+ """Использование функции recommend_songs"""
|
|
|
|
|
+ template_name = 'mysite/recommendations.html'
|
|
|
|
|
+
|
|
|
|
|
+ def get_context_data(self, **kwargs):
|
|
|
|
|
+ context = super().get_context_data(**kwargs)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ meloman = Meloman.objects.get(user=self.request.user)
|
|
|
|
|
+ meloman_id = meloman.meloman_id
|
|
|
|
|
+ except Meloman.DoesNotExist:
|
|
|
|
|
+ context['recommendations'] = []
|
|
|
|
|
+ context['error'] = 'Профиль меломана не найден'
|
|
|
|
|
+ return context
|
|
|
|
|
+
|
|
|
|
|
+ with connection.cursor() as cursor:
|
|
|
|
|
+ cursor.execute("""
|
|
|
|
|
+ SELECT * FROM recommend_songs(%s, %s)
|
|
|
|
|
+ """, [meloman_id, 10])
|
|
|
|
|
+
|
|
|
|
|
+ columns = [col[0] for col in cursor.description]
|
|
|
|
|
+ recommendations = [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
|
|
|
+
|
|
|
|
|
+ context['recommendations'] = recommendations
|
|
|
|
|
+ return context
|