diff --git a/EpisodesCommunity/settings.py b/EpisodesCommunity/settings.py index 63e691d..89949cd 100644 --- a/EpisodesCommunity/settings.py +++ b/EpisodesCommunity/settings.py @@ -44,6 +44,7 @@ ALLOWED_HOSTS = [] AUTHENTICATION_BACKENDS = ( 'LandingPage.backends.OAuthBackend', + 'guardian.backends.ObjectPermissionBackend', 'django.contrib.auth.backends.ModelBackend', ) @@ -59,6 +60,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'guardian', ] MIDDLEWARE = [ diff --git a/EpisodesCommunity/urls.py b/EpisodesCommunity/urls.py index 8676f12..d993ef4 100644 --- a/EpisodesCommunity/urls.py +++ b/EpisodesCommunity/urls.py @@ -20,6 +20,6 @@ from django.conf.urls.static import static urlpatterns = [ url(r'^admin/', admin.site.urls), - url(r'^show/(?P\w{1,5})/', include('Show.urls')), + url(r'^show/(?P\w{1,5})/', include('Show.urls')), url(r'^', include('LandingPage.urls')) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/LandingPage/admin.py b/LandingPage/admin.py index 44f4c55..b772b2c 100644 --- a/LandingPage/admin.py +++ b/LandingPage/admin.py @@ -3,6 +3,8 @@ from django.contrib.auth.admin import UserAdmin from .models import * from .forms import SpecialUserChangeForm +from guardian.admin import GuardedModelAdmin + class SpecialUserAdmin(UserAdmin): form = SpecialUserChangeForm @@ -10,8 +12,11 @@ class SpecialUserAdmin(UserAdmin): (None, {'fields': ('display_name',)}), ) +class ShowAdmin(GuardedModelAdmin): + pass + # Register your models here. -admin.site.register(Show) +admin.site.register(Show, ShowAdmin) admin.site.register(User, SpecialUserAdmin) admin.site.register(Ban) admin.site.register(ShowModerator) diff --git a/LandingPage/static/css/style.css b/LandingPage/static/css/style.css index a37abd1..9000d5e 100644 --- a/LandingPage/static/css/style.css +++ b/LandingPage/static/css/style.css @@ -13,6 +13,12 @@ body { text-decoration: none; color: #000; } +.button.modbutton { + background-color: #ffe599; + border: 1px solid #a76300; + color: #945800; + font-weight: bold; +} input[type="submit"] { font-size: 120%; } @@ -130,6 +136,9 @@ section.show-details { margin: 0; font-size: 200%; } +.show-details .details.season h3 { + margin-top: 0; +} .show-details .details .description { width: 80%; } @@ -165,16 +174,22 @@ a.episode .submission_cnt { .submission { padding: 10px; } +.submission .data { + font-size: 90%; + color: #b1b1b1; +} .submission a.link { font-size: 180%; text-decoration: none; color: #191919; font-style: italic; + display: inline-block; + padding: 15px 0; } .vote-btns { float: right; } -.vote-positive, .vote-negative { +.vote-positive, .vote-negative, .sub-report { padding: 10px; display: inline-block; min-width: 20px; @@ -191,7 +206,7 @@ a.episode .submission_cnt { background-color: #a4ffa7; color: #008005; } -.vote-negative { +.vote-negative, .sub-report { background-color: #ffa6a6; color: #ab0000; } @@ -205,6 +220,15 @@ a.episode .submission_cnt { border: 1px solid #b10000; margin: 5px 0; } +.actions { + float: right; + padding: 15px; +} +.divider { + margin: 0 5px; + opacity: 0.5; + user-select: none; +} @media all and (max-width: 800px) { .logo { font-size: 5vw !important; diff --git a/Show/forms.py b/Show/forms.py index 163cff9..663c3a4 100644 --- a/Show/forms.py +++ b/Show/forms.py @@ -1,5 +1,5 @@ from django import forms -from LandingPage.models import Submission +from LandingPage.models import Submission, Season, Episode class SubmissionForm(forms.ModelForm): class Meta(): @@ -9,3 +9,13 @@ class SubmissionForm(forms.ModelForm): 'url': 'URL to your episode', 'tags': 'Describe your link. Comma-separated list of keywords' } + +class SeasonForm(forms.ModelForm): + class Meta(): + model = Season + fields = ('name','number','description','artwork',) + +class EpisodeForm(forms.ModelForm): + class Meta(): + model = Episode + fields = ('episode','name','summary','airdate',) diff --git a/Show/templates/episode.html b/Show/templates/episode.html index 0fa4fbe..16d6ba7 100644 --- a/Show/templates/episode.html +++ b/Show/templates/episode.html @@ -14,16 +14,27 @@ {% endblock %} {% block content %} +{% load guardian_tags %} +{% get_obj_perms request.user for show as "show_perms" %}
+
+ Submitted {{sbm.timestamp}} by {{sbm.user.display_name}}· + {% if "change_show" in show_perms %} +  Change + {% else %} + Report Invalid or Spam + {% endif %} +
{% empty %}

Nobody has submitted any links yet.

diff --git a/Show/templates/episode_add.html b/Show/templates/episode_add.html new file mode 100644 index 0000000..786c5f1 --- /dev/null +++ b/Show/templates/episode_add.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %} + Add an Episode - Season {{season.number}} of {{show.name}} - Episodes.Community +{% endblock %} +{% block content %} +
+ + +
+
+  Show Index +

