First working version

This commit is contained in:
2023-01-15 18:32:05 +01:00
parent 0b7d16d3e0
commit 823470e2f2
37 changed files with 902 additions and 24 deletions
View File
+20
View File
@@ -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)
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class WatchlistConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'watchlist'
+9
View File
@@ -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"]
+34
View File
@@ -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")]},
),
]
View File
+42
View File
@@ -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}"
+18
View File
@@ -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):
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+16
View File
@@ -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")
]
+119
View File
@@ -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"))