commit
325af6929f
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
@ -20,5 +20,6 @@ from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^show/(?P<abbreviation>\w{1,5})/', include('Show.urls')),
|
||||
url(r'^', include('LandingPage.urls'))
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
@ -4,6 +4,38 @@ body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.button, input[type="submit"], button {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
background-color: #fdfdfd;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
input[type="submit"] {
|
||||
font-size: 120%;
|
||||
}
|
||||
label {
|
||||
width: 200px;
|
||||
display: block;
|
||||
}
|
||||
input[type=text], input:not([type=submit]) {
|
||||
padding: 5px;
|
||||
font-size: 120%;
|
||||
width: 280px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #b3b3b3;
|
||||
background-color: #ffffff;
|
||||
box-shadow: inset 2px 2px 5px #ddd;
|
||||
}
|
||||
.helptext {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
cursor: help;
|
||||
}
|
||||
.unibar {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
@ -59,6 +91,117 @@ body {
|
||||
width: 20%;
|
||||
float: right;
|
||||
}
|
||||
section.show-details {
|
||||
position: relative;
|
||||
}
|
||||
.show-details .banner {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: blur(4px);
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
}
|
||||
.show-details .banner-cover {
|
||||
overflow: auto;
|
||||
}
|
||||
.show-details .artwork {
|
||||
width: 182px;
|
||||
margin: 15px;
|
||||
float: left;
|
||||
}
|
||||
.show-details .details {
|
||||
margin-left: 220px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
min-height: 260px;
|
||||
overflow: hidden;
|
||||
font-size: 150%;
|
||||
background-color: #dadada38;
|
||||
padding: 10px;
|
||||
}
|
||||
.show-details .details h1 {
|
||||
margin: 0;
|
||||
font-size: 200%;
|
||||
}
|
||||
.show-details .details .description {
|
||||
width: 80%;
|
||||
}
|
||||
section.seasons, section.submissions {
|
||||
min-height: 100vh;
|
||||
padding: 25px;
|
||||
}
|
||||
.season-name {
|
||||
padding: 6px;
|
||||
font-size: 180%;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
a.episode {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
a.episode:nth-child(even) {
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
a.episode .submission_cnt {
|
||||
float: right;
|
||||
color: #949494;
|
||||
}
|
||||
.submission-list .submission {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.submission-list .submission:nth-child(even) {
|
||||
background-color: #ececec;
|
||||
}
|
||||
.submission {
|
||||
padding: 10px;
|
||||
}
|
||||
.submission a.link {
|
||||
font-size: 180%;
|
||||
text-decoration: none;
|
||||
color: #191919;
|
||||
font-style: italic;
|
||||
}
|
||||
.vote-btns {
|
||||
float: right;
|
||||
}
|
||||
.vote-positive, .vote-negative {
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.vote-btns form {
|
||||
display: inline-block;
|
||||
}
|
||||
.vote-positive {
|
||||
background-color: #a4ffa7;
|
||||
color: #008005;
|
||||
}
|
||||
.vote-negative {
|
||||
background-color: #ffa6a6;
|
||||
color: #ab0000;
|
||||
}
|
||||
.submission-list .submission.buried {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.message.error {
|
||||
width: fit-content;
|
||||
padding: 10px;
|
||||
background-color: #ffcaca;
|
||||
border: 1px solid #b10000;
|
||||
margin: 5px 0;
|
||||
}
|
||||
@media all and (max-width: 800px) {
|
||||
.logo {
|
||||
font-size: 5vw !important;
|
||||
|
@ -25,7 +25,7 @@
|
||||
<div class="content">
|
||||
{% if not recent %} Nothing to show {% endif %}
|
||||
{% for show in recent %}
|
||||
<a class="show-promo" href="#">
|
||||
<a class="show-promo" href="/show/{{show.abbr}}">
|
||||
<img class="artwork" src="/media/uploaded_resources/{{show.artwork}}">
|
||||
<span>{{show.name}}</span>
|
||||
</a>
|
||||
|
11
Show/forms.py
Normal file
11
Show/forms.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django import forms
|
||||
from LandingPage.models import Submission
|
||||
|
||||
class SubmissionForm(forms.ModelForm):
|
||||
class Meta():
|
||||
model = Submission
|
||||
fields = ('url','tags',)
|
||||
help_texts = {
|
||||
'url': 'URL to your episode',
|
||||
'tags': 'Describe your link. Comma-separated list of keywords'
|
||||
}
|
63
Show/templates/episode.html
Normal file
63
Show/templates/episode.html
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Episode "{{episode.name}}" - S{{episode.season.number}}E{{episode.episode}} - {{show.name}} - Episodes.Community
|
||||
{% endblock %}
|
||||
|
||||
{% block meta %}
|
||||
{{ block.super }}
|
||||
<meta property="og:title" content="{{episode.name}}">
|
||||
<meta property="og:type" content="video.episode">
|
||||
<meta property="og:video:series" content="{{show.name}}">
|
||||
<meta property="og:image" content="/media/uploaded_resources/{{show.artwork}}">
|
||||
<meta property="og:url" content="https://{{show.abbr}}.episodes.community/episode/{{episode.season.number}}/{{episode.episode}}-{{episode.name|slugify}}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="show-details">
|
||||
<div class="banner" style="background-image: url(/media/uploaded_resources/{{show.banner}});"></div>
|
||||
<div class="banner-cover">
|
||||
<div class="artwork">
|
||||
<img src="/media/uploaded_resources/{{show.artwork}}">
|
||||
</div>
|
||||
<div class="details">
|
||||
<h1>{{show.name}}</h1>
|
||||
<p class="description">
|
||||
{{show.description}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="submissions">
|
||||
<a href="/show/{{show.abbr}}" class="button"><i class="fa fa-fw fa-home"></i> Show Index</a>
|
||||
{% if request.session.user_id %}
|
||||
<a href="/show/{{show.abbr}}/episode/{{episode.season.number}}/{{episode.episode}}/submit" class="button"><i class="fa fa-fw fa-plus"></i> Submit New Link</a>
|
||||
{% else %}
|
||||
<span class="fillertext"><a href="/login">Log in</a> to submit a link</span>
|
||||
{% endif %}
|
||||
<h1>Watch <q>{{episode.name}}</q> From</h1>
|
||||
<div class="submission-list">
|
||||
{% for sbm in submissions %}
|
||||
<div class="submission{% if sbm.positives < sbm.negatives %} buried{% endif %}">
|
||||
<a href="{{sbm.url}}" class="link"><i class="fa fa-fw fa-globe"></i> {{sbm.url}}</a>
|
||||
<div class="vote-btns" data-vote-id="{{sbm.id}}">
|
||||
<form method="POST" action="/show/{{show.abbr}}/vote/{{sbm.id}}/1">
|
||||
{% csrf_token %}
|
||||
<button class="vote-positive">
|
||||
<i class="fa fa-fw fa-thumbs-up"></i> {{sbm.positives}}
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="/show/{{show.abbr}}/vote/{{sbm.id}}/0">
|
||||
{% csrf_token %}
|
||||
<button class="vote-negative">
|
||||
<i class="fa fa-fw fa-thumbs-down"></i> {{sbm.negatives}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<h3>Nobody has submitted any links yet.</h3>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
53
Show/templates/show.html
Normal file
53
Show/templates/show.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
Watch {{show.name}} Now - on Episodes.Community!
|
||||
{% endblock %}
|
||||
|
||||
{% block meta %}
|
||||
{{ block.super }}
|
||||
<meta property="og:title" content="{{show.name}}">
|
||||
<meta property="og:type" content="video.tv_show">
|
||||
<meta property="og:image" content="/media/uploaded_resources/{{show.artwork}}">
|
||||
<meta property="og:url" content="https://{{show.abbr}}.episodes.community/">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="show-details">
|
||||
<div class="banner" style="background-image: url(/media/uploaded_resources/{{show.banner}});"></div>
|
||||
<div class="banner-cover">
|
||||
<div class="artwork">
|
||||
<img src="/media/uploaded_resources/{{show.artwork}}">
|
||||
</div>
|
||||
<div class="details">
|
||||
<h1>{{show.name}}</h1>
|
||||
<p class="description">
|
||||
{{show.description}}
|
||||
</p>
|
||||
<!--<div class="data">
|
||||
<a class="imdb" href="{{show.imdb}}" target="_blank">IMDb Page</a>
|
||||
<div class="stats">
|
||||
<div class="param">Seasons</div>
|
||||
<div class="value">{{seasons|length}}</div>
|
||||
<div class="param">Episodes</div>
|
||||
<div class="value">{{episodes|length}}</div>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="seasons">
|
||||
<h1>Watch Now</h1>
|
||||
{% for season in seasons %}
|
||||
<div class="season" data-season="{{season.number}}">
|
||||
<div class="season-name">{% if season.name %}{{season.number}} - {{season.name}}{% else %}Season {{season.number}}{%endif%}</div>
|
||||
<div class="episodes">
|
||||
{% for episode in season.episodes.all %}
|
||||
<a class="episode" data-season="{{season.number}}" data-number="{{episode.episode}}" data-title="{{episode.name}}" href="episode/{{season.number}}/{{episode.episode}}-{{episode.name|slugify}}">{{episode.episode}} - {{episode.name}}
|
||||
<span class="submission_cnt">{{episode.submissions.all|length}} Available Links</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endblock %}
|
34
Show/templates/submit.html
Normal file
34
Show/templates/submit.html
Normal file
@ -0,0 +1,34 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
Submit a link - S{{episode.season.number}}E{{episode.episode}} - {{show.name}} - Episodes.Community
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section class="show-details">
|
||||
<div class="banner" style="background-image: url(/media/uploaded_resources/{{show.banner}});"></div>
|
||||
<div class="banner-cover">
|
||||
<div class="artwork">
|
||||
<img src="/media/uploaded_resources/{{show.artwork}}">
|
||||
</div>
|
||||
<div class="details">
|
||||
<h1>{{show.name}}</h1>
|
||||
<p class="description">
|
||||
{{show.description}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="submissions">
|
||||
<a href="/show/{{show.abbr}}" class="button"><i class="fa fa-fw fa-home"></i> Show Index</a>
|
||||
<a href="/show/{{show.abbr}}/episode/{{episode.season.number}}/{{episode.episode}}-{{episode.name|slugify}}" class="button">Back to Episode</a>
|
||||
<h1>Submit a link</h1>
|
||||
{% if error %}
|
||||
<div class="message error">{{error}}</div>
|
||||
{% endif %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
26
Show/urls.py
Normal file
26
Show/urls.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""Show URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/1.11/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url, include
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view()),
|
||||
url(r'^episode/(?P<season>\d{1,4})/(?P<episode>\d{1,4})(-[\w-]+)?/?$', views.EpisodeView.as_view()),
|
||||
url(r'^episode/(?P<season>\d{1,4})/(?P<episode>\d{1,4})(-[\w-]+)?/submit$', views.SubmissionForm),
|
||||
url(r'^vote/(?P<subid>\d+)/(?P<positive>[0-1])/?$', views.SubmissionVoteSubmit.as_view())
|
||||
]
|
183
Show/views.py
183
Show/views.py
@ -1,3 +1,186 @@
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import render
|
||||
from django.views import View
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.db.models import Case, When, Value, IntegerField, Count, F
|
||||
|
||||
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 . import forms
|
||||
|
||||
import datetime
|
||||
|
||||
# Create your views here.
|
||||
|
||||
# Index page of a show
|
||||
class IndexView(TemplateView):
|
||||
template_name = "show.html"
|
||||
|
||||
def get_context_data(self, abbreviation, **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 all seasons of the show and annotate episode counts onto them
|
||||
seasons = show.seasons.all()
|
||||
|
||||
# Add fields to context
|
||||
ctx['show'] = show
|
||||
ctx['seasons'] = seasons
|
||||
|
||||
return ctx
|
||||
|
||||
# Episodes page of a show
|
||||
class EpisodeView(TemplateView):
|
||||
template_name = "episode.html"
|
||||
|
||||
def get_context_data(self, abbreviation, 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")
|
||||
|
||||
# I acknowledge that this is a mess. A functional mess. But a mess nonetheless. Hey, that rhymed!
|
||||
submissions = episode.submissions.annotate(
|
||||
positives=Count(
|
||||
Case(
|
||||
When(
|
||||
votes__positive=True,
|
||||
then=Value(1)
|
||||
)
|
||||
)
|
||||
),
|
||||
negatives=Count('votes') - F('positives'),
|
||||
score=F('positives') - F('negatives')
|
||||
).order_by('-score')
|
||||
|
||||
# Add fields to context
|
||||
ctx['show'] = show
|
||||
ctx['episode'] = episode
|
||||
ctx['submissions'] = submissions
|
||||
|
||||
return ctx
|
||||
|
||||
# Submission form GET and POST
|
||||
def SubmissionForm(req, abbreviation, season, episode):
|
||||
show = Show.objects.get(abbr=abbreviation)
|
||||
episode = Episode.objects.filter(show=show,season__number=season,episode=episode).first()
|
||||
|
||||
# Check for login status
|
||||
if not 'user_id' in req.session:
|
||||
return HttpResponse('<h1>Error</h1><p>You need to be logged in to submit. Please <a href=/login>log in</a></p>', status=400)
|
||||
|
||||
user = User.objects.get(user_id=req.session['user_id'])
|
||||
|
||||
# 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
|
||||
ctx = {
|
||||
'form': form,
|
||||
'show': show,
|
||||
'episode': episode
|
||||
}
|
||||
|
||||
# Handle POST
|
||||
if req.method == 'POST':
|
||||
form = forms.SubmissionForm(req.POST)
|
||||
ctx['form'] = form
|
||||
|
||||
if form.is_valid():
|
||||
form_data = form.cleaned_data
|
||||
|
||||
# Check if the URL has already been submitted
|
||||
if Submission.objects.filter(episode=episode,url=form_data['url']).count() > 0:
|
||||
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)
|
||||
|
||||
# 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))
|
||||
else:
|
||||
ctx['error'] = 'Invalid fields!'
|
||||
|
||||
return render(req, "submit.html", ctx)
|
||||
|
||||
# Vote request
|
||||
# /show/{{abbr}}/vote/{{submission id}}/{{positive == 1}}
|
||||
class SubmissionVoteSubmit(View):
|
||||
def post (self, req, abbreviation, subid, positive):
|
||||
# Convert positive parameter into a boolean
|
||||
pos_bool = int(positive) == 1
|
||||
|
||||
# Check for login status
|
||||
if not 'user_id' in req.session:
|
||||
return HttpResponse('<h1>Error</h1><p>You need to be logged in to vote. Please <a href=/login>log in</a></p>', status=400)
|
||||
|
||||
user = User.objects.get(user_id=req.session['user_id'])
|
||||
|
||||
# Get the submission from the database
|
||||
submission = Submission.objects.filter(id=subid).first()
|
||||
|
||||
# 404
|
||||
if not submission:
|
||||
raise Http404("Submission does not exist")
|
||||
|
||||
# Prevent voting for own submission
|
||||
if submission.user == user:
|
||||
return HttpResponse('<h1>Error</h1><p>You cannot vote for your own submission.</p>', status=400)
|
||||
|
||||
# Allow changing a vote from positive to negative or vice-versa. Delete vote if its a re-vote
|
||||
vote = submission.votes.filter(user=user,submission__id=submission.id).first()
|
||||
if vote:
|
||||
if not vote.positive == pos_bool:
|
||||
vote.positive = pos_bool
|
||||
vote.save()
|
||||
else:
|
||||
vote.delete()
|
||||
else:
|
||||
new_vote = SubmissionVote(
|
||||
user=user,
|
||||
submission=submission,
|
||||
positive=pos_bool
|
||||
)
|
||||
new_vote.save()
|
||||
|
||||
return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbreviation, submission.episode.season.number, submission.episode.episode))
|
||||
|
||||
|
Reference in New Issue
Block a user