diff --git a/Discussions/tests.py b/Discussions/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/Discussions/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/LandingPage/models.py b/LandingPage/models.py index 212a8f4..e03a920 100644 --- a/LandingPage/models.py +++ b/LandingPage/models.py @@ -68,6 +68,8 @@ class Show(TimestampedModel): help_text="A banner used for the show's page.", verbose_name="Artwork" ) + def __str__(self): + return '%s [%s]'%(self.name,self.abbr) class User(TimestampedModel): user_id = models.CharField( @@ -92,6 +94,8 @@ class User(TimestampedModel): related_name='watched_by', through='Watch' ) + def __str__(self): + return self.email class Admin(User): pass @@ -136,6 +140,8 @@ class Ban(TimestampedModel): help_text='If checked, this is a site-wide ban, and the user is automatically banned from all shows, not just those in the Banned From (scope) paramenter', verbose_name = 'Site Wide Ban' ) + def __str__(self): + return ("Permanent" if self.permanent else "Temporary") + " ban of %s"%self.user class ShowModerator(TimestampedModel): show = models.ForeignKey( @@ -160,6 +166,8 @@ class ShowModerator(TimestampedModel): help_text='The user who appointed this moderator', verbose_name='Appointed by' ) + def __str__(self): + return "%s on %s"%(self.user,self.show.abbr) class Report(TimestampedModel): reporter = models.ForeignKey( @@ -184,6 +192,8 @@ class Report(TimestampedModel): help_text='The URL of the content being reported', verbose_name = 'Content URL' ) + def __str__(self): + return "%s's report of %s"%(self.reporter, self.url) class ShowSubmission(TimestampedModel): user = models.ForeignKey( @@ -193,11 +203,17 @@ class ShowSubmission(TimestampedModel): help_text='The user who submitted this show', verbose_name='Submitter' ) - name = Show.name + name = models.CharField( + max_length=40, + help_text="The full name of the show", + verbose_name="Full Name" + ) details = models.TextField( help_text='Some details about the show. Why it should be added, where information about it can be found, etc.', verbose_name='Details' ) + def __str__(self): + return '"%s" by %s'%(self.name, self.user) class Season(models.Model): show = models.ForeignKey( @@ -225,6 +241,8 @@ class Season(models.Model): verbose_name="Artwork", blank=True ) + def __str__(self): + return self.show.name + " S%d"%self.number class Episode(models.Model): show = models.ForeignKey( @@ -246,7 +264,7 @@ class Episode(models.Model): name = models.CharField( max_length=40, help_text='The name given to this episode by its producers', - verbose_name='Episode Season' + verbose_name='Episode Name' ) summary = models.TextField( help_text='A summary of this episode' @@ -255,6 +273,8 @@ class Episode(models.Model): help_text='The date this episode officially aired for the first time', verbose_name='Original Air Date' ) + def __str__(self): + return "[s%dep%d] %s — %s"%(self.season.number,self.episode,self.show.name, self.name) class Submission(TimestampedModel): episode = models.ForeignKey( @@ -278,6 +298,8 @@ class Submission(TimestampedModel): help_text='Tags applied to this link submission', max_length=200 ) + def __str__(self): + return '%s\'s submission for %s — s%dep%d'%(self.user,self.episode.show.name,self.episode.season.number, self.episode.episode) class SubmissionVote(TimestampedModel): submission = models.ForeignKey( @@ -295,6 +317,8 @@ class SubmissionVote(TimestampedModel): positive = models.BooleanField( help_text='If this is true, the vote is an upvote. Otherwise, it is a downvote' ) + def __str__(self): + return "%s's vote on %s"%(self.user,self.submission) class Favorite(TimestampedModel): user = models.ForeignKey( @@ -305,6 +329,8 @@ class Favorite(TimestampedModel): Episode, on_delete=models.CASCADE ) + def __str__(self): + return "%s \u2665 %s"%(self.user, self.episode) class Watch(TimestampedModel): user = models.ForeignKey( @@ -315,6 +341,8 @@ class Watch(TimestampedModel): Episode, on_delete=models.CASCADE ) + def __str__(self): + return "%s \U0001f441 %s"%(self.user, self.episode) class DiscussionBoard(TimestampedModel): show = models.ForeignKey( @@ -338,6 +366,8 @@ class DiscussionBoard(TimestampedModel): help_text='The body of the post', verbose_name='Body' ) + def __str__(self): + return '[%s] "%s" by %s'%(self.show.abbr, self.title, self.user) class DiscussionReply(TimestampedModel): board = models.ForeignKey( @@ -357,6 +387,8 @@ class DiscussionReply(TimestampedModel): help_text='The body of the response', verbose_name='Body' ) + def __str__(self): + return '[%s] %s\'s response to "%s"'%(self.board.show.abbr,self.user, self.board.title) class DiscussionVote(TimestampedModel): user = models.ForeignKey( @@ -371,6 +403,8 @@ class DiscussionVote(TimestampedModel): related_name='votes', help_text='The board this vote was cast on' ) - postive = models.BooleanField( + positive = models.BooleanField( help_text='If true, the vote is an upvote. Otherwise, it is a downvote. Neutral votes are not recorded' ) + def __str__(self): + return "%s %s %s"%(self.user, '\U0001f592' if self.positive else '\U0001f44e', self.board.title) diff --git a/LandingPage/tests.py b/LandingPage/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/LandingPage/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/LandingPage/views.py b/LandingPage/views.py index c847595..84eeb7f 100644 --- a/LandingPage/views.py +++ b/LandingPage/views.py @@ -55,6 +55,7 @@ class LoginRedirect(View): ).json() req.session['user_id'] = user_info['uuid'] matches = User.objects.filter(user_id=user_info['uuid']) + match = None if not len(matches): user = User( user_id = user_info['uuid'], @@ -62,7 +63,11 @@ class LoginRedirect(View): display_name = user_info['display_name'] ) user.save() + match = user + else: + match = matches[0] req.session['token'] = resp_json['access_token'] + req.session['disp_name'] = match.display_name return HttpResponseRedirect('/') else: return HttpResponse('

Unmatching state tokens


It looks like the request to login wasn\'t started by you. Try going back to the home page and logging in again.

', status=400) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1593eea --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Episodes.Community +## Overview +Episodes.Community is an in-development website for the discussion and viewing of any and every television show. An index of episodes is maintained for each show. Users are able to submit a link to a streaming location for an episode. This link is then voted on by other users, with the resulting score determining that link's priority. + +## Planned Features + * Each show given a subdomain that can be used as an alternative to the url + * Tags can be set by users on link submissions, and links can be filtered by tag + * A system by which content can be flagged by users for admin/moderators to check + * User moderators for shows + * An api for automation of link submission + +See a detailed draft [here](https://docs.google.com/document/d/1VI-wDvCF-3qvyC7tVX0HwruII93UDUJvFjTIfrH4fZI/edit?usp=sharing) + +## Installation +0. Install prerequisites + * [python3](https://www.python.org/) + * [pip](https://pip.pypa.io/en/stable/installing/) (or manually install all python deps in the [requirements][requirements.txt] file) + * Some kind of database server. Any kind [supported by Django](https://docs.djangoproject.com/en/1.11/ref/databases/) will work. You can use a third-party database as well, but you are responsible for [configuring Django](https://docs.djangoproject.com/en/1.11/ref/databases/#using-a-3rd-party-database-backend), and should not expect help from the community. + * [gunicorn](http://gunicorn.org/) (for production) +1. Clone the repository + ``` + $ git clone https://github.com/IcyNet/IcyNet.eu.git + $ cd IcyNet.eu + ``` +2. Install requirements. If you're using pip (recomended), use + ``` + $ sudo pip install -r requirements.txt + ``` + if installing as root, or + ``` + $ pip install -r requirements.txt --user + ``` + if installing for your user only. +3. If you're not using a full release, you'll need to generate the migration instructions. If you are using a full release, you can skip this step. + ``` + $ python3 manage.py makemigrations + ``` +4. Copy the config file, and make any needed changes + ``` + $ cp options_example.ini options.ini + $ $EDITOR options.ini + ``` +5. Setup the database + ``` + $ python3 manage.py migrate + ``` +6. Run the server. For development purposes, you can use + ``` + $ python3 manage.py runserver + ``` + For production, run + ``` + $ gunicorn EpisodesCommunity.wsgi + ``` +## Contributing +If you want to contribute, we'd love your help. You can get in contact with @LunaSquee or @Tsa6, or just start in on anything on the [issues list](https://github.com/IcyNet/Episodes.Community/issues) that hasn't already been assigned. We do ask that you follow the [GitHub Workflow](https://guides.github.com/introduction/flow/) diff --git a/Show/tests.py b/Show/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/Show/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/tests/LandingPage/__init__.py b/tests/LandingPage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/LandingPage/test_views.py b/tests/LandingPage/test_views.py new file mode 100644 index 0000000..4615965 --- /dev/null +++ b/tests/LandingPage/test_views.py @@ -0,0 +1,96 @@ +from django.test import TestCase,Client,override_settings +import responses +from LandingPage.models import User +from urllib import parse + +@override_settings( + AUTH_TOKEN_ENDPOINT='http://icynet.test/api/', + AUTH_CLIENT_ID='clid', + AUTH_B64='Y2xpZDpjbGlzZWM=', + AUTH_REDIRECT_URL='http://redirect.test' +) +class TestLogin(TestCase): + + def test_login_new_user(self): + # Set up responses to control network flow + with responses.RequestsMock() as rm: + rm.add(responses.POST,'http://icynet.test/api/token',json={'access_token':'1accesstoken1'}) + rm.add(responses.GET,'http://icynet.test/api/user',json={'uuid':'935a41b5-b38d-42c3-96ef-653402fc44ca','email':'johnsmith@gmail.com','display_name':'Mr. Smith'}) + + # Make initial request to redirect endpoint + client = Client() + resp = client.get('/login') + self.assertEqual(resp.status_code, 302) + query = parse.parse_qs(parse.urlparse(resp['Location']).query) + state = query['state'][0] + self.assertEqual(query['client_id'][0],'clid') + self.assertEqual(query['response_type'][0],'code') + self.assertEqual(query['redirect_uri'][0],'http://redirect.test') + self.assertEqual(query['scope'][0],'email') + + # Make connection to the real endpoint + resp = client.get('/login/redirect?state=%s&code=%s'%(state, 'code')) + self.assertEqual(resp.status_code, 302) + + # Check that the database is all good + users = User.objects.all() + self.assertEqual(len(users), 1) + user = users[0] + self.assertEqual(user.user_id,'935a41b5-b38d-42c3-96ef-653402fc44ca') + self.assertEqual(user.email,'johnsmith@gmail.com') + self.assertEqual(user.display_name, 'Mr. Smith') + + # Check appropriate values are in the session + self.assertEqual(client.session['user_id'], '935a41b5-b38d-42c3-96ef-653402fc44ca') + self.assertEqual(client.session['token'],'1accesstoken1') + self.assertEqual(client.session['disp_name'], 'Mr. Smith') + + def test_reject_bad_state(self): + with responses.RequestsMock() as rm: + client = Client() + resp = client.get('/login/redirect?state=%s&code=%s'%('bad_state', 'code')) + self.assertEqual(resp.status_code, 400) + + def test_login_old_user(self): + # Set up responses to control network flow + with responses.RequestsMock() as rm: + rm.add(responses.POST,'http://icynet.test/api/token',json={'access_token':'1accesstoken1'}) + rm.add(responses.GET,'http://icynet.test/api/user',json={'uuid':'935a41b5-b38d-42c3-96ef-653402fc44ca','email':'johnsmith@gmail.com','display_name':'Mr. Smith'}) + + # Set up the database + user = User(user_id='935a41b5-b38d-42c3-96ef-653402fc44ca',email='johnsmith@gmail.com',display_name='Mr. Smith') + user.save() + + # Make initial request to redirect endpoint + client = Client() + resp = client.get('/login') + state = parse.parse_qs(parse.urlparse(resp['Location']).query)['state'][0] + + # Make connection to the real endpoint + resp = client.get('/login/redirect?state=%s&code=%s'%(state, 'code')) + self.assertEqual(resp.status_code, 302) + + # Check that the database is all good + users = User.objects.all() + self.assertEqual(len(users), 1) + user = users[0] + self.assertEqual(user.user_id,'935a41b5-b38d-42c3-96ef-653402fc44ca') + self.assertEqual(user.email,'johnsmith@gmail.com') + self.assertEqual(user.display_name, 'Mr. Smith') + + # Check appropriate values are in the session + self.assertEqual(client.session['user_id'], '935a41b5-b38d-42c3-96ef-653402fc44ca') + self.assertEqual(client.session['token'],'1accesstoken1') + self.assertEqual(client.session['disp_name'], 'Mr. Smith') + + def test_states_unique(self): + with responses.RequestsMock() as rm: + client1 = Client() + resp1 = client1.get('/login') + state1 = parse.parse_qs(parse.urlparse(resp1['Location']).query)['state'][0] + + client2 = Client() + resp2 = client2.get('/login') + state2 = parse.parse_qs(parse.urlparse(resp2['Location']).query)['state'][0] + + self.assertNotEqual(state1,state2) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29