Add an Episode

+ {% if error %} +
{{error}}
+ {% endif %} +
+ {% csrf_token %} + {{ form }} + +
+ +
+{% endblock %} diff --git a/Show/templates/season_add.html b/Show/templates/season_add.html new file mode 100644 index 0000000..e907841 --- /dev/null +++ b/Show/templates/season_add.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %} + Add a season - {{show.name}} - Episodes.Community +{% endblock %} +{% block content %} +
+ + +
+
+  Show Index +

Add a Season

+ {% if error %} +
{{error}}
+ {% endif %} +
+ {% csrf_token %} + {{ form }} + +
+ +
+{% endblock %} diff --git a/Show/templates/show.html b/Show/templates/show.html index a4ed4de..a25be66 100644 --- a/Show/templates/show.html +++ b/Show/templates/show.html @@ -12,6 +12,8 @@ {% endblock %} {% block content %} +{% load guardian_tags %} +{% get_obj_perms request.user for show as "show_perms" %}
+ {% if "change_show" in show_perms %} +  Add a Season + {% endif %}

Watch Now

{% for season in seasons %}
+ {% if "change_show" in show_perms %} +
+ +
+ {% endif %}
{% if season.name %}{{season.number}} - {{season.name}}{% else %}Season {{season.number}}{%endif%}
{% for episode in season.episodes.all %} diff --git a/Show/templates/submit.html b/Show/templates/submit.html index 98d285c..45e51b5 100644 --- a/Show/templates/submit.html +++ b/Show/templates/submit.html @@ -7,12 +7,21 @@ +
+{% endblock %} diff --git a/Show/urls.py b/Show/urls.py index 10095fa..9eaecc5 100644 --- a/Show/urls.py +++ b/Show/urls.py @@ -20,6 +20,9 @@ from . import views urlpatterns = [ url(r'^$', views.IndexView.as_view()), + url(r'^season/new$', views.SeasonSubmitForm), + url(r'^season/(?P\d{1,4})/append$', views.EpisodeSubmitForm), + url(r'^submission/(?P\d{1,4})/moderate$', views.SubmissionModForm), url(r'^episode/(?P\d{1,4})/(?P\d{1,4})(-[\w-]+)?/?$', views.EpisodeView.as_view()), url(r'^episode/(?P\d{1,4})/(?P\d{1,4})(-[\w-]+)?/submit$', views.SubmissionForm), url(r'^vote/(?P\d+)/(?P[0-1])/?$', views.SubmissionVoteSubmit.as_view()) diff --git a/Show/views.py b/Show/views.py index f2e2ce4..eff4d8c 100644 --- a/Show/views.py +++ b/Show/views.py @@ -1,5 +1,5 @@ from django.template import RequestContext -from django.shortcuts import render +from django.shortcuts import render, get_list_or_404, get_object_or_404 from django.views import View from django.views.generic.base import TemplateView from django.contrib.auth.decorators import login_required @@ -10,11 +10,9 @@ from django.http import HttpResponseRedirect from django.db.models import Case, When, Value, IntegerField, Count, F from django.contrib.auth.mixins import LoginRequiredMixin -from LandingPage.models import User -from LandingPage.models import Show -from LandingPage.models import Season -from LandingPage.models import Episode -from LandingPage.models import Submission, SubmissionVote +from guardian.decorators import permission_required_or_403 + +from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote from . import forms @@ -26,15 +24,11 @@ import datetime class IndexView(TemplateView): template_name = "show.html" - def get_context_data(self, abbreviation, **kwargs): + def get_context_data(self, abbr, **kwargs): ctx = super().get_context_data() - # Get show by abbreviation, add episode count to the show and return only the first object - show = Show.objects.filter(abbr=abbreviation).first() - - # 404 - if not show: - raise Http404("Show does not exist") + # Get show by abbr, add episode count to the show and return only the first object + show = get_object_or_404(Show, abbr=abbr) # Get all seasons of the show and annotate episode counts onto them seasons = show.seasons.all() @@ -49,21 +43,12 @@ class IndexView(TemplateView): class EpisodeView(TemplateView): template_name = "episode.html" - def get_context_data(self, abbreviation, season, episode, **kwargs): + def get_context_data(self, abbr, season, episode, **kwargs): ctx = super().get_context_data() - # Get show by abbreviation - show = Show.objects.filter(abbr=abbreviation).first() - - # Get episode by season and episode number - episode = Episode.objects.filter(show=show,season__number=season,episode=episode).first() - - # 404's - if not show: - raise Http404("Show does not exist") - - if not episode: - raise Http404("Episode does not exist") + # Get show by abbr + show = get_object_or_404(Show, abbr=abbr) + episode = get_object_or_404(Episode, show=show,season__number=season,episode=episode) # I acknowledge that this is a mess. A functional mess. But a mess nonetheless. Hey, that rhymed! submissions = episode.submissions.annotate( @@ -88,18 +73,11 @@ class EpisodeView(TemplateView): # Submission form GET and POST @login_required -def SubmissionForm(req, abbreviation, season, episode): - show = Show.objects.get(abbr=abbreviation) - episode = Episode.objects.filter(show=show,season__number=season,episode=episode).first() +def SubmissionForm(req, abbr, season, episode): + show = get_object_or_404(Show, abbr=abbr) + episode = get_object_or_404(Episode, show=show,season__number=season,episode=episode) user = req.user - # 404's - if not show: - raise Http404("Show does not exist") - - if not episode: - raise Http404("Episode does not exist") - form = forms.SubmissionForm() # Request context @@ -122,39 +100,152 @@ def SubmissionForm(req, abbreviation, season, episode): ctx['error'] = 'This URL has already been submitted!' return render(req, "submit.html", ctx) - # Check if there has been a submission by this user for this episode within the last 24 hours - if Submission.objects.filter(user=user,episode=episode,timestamp__gte=datetime.datetime.now() - datetime.timedelta(hours=24)).count() > 0: - ctx['error'] = 'You can only submit one link for an episode in 24 hours!' - return render(req, "submit.html", ctx) + if not user.has_perm('LandingPage.change_show'): + # Check if there has been a submission by this user for this episode within the last 24 hours + if Submission.objects.filter(user=user,episode=episode,timestamp__gte=datetime.datetime.now() - datetime.timedelta(hours=24)).count() > 0: + ctx['error'] = 'You can only submit one link for an episode in 24 hours!' + return render(req, "submit.html", ctx) - # Have to do this because you can't add fields to a form - # If you know a better way of doing this, be my guest new_submission = form.save(commit=False) new_submission.user = user new_submission.episode = episode new_submission.save() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbreviation, episode.season.number, episode.episode)) + return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' return render(req, "submit.html", ctx) +# Edit a submission - for moderators +@permission_required_or_403('LandingPage.change_show', (Show, 'abbr', 'abbr'), accept_global_perms=True) +def SubmissionModForm(req, abbr, submission): + show = get_object_or_404(Show, abbr=abbr) + submission = get_object_or_404(Submission, pk=submission) + episode = submission.episode + user = req.user + + form = forms.SubmissionForm(instance=submission) + + # Request context + ctx = { + 'form': form, + 'show': show, + 'episode': episode + } + + # Handle POST + if req.method == 'POST': + + if 'delete' in req.POST: + submission.delete() + return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) + + if 'delete_ban' in req.POST: + submission.delete() + return HttpResponseRedirect('/ban?user=%d'%(submission.user.pk)) + + form = forms.SubmissionForm(req.POST, instance=submission) + ctx['form'] = form + + if form.is_valid(): + form_data = form.cleaned_data + form.save() + + return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) + else: + ctx['error'] = 'Invalid fields!' + + return render(req, "submit_mod.html", ctx) + +# Season form GET and POST +@permission_required_or_403('LandingPage.change_show', (Show, 'abbr', 'abbr'), accept_global_perms=True) +def SeasonSubmitForm(req, abbr): + show = get_object_or_404(Show, abbr=abbr) + user = req.user + + form = forms.SeasonForm() + + # Request context + ctx = { + 'form': form, + 'show': show + } + + # Handle POST + if req.method == 'POST': + form = forms.SeasonForm(req.POST) + ctx['form'] = form + + if form.is_valid(): + form_data = form.cleaned_data + + # Check if the URL has already been submitted + if Season.objects.filter(show=show,number=form_data['number']).count() > 0: + ctx['error'] = 'This season has already been submitted!' + return render(req, "season_add.html", ctx) + + new_season = form.save(commit=False) + new_season.show = show + new_season.save() + + return HttpResponseRedirect('/show/%s'%(abbr)) + else: + ctx['error'] = 'Invalid fields!' + + return render(req, "season_add.html", ctx) + +# Episode form GET and POST +@permission_required_or_403('LandingPage.change_show', (Show, 'abbr', 'abbr'), accept_global_perms=True) +def EpisodeSubmitForm(req, abbr, season): + show = get_object_or_404(Show, abbr=abbr) + season = get_object_or_404(Season, show=show,number=season) + user = req.user + + form = forms.EpisodeForm() + + # Request context + ctx = { + 'form': form, + 'season': season, + 'show': show + } + + # Handle POST + if req.method == 'POST': + form = forms.EpisodeForm(req.POST) + ctx['form'] = form + + if form.is_valid(): + form_data = form.cleaned_data + + # Check if the URL has already been submitted + if Episode.objects.filter(show=show,episode=form_data['episode'],season=season).count() > 0: + ctx['error'] = 'This episode has already been submitted!' + return render(req, "episode_add.html", ctx) + + new_episode = form.save(commit=False) + new_episode.show = show + new_episode.season = season + new_episode.save() + + return HttpResponseRedirect('/show/%s'%(abbr)) + else: + ctx['error'] = 'Invalid fields!' + + return render(req, "episode_add.html", ctx) + # Vote request # /show/{{abbr}}/vote/{{submission id}}/{{positive == 1}} class SubmissionVoteSubmit(LoginRequiredMixin, View): - def post (self, req, abbreviation, subid, positive): + def post (self, req, abbr, subid, positive): # Convert positive parameter into a boolean pos_bool = int(positive) == 1 user = req.user # Get the submission from the database - submission = Submission.objects.filter(id=subid).first() - - # 404 - if not submission: - raise Http404("Submission does not exist") + submission = get_object_or_404(Submission, id=subid) # Prevent voting for own submission if submission.user == user: @@ -176,5 +267,5 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View): ) new_vote.save() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbreviation, submission.episode.season.number, submission.episode.episode)) + return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, submission.episode.season.number, submission.episode.episode)) diff --git a/requirements.txt b/requirements.txt index 2b00bcd..71ad7c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ Django==1.11.4 Pillow==4.2.1 dj-database-url==0.4.2 requests==2.18.4 +django-guardian==1.4.9