First working version
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
class MovieVoteInline(admin.StackedInline):
|
||||
model = models.MovieVote()
|
||||
|
||||
class MovieAdmin(admin.ModelAdmin):
|
||||
fields = [
|
||||
"name", "watched", "suggested_by", "score"
|
||||
]
|
||||
readonly_fields = ("score",)
|
||||
list_display = ["name", "watched", "suggested_by", "score"]
|
||||
|
||||
@admin.display(description="Score")
|
||||
def score(self, instance):
|
||||
return instance.score
|
||||
|
||||
|
||||
admin.site.register(models.Movie, MovieAdmin)
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WatchlistConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'watchlist'
|
||||
@@ -0,0 +1,9 @@
|
||||
from django import forms
|
||||
|
||||
from . import models
|
||||
|
||||
class MovieEditForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = models.Movie
|
||||
fields = ["name", "suggested_by", "watched"]
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-12 21:45
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Movie',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MovieVote',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('vote', models.IntegerField(choices=[(1, 'Upvote'), (0, 'Novote'), (-1, 'Downvote')], default=0)),
|
||||
('seen', models.BooleanField(default=False, null=True)),
|
||||
('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='watchlist.movie')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-12 21:57
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('watchlist', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='movie',
|
||||
name='suggested_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='movie',
|
||||
name='watched',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-15 15:57
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('watchlist', '0002_movie_suggested_by_movie_watched'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='movie',
|
||||
options={'permissions': [('change_suggested_by', 'Can change who suggested movie'), ('change_watched', 'Can mark as watched')]},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='movie',
|
||||
name='suggested_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-15 16:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('watchlist', '0003_alter_movie_options_alter_movie_suggested_by'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='movie',
|
||||
options={'permissions': [('moderate_movies', "Can edit other's movies and mark them as watched")]},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
from functools import reduce
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib import admin
|
||||
|
||||
class Movie(models.Model):
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
("moderate_movies", "Can edit other's movies and mark them as watched"),
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
suggested_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
watched = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def score(self):
|
||||
return reduce(lambda result,v: result+v.vote, self.movievote_set.all(), 0)
|
||||
|
||||
@property
|
||||
def seen_score(self):
|
||||
return reduce(lambda result,v: result+int(v.seen), self.movievote_set.all(), 0)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class MovieVote(models.Model):
|
||||
|
||||
class Vote(models.IntegerChoices):
|
||||
UPVOTE = 1
|
||||
NOVOTE = 0
|
||||
DOWNVOTE = -1
|
||||
|
||||
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
vote = models.IntegerField(choices=Vote.choices, default=Vote.NOVOTE)
|
||||
seen = models.BooleanField(default=False, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username}'s vote for {self.movie.name}"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
|
||||
class MovieSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
suggested_by = serializers.ReadOnlyField(source="suggested_by.username")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = models.Movie
|
||||
fields = ["url", "name", "watched", "suggested_by", "score"]
|
||||
|
||||
# class VoteSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,16 @@
|
||||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
from . import views
|
||||
|
||||
api_router = routers.DefaultRouter()
|
||||
api_router.register(r'movie', views.MovieViewSet)
|
||||
|
||||
app_name = "watchlist"
|
||||
urlpatterns = [
|
||||
path('', views.IndexView.as_view(), name="index"),
|
||||
path('movie/<int:pk>', views.DetailView.as_view(), name="detail"),
|
||||
path('movie/<int:pk>/vote', views.vote, name="vote"),
|
||||
path('movie/<int:pk>/edit', views.EditView.as_view(), name="edit"),
|
||||
path('movie/<int:pk>/delete', views.delete, name="delete"),
|
||||
path('movie', views.submit, name="submit")
|
||||
]
|
||||
@@ -0,0 +1,119 @@
|
||||
from django.http import HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.views import generic
|
||||
from django.views.decorators.http import require_http_methods, require_safe, require_POST
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import viewsets, permissions
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from . import serializers, models
|
||||
|
||||
class MovieViewSet(viewsets.ModelViewSet):
|
||||
queryset = models.Movie.objects.order_by('id').all()
|
||||
serializer_class = serializers.MovieSerializer
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user)
|
||||
|
||||
# @action(detail=True, methods=["POST"])
|
||||
# def vote(self, request, pk=None):
|
||||
# movie = self.get_object()
|
||||
# vote = request.date.get("vote", 0)
|
||||
|
||||
|
||||
class IndexView(generic.ListView):
|
||||
template_name = "watchlist/index.html"
|
||||
model = models.Movie
|
||||
|
||||
def get_queryset(self):
|
||||
return models.Movie.objects.order_by('id').all()
|
||||
|
||||
def get_context_data(self):
|
||||
context = super().get_context_data()
|
||||
context['can_add_movie'] = self.request.user.has_perm("watchlist.add_movie")
|
||||
return context
|
||||
|
||||
class DetailView(generic.DetailView):
|
||||
model = models.Movie
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
votes = self.object.movievote_set.all()
|
||||
user_vote = None
|
||||
if self.request.user.is_authenticated:
|
||||
user_vote = votes.filter(user=self.request.user).first()
|
||||
context["votes"] = votes
|
||||
context["user_vote"] = user_vote
|
||||
return context
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def vote(request, pk):
|
||||
movie = get_object_or_404(models.Movie, pk=pk)
|
||||
user_vote = movie.movievote_set.filter(user=request.user).first()
|
||||
if user_vote is None:
|
||||
user_vote = models.MovieVote(movie=movie, user=request.user)
|
||||
user_vote.vote = request.POST['vote']
|
||||
user_vote.seen = request.POST.get('seen', False) == "on"
|
||||
user_vote.save()
|
||||
return HttpResponseRedirect(reverse('watchlist:detail', args=(pk,)))
|
||||
|
||||
class EditView(generic.DetailView):
|
||||
model = models.Movie
|
||||
template_name = "watchlist/edit.html"
|
||||
|
||||
def can_edit_movie(self, request):
|
||||
return request.user.has_perm('watchlist.moderate_movies') or request.user == self.object.suggested_by
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["users"] = User.objects.all() if self.request.user.has_perm("watchlist.moderate_movies") else None
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if not self.can_edit_movie(request):
|
||||
return HttpResponseForbidden("You cannot edit this object.")
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if not self.can_edit_movie(request):
|
||||
return HttpResponseForbidden("You cannot edit this object.")
|
||||
if "name" in request.POST:
|
||||
self.object.name = request.POST["name"]
|
||||
if "suggested_by" in request.POST:
|
||||
if request.user.has_perm('watchlist.moderate_movies'):
|
||||
new_suggestor = User.objects.filter(username=request.POST["suggested_by"]).first()
|
||||
if new_suggestor is None:
|
||||
return HttpResponseBadRequest("The new suggestor doesn't exist.")
|
||||
self.object.suggested_by = new_suggestor
|
||||
# else:
|
||||
# if request.POST["suggested_by"] != self.object.suggested_by.username:
|
||||
# return HttpResponseForbidden("You cannot change the suggested by field.")
|
||||
self.object.save()
|
||||
return HttpResponseRedirect(reverse('watchlist:detail', args=(kwargs["pk"],)))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def submit(request):
|
||||
if not request.user.has_perm("watchlist.add_movie"):
|
||||
return HttpResponseForbidden("You can't add new movies.")
|
||||
movie = models.Movie(name=request.POST["name"], suggested_by=request.user, watched=False)
|
||||
movie.save()
|
||||
return HttpResponseRedirect(reverse("watchlist:index"))
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def delete(request, pk):
|
||||
movie = get_object_or_404(models.Movie, pk=pk)
|
||||
if not (request.user.has_perm("watchlist.moderate_movies") or (
|
||||
request.user.has_perm("watchlist.delete_movie") and request.user == movie.suggested_by
|
||||
)):
|
||||
return HttpResponseForbidden("You can't delete this movie")
|
||||
movie.delete()
|
||||
return HttpResponseRedirect(reverse("watchlist:index"))
|
||||
Reference in New Issue
Block a user