diff --git a/CHANGELOG.md b/CHANGELOG.md index 98fbf1c..4d567f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.3.9 + +- Cursor based pagination +- Updated django version + ## 0.3.8 - Update light / dark theme diff --git a/package.json b/package.json index dbd4d56..7c07ae9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "newsreader", - "version": "0.1.0", + "version": "0.3.9", "description": "Application for viewing RSS feeds", "main": "index.js", "scripts": { diff --git a/poetry.lock b/poetry.lock index be2b3af..baba780 100644 --- a/poetry.lock +++ b/poetry.lock @@ -37,10 +37,10 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] name = "autoflake" @@ -61,15 +61,13 @@ category = "main" optional = false python-versions = "*" +[package.dependencies] +soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} + [package.extras] html5lib = ["html5lib"] lxml = ["lxml"] -[package.dependencies] -[package.dependencies.soupsieve] -version = ">1.2" -python = ">=3.0" - [[package]] name = "billiard" version = "3.6.3.0" @@ -86,15 +84,15 @@ category = "dev" optional = false python-versions = ">=3.6" -[package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] - [package.dependencies] appdirs = "*" attrs = ">=18.1.0" click = ">=6.5" toml = ">=0.9.4" +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + [[package]] name = "bleach" version = "3.2.1" @@ -116,14 +114,20 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[package.dependencies] +billiard = ">=3.6.3.0,<4.0" +kombu = ">=4.6.10,<4.7" +pytz = ">0.0-dev" +vine = "1.3.0" + [package.extras] arangodb = ["pyArango (>=1.3.2)"] auth = ["cryptography"] -azureblockblob = ["azure-storage (0.36.0)", "azure-common (1.1.5)", "azure-storage-common (1.1.0)"] +azureblockblob = ["azure-storage (==0.36.0)", "azure-common (==1.1.5)", "azure-storage-common (==1.1.0)"] brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] cassandra = ["cassandra-driver (<3.21.0)"] consul = ["python-consul"] -cosmosdbsql = ["pydocumentdb (2.3.2)"] +cosmosdbsql = ["pydocumentdb (==2.3.2)"] couchbase = ["couchbase-cffi (<3.0.0)", "couchbase (<3.0.0)"] couchdb = ["pycouchdb"] django = ["Django (>=1.11)"] @@ -134,7 +138,7 @@ gevent = ["gevent"] librabbitmq = ["librabbitmq (>=1.5.0)"] lzma = ["backports.lzma"] memcache = ["pylibmc"] -mongodb = ["pymongo (>=3.3.0)"] +mongodb = ["pymongo[srv] (>=3.3.0)"] msgpack = ["msgpack"] pymemcache = ["python-memcached"] pyro = ["pyro4"] @@ -144,18 +148,12 @@ s3 = ["boto3 (>=1.9.125)"] slmq = ["softlayer-messaging (>=1.0.3)"] solar = ["ephem"] sqlalchemy = ["sqlalchemy"] -sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.5)"] +sqs = ["boto3 (>=1.9.125)", "pycurl (==7.43.0.5)"] tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] zstd = ["zstandard"] -[package.dependencies] -billiard = ">=3.6.3.0,<4.0" -kombu = ">=4.6.10,<4.7" -pytz = ">0.0-dev" -vine = "1.3.0" - [[package]] name = "certifi" version = "2020.12.5" @@ -218,21 +216,21 @@ toml = ["toml"] [[package]] name = "django" -version = "3.1.5" +version = "3.1.6" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false python-versions = ">=3.6" -[package.extras] -argon2 = ["argon2-cffi (>=16.1.0)"] -bcrypt = ["bcrypt"] - [package.dependencies] asgiref = ">=3.2.10,<4" pytz = "*" sqlparse = ">=0.2.2" +[package.extras] +argon2 = ["argon2-cffi (>=16.1.0)"] +bcrypt = ["bcrypt"] + [[package]] name = "django-axes" version = "5.12.0" @@ -306,13 +304,13 @@ category = "main" optional = false python-versions = ">=3.5" -[package.extras] -rest_framework = ["djangorestframework (>=3.0.0)"] - [package.dependencies] django = ">=2.2" pytz = "*" +[package.extras] +rest_framework = ["djangorestframework (>=3.0.0)"] + [[package]] name = "djangorestframework" version = "3.12.2" @@ -332,9 +330,6 @@ category = "main" optional = false python-versions = ">=3.6" -[package.extras] -validation = ["swagger-spec-validator (>=2.1.0)"] - [package.dependencies] coreapi = ">=2.3.3" coreschema = ">=0.0.4" @@ -345,6 +340,9 @@ packaging = "*" "ruamel.yaml" = ">=0.15.34" uritemplate = ">=3.0.0" +[package.extras] +validation = ["swagger-spec-validator (>=2.1.0)"] + [[package]] name = "factory-boy" version = "2.12.0" @@ -413,9 +411,6 @@ gevent = ["gevent (>=0.13)"] setproctitle = ["setproctitle"] tornado = ["tornado (>=0.2)"] -[package.dependencies] -setuptools = ">=3.0" - [[package]] name = "idna" version = "2.10" @@ -431,18 +426,14 @@ description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.6" -marker = "python_version < \"3.8\"" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] - -[package.dependencies] -zipp = ">=0.5" - -[package.dependencies.typing-extensions] -version = ">=3.6.4" -python = "<3.8" +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "inflection" @@ -482,12 +473,12 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[package.extras] -i18n = ["Babel (>=0.8)"] - [package.dependencies] MarkupSafe = ">=0.23" +[package.extras] +i18n = ["Babel (>=0.8)"] + [[package]] name = "kombu" version = "4.6.11" @@ -496,6 +487,10 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[package.dependencies] +amqp = ">=2.6.0,<2.7" +importlib-metadata = {version = ">=0.18", markers = "python_version < \"3.8\""} + [package.extras] azureservicebus = ["azure-servicebus (>=0.21.1)"] azurestoragequeues = ["azure-storage-queue"] @@ -508,17 +503,10 @@ qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] redis = ["redis (>=3.3.11)"] slmq = ["softlayer-messaging (>=1.0.3)"] sqlalchemy = ["sqlalchemy"] -sqs = ["boto3 (>=1.4.4)", "pycurl (7.43.0.2)"] +sqs = ["boto3 (>=1.4.4)", "pycurl (==7.43.0.2)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] -[package.dependencies] -amqp = ">=2.6.0,<2.7" - -[package.dependencies.importlib-metadata] -version = ">=0.18" -python = "<3.8" - [[package]] name = "lxml" version = "4.6.2" @@ -597,13 +585,13 @@ category = "main" optional = false python-versions = "*" +[package.dependencies] +python-dateutil = "*" + [package.extras] cron-description = ["cron-descriptor"] cron-schedule = ["croniter"] -[package.dependencies] -python-dateutil = "*" - [[package]] name = "python-dateutil" version = "2.8.1" @@ -653,16 +641,16 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - [package.dependencies] certifi = ">=2017.4.17" chardet = ">=3.0.2,<5" idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + [[package]] name = "requests-oauthlib" version = "1.3.0" @@ -671,13 +659,13 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[package.extras] -rsa = ["oauthlib (>=3.0.0)"] - [package.dependencies] oauthlib = ">=3.0.0" requests = ">=2.0.0" +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + [[package]] name = "ruamel.yaml" version = "0.16.12" @@ -686,15 +674,13 @@ category = "main" optional = false python-versions = "*" +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.9\""} + [package.extras] docs = ["ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] -[package.dependencies] -[package.dependencies."ruamel.yaml.clib"] -version = ">=0.1.2" -python = "<3.9" - [[package]] name = "ruamel.yaml.clib" version = "0.2.2" @@ -702,7 +688,6 @@ description = "C version of reader, parser and emitter for ruamel.yaml derived f category = "main" optional = false python-versions = "*" -marker = "platform_python_implementation == \"CPython\" and python_version < \"3.9\"" [[package]] name = "sentry-sdk" @@ -712,6 +697,10 @@ category = "main" optional = true python-versions = "*" +[package.dependencies] +certifi = "*" +urllib3 = ">=1.10.0" + [package.extras] aiohttp = ["aiohttp (>=3.5)"] beam = ["beam (>=2.12)"] @@ -726,10 +715,6 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] -[package.dependencies] -certifi = "*" -urllib3 = ">=1.10.0" - [[package]] name = "six" version = "1.15.0" @@ -745,7 +730,6 @@ description = "A modern CSS selector implementation for Beautiful Soup." category = "main" optional = false python-versions = ">=3.5" -marker = "python_version >= \"3.0\"" [[package]] name = "sqlparse" @@ -786,7 +770,6 @@ description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false python-versions = "*" -marker = "python_version < \"3.8\"" [[package]] name = "uritemplate" @@ -807,7 +790,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "vine" @@ -840,17 +823,16 @@ description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.6" -marker = "python_version < \"3.8\"" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] sentry = ["sentry-sdk"] [metadata] -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.7" content-hash = "051ae963128801a760a39a286257ec7d4faa8f1d8a47739fba80fdae6450002b" @@ -967,8 +949,8 @@ coverage = [ {file = "coverage-5.3.1.tar.gz", hash = "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b"}, ] django = [ - {file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"}, - {file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"}, + {file = "Django-3.1.6-py3-none-any.whl", hash = "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f"}, + {file = "Django-3.1.6.tar.gz", hash = "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"}, ] django-axes = [ {file = "django-axes-5.12.0.tar.gz", hash = "sha256:c26167f7ca2003df8358eb23537dffb1d97bd9f44ccef70d5c64a7aba2349456"}, diff --git a/pyproject.toml b/pyproject.toml index 0936690..b13c72d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "newsreader" -version = "0.2" +version = "0.3.9" description = "Webapplication for reading RSS feeds" authors = ["Sonny "] license = "GPL-3.0" diff --git a/src/newsreader/core/pagination.py b/src/newsreader/core/pagination.py index 5e19771..b44c862 100644 --- a/src/newsreader/core/pagination.py +++ b/src/newsreader/core/pagination.py @@ -1,7 +1,7 @@ -from rest_framework.pagination import PageNumberPagination +from rest_framework import pagination -class ResultSetPagination(PageNumberPagination): +class ResultSetPagination(pagination.PageNumberPagination): page_size_query_param = "count" max_page_size = 50 page_size = 30 @@ -10,3 +10,9 @@ class ResultSetPagination(PageNumberPagination): class LargeResultSetPagination(ResultSetPagination): max_page_size = 100 page_size = 50 + + +class CursorPagination(pagination.CursorPagination): + page_size_query_param = "count" + ordering = "-publication_date" + page_size = 30 diff --git a/src/newsreader/js/pages/homepage/actions/posts.js b/src/newsreader/js/pages/homepage/actions/posts.js index f04f3e1..826512f 100644 --- a/src/newsreader/js/pages/homepage/actions/posts.js +++ b/src/newsreader/js/pages/homepage/actions/posts.js @@ -64,7 +64,7 @@ export const markPostRead = (post, token) => { }; }; -export const fetchPostsBySection = (section, page = false) => { +export const fetchPostsBySection = (section, next = false) => { return dispatch => { if (section.unread === 0) { return; @@ -76,10 +76,10 @@ export const fetchPostsBySection = (section, page = false) => { switch (section.type) { case RULE_TYPE: - url = page ? page : `/api/rules/${section.id}/posts/?read=false`; + url = next ? next : `/api/rules/${section.id}/posts/?read=false`; break; case CATEGORY_TYPE: - url = page ? page : `/api/categories/${section.id}/posts/?read=false`; + url = next ? next : `/api/categories/${section.id}/posts/?read=false`; break; } diff --git a/src/newsreader/js/pages/homepage/components/postlist/PostList.js b/src/newsreader/js/pages/homepage/components/postlist/PostList.js index 66e3b7f..282300b 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/PostList.js +++ b/src/newsreader/js/pages/homepage/components/postlist/PostList.js @@ -90,7 +90,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ - fetchPostsBySection: (rule, page = false) => dispatch(fetchPostsBySection(rule, page)), + fetchPostsBySection: (rule, next = false) => dispatch(fetchPostsBySection(rule, next)), }); export default connect(mapStateToProps, mapDispatchToProps)(PostList); diff --git a/src/newsreader/js/pages/homepage/index.js b/src/newsreader/js/pages/homepage/index.js index b934ad3..86cdd09 100644 --- a/src/newsreader/js/pages/homepage/index.js +++ b/src/newsreader/js/pages/homepage/index.js @@ -14,7 +14,7 @@ if (page) { const settings = JSON.parse(document.getElementById('homepageSettings').textContent); const { feedUrl, subredditUrl, timelineUrl, categoriesUrl } = settings; - ReactDOM.render( + const app = ( - , - page + ); + + ReactDOM.render(app, page); } diff --git a/src/newsreader/news/collection/endpoints.py b/src/newsreader/news/collection/endpoints.py index 7f2ede0..623283d 100644 --- a/src/newsreader/news/collection/endpoints.py +++ b/src/newsreader/news/collection/endpoints.py @@ -2,12 +2,16 @@ from rest_framework import status from rest_framework.generics import ( GenericAPIView, ListAPIView, - RetrieveUpdateDestroyAPIView, + RetrieveUpdateAPIView, get_object_or_404, ) from rest_framework.response import Response -from newsreader.core.pagination import LargeResultSetPagination, ResultSetPagination +from newsreader.core.pagination import ( + CursorPagination, + LargeResultSetPagination, + ResultSetPagination, +) from newsreader.news.collection.models import CollectionRule from newsreader.news.collection.serializers import RuleSerializer from newsreader.news.core.filters import ReadFilter @@ -15,26 +19,15 @@ from newsreader.news.core.models import Post from newsreader.news.core.serializers import PostSerializer -class ListRuleView(ListAPIView): +class DetailRuleView(RetrieveUpdateAPIView): queryset = CollectionRule.objects.all() serializer_class = RuleSerializer - pagination_class = ResultSetPagination - - def get_queryset(self): - user = self.request.user - return self.queryset.filter(user=user).order_by("-created") - - -class DetailRuleView(RetrieveUpdateDestroyAPIView): - queryset = CollectionRule.objects.all() - serializer_class = RuleSerializer - pagination_class = ResultSetPagination class NestedRuleView(ListAPIView): queryset = CollectionRule.objects.prefetch_related("posts").all() serializer_class = PostSerializer - pagination_class = LargeResultSetPagination + pagination_class = CursorPagination filter_backends = [ReadFilter] def get_queryset(self): diff --git a/src/newsreader/news/collection/tests/endpoints/rule/detail/tests.py b/src/newsreader/news/collection/tests/endpoints/rule/detail/tests.py index 8dfe6ed..0e1bc7e 100644 --- a/src/newsreader/news/collection/tests/endpoints/rule/detail/tests.py +++ b/src/newsreader/news/collection/tests/endpoints/rule/detail/tests.py @@ -121,15 +121,6 @@ class CollectionRuleDetailViewTestCase(TestCase): self.assertEquals(response.status_code, 200) self.assertEquals(data["name"], "BBC") - def test_delete(self): - rule = FeedFactory(user=self.user) - - response = self.client.delete( - reverse("api:news:collection:rules-detail", args=[rule.pk]) - ) - - self.assertEquals(response.status_code, 204) - def test_rule_with_unauthenticated_user(self): self.client.logout() diff --git a/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py b/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py index 44e3eaa..5a34dbc 100644 --- a/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py +++ b/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py @@ -12,137 +12,6 @@ from newsreader.news.collection.tests.factories import FeedFactory from newsreader.news.core.tests.factories import CategoryFactory, FeedPostFactory -class RuleListViewTestCase(TestCase): - def setUp(self): - self.user = UserFactory(password="test") - self.client.force_login(self.user) - - def test_simple(self): - FeedFactory.create_batch(size=3, user=self.user) - - response = self.client.get(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 3) - - def test_ordering(self): - rules = [ - FeedFactory( - created=datetime.combine( - date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc - ), - user=self.user, - ), - FeedFactory( - created=datetime.combine( - date(2019, 7, 20), time(hour=18, minute=7, second=37), pytz.utc - ), - user=self.user, - ), - FeedFactory( - created=datetime.combine( - date(2019, 7, 20), time(hour=16, minute=7, second=37), pytz.utc - ), - user=self.user, - ), - ] - - response = self.client.get(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 3) - - self.assertEquals(data["results"][0]["id"], rules[1].pk) - self.assertEquals(data["results"][1]["id"], rules[2].pk) - self.assertEquals(data["results"][2]["id"], rules[0].pk) - - def test_pagination_count(self): - FeedFactory.create_batch(size=80, user=self.user) - - response = self.client.get( - reverse("api:news:collection:rules-list"), {"count": 30} - ) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 80) - self.assertEquals(len(data["results"]), 30) - - def test_empty(self): - response = self.client.get(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - - self.assertEquals(data["count"], 0) - self.assertEquals(len(data["results"]), 0) - - def test_post(self): - category = CategoryFactory(user=self.user) - - data = {"name": "BBC", "url": "https://www.bbc.co.uk", "category": category.pk} - - response = self.client.post( - reverse("api:news:collection:rules-list"), - data=json.dumps(data), - content_type="application/json", - ) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "POST" not allowed.') - - def test_patch(self): - response = self.client.patch(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PATCH" not allowed.') - - def test_put(self): - response = self.client.put(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PUT" not allowed.') - - def test_delete(self): - response = self.client.delete(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "DELETE" not allowed.') - - def test_rules_with_unauthenticated_user(self): - self.client.logout() - - FeedFactory.create_batch(size=3, user=self.user) - - response = self.client.get(reverse("api:news:collection:rules-list")) - - self.assertEquals(response.status_code, 403) - - def test_rules_with_unauthorized_user(self): - other_user = UserFactory() - FeedFactory.create_batch(size=3, user=other_user) - - response = self.client.get(reverse("api:news:collection:rules-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - - self.assertEquals(data["count"], 0) - self.assertEquals(len(data["results"]), 0) - - class NestedRuleListViewTestCase(TestCase): def setUp(self): self.user = UserFactory(password="test") @@ -157,11 +26,10 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 5) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 5) + self.assertEqual(data["next"], None) + self.assertEqual(data["previous"], None) def test_pagination(self): rule = FeedFactory.create(user=self.user) @@ -178,11 +46,12 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 80) - self.assertEquals(len(data["results"]), 30) + self.assertEqual(response.status_code, 200) + self.assertTrue(data["next"]) + self.assertFalse(data["previous"]) - self.assertEquals( + self.assertEqual(len(data["results"]), 30) + self.assertEqual( [post["id"] for post in data["results"]], [post.id for post in posts[:30]] ) @@ -194,16 +63,15 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 0) - self.assertEquals(len(data["results"]), 0) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 0) def test_not_known(self): response = self.client.get( reverse("api:news:collection:rules-nested-posts", kwargs={"pk": 0}) ) - self.assertEquals(response.status_code, 404) + self.assertEqual(response.status_code, 404) def test_post(self): rule = FeedFactory.create(user=self.user) @@ -215,8 +83,8 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "POST" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "POST" not allowed.') def test_patch(self): rule = FeedFactory.create(user=self.user) @@ -228,8 +96,8 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PATCH" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PATCH" not allowed.') def test_put(self): rule = FeedFactory.create(user=self.user) @@ -241,8 +109,8 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PUT" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PUT" not allowed.') def test_delete(self): rule = FeedFactory.create(user=self.user) @@ -254,8 +122,8 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "DELETE" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "DELETE" not allowed.') def test_rule_with_unauthenticated_user(self): self.client.logout() @@ -266,7 +134,7 @@ class NestedRuleListViewTestCase(TestCase): reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}) ) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_rule_with_unauthorized_user(self): other_user = UserFactory() @@ -276,7 +144,7 @@ class NestedRuleListViewTestCase(TestCase): reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}) ) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_posts_ordering(self): rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) @@ -310,14 +178,12 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 3) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 3) - self.assertEquals(data["results"][0]["id"], posts[1].pk) - self.assertEquals(data["results"][1]["id"], posts[2].pk) - self.assertEquals(data["results"][2]["id"], posts[0].pk) + self.assertEqual(data["results"][0]["id"], posts[1].pk) + self.assertEqual(data["results"][1]["id"], posts[2].pk) + self.assertEqual(data["results"][2]["id"], posts[0].pk) def test_only_posts_from_rule_are_returned(self): rule = FeedFactory.create(user=self.user) @@ -331,14 +197,12 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 5) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 5) for post in data["results"]: - self.assertEquals(post["rule"], rule.pk) + with self.subTest(post=post): + self.assertEqual(post["rule"], rule.pk) def test_unread_posts(self): rule = FeedFactory.create(user=self.user) @@ -352,13 +216,13 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 10) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 10) - for post in posts: - self.assertEquals(post["read"], False) + for post in data["results"]: + with self.subTest(post=post): + self.assertEqual(post["read"], False) def test_read_posts(self): rule = FeedFactory.create(user=self.user) @@ -372,10 +236,10 @@ class NestedRuleListViewTestCase(TestCase): ) data = response.json() - posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 10) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 10) - for post in posts: - self.assertEquals(post["read"], True) + for post in data["results"]: + with self.subTest(post=post): + self.assertEqual(post["read"], True) diff --git a/src/newsreader/news/collection/urls.py b/src/newsreader/news/collection/urls.py index 7d883f2..e5276cb 100644 --- a/src/newsreader/news/collection/urls.py +++ b/src/newsreader/news/collection/urls.py @@ -3,7 +3,6 @@ from django.urls import path from newsreader.news.collection.endpoints import ( DetailRuleView, - ListRuleView, NestedRuleView, RuleReadView, ) @@ -26,7 +25,6 @@ endpoints = [ path("rules//", DetailRuleView.as_view(), name="rules-detail"), path("rules//posts/", NestedRuleView.as_view(), name="rules-nested-posts"), path("rules//read/", RuleReadView.as_view(), name="rules-read"), - path("rules/", ListRuleView.as_view(), name="rules-list"), ] urlpatterns = [ diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index f5a48bc..ab47cca 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -1,5 +1,3 @@ -from django.db.models import Q - from rest_framework import status from rest_framework.generics import ( GenericAPIView, @@ -13,30 +11,13 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from newsreader.accounts.permissions import IsPostOwner -from newsreader.core.pagination import LargeResultSetPagination +from newsreader.core.pagination import CursorPagination from newsreader.news.collection.serializers import RuleSerializer from newsreader.news.core.filters import ReadFilter from newsreader.news.core.models import Category, Post from newsreader.news.core.serializers import CategorySerializer, PostSerializer -class ListPostView(ListAPIView): - queryset = Post.objects.all() - serializer_class = PostSerializer - pagination_class = LargeResultSetPagination - filter_backends = [ReadFilter] - - def get_queryset(self): - user = self.request.user - queryset = ( - self.queryset.filter(rule__user=user) - .filter(Q(rule__category=None) | Q(rule__category__user=user)) - .order_by("rule", "-publication_date", "-created") - ) - - return queryset - - class DetailPostView(RetrieveUpdateAPIView): queryset = Post.objects.all() serializer_class = PostSerializer @@ -77,7 +58,7 @@ class NestedRuleCategoryView(ListAPIView): class NestedPostCategoryView(ListAPIView): queryset = Category.objects.prefetch_related("rules", "rules__posts").all() serializer_class = PostSerializer - pagination_class = LargeResultSetPagination + pagination_class = CursorPagination filter_backends = [ReadFilter] def get_queryset(self): @@ -90,9 +71,8 @@ class NestedPostCategoryView(ListAPIView): category = get_object_or_404(self.queryset, **filter_kwargs) self.check_object_permissions(self.request, category) - queryset = Post.objects.filter( - rule__in=category.rules.values_list("id", flat=True) - ).order_by("-publication_date", "rule__name") + rules = category.rules.values_list("id", flat=True) + queryset = Post.objects.filter(rule__in=rules) return queryset diff --git a/src/newsreader/news/core/tests/endpoints/category/list/tests.py b/src/newsreader/news/core/tests/endpoints/category/list/tests.py index 15fb166..c822950 100644 --- a/src/newsreader/news/core/tests/endpoints/category/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/category/list/tests.py @@ -1,6 +1,6 @@ import json -from datetime import date, datetime, time +from datetime import datetime from django.test import TestCase from django.urls import reverse @@ -23,27 +23,21 @@ class CategoryListViewTestCase(TestCase): response = self.client.get(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 3) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 3) def test_ordering(self): categories = [ CategoryFactory( - created=datetime.combine( - date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc - ), + created=datetime(2019, 5, 20, 16, 7, 37, tzinfo=pytz.utc), user=self.user, ), CategoryFactory( - created=datetime.combine( - date(2019, 7, 20), time(hour=18, minute=7, second=37), pytz.utc - ), + created=datetime(2019, 7, 20, 18, 7, 37, tzinfo=pytz.utc), user=self.user, ), CategoryFactory( - created=datetime.combine( - date(2019, 7, 20), time(hour=16, minute=7, second=37), pytz.utc - ), + created=datetime(2019, 7, 20, 16, 7, 37, tzinfo=pytz.utc), user=self.user, ), ] @@ -51,18 +45,18 @@ class CategoryListViewTestCase(TestCase): response = self.client.get(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) - self.assertEquals(data[0]["id"], categories[1].pk) - self.assertEquals(data[1]["id"], categories[2].pk) - self.assertEquals(data[2]["id"], categories[0].pk) + self.assertEqual(data[0]["id"], categories[1].pk) + self.assertEqual(data[1]["id"], categories[2].pk) + self.assertEqual(data[2]["id"], categories[0].pk) def test_empty(self): response = self.client.get(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 0) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 0) def test_post(self): data = {"name": "Tech"} @@ -74,29 +68,29 @@ class CategoryListViewTestCase(TestCase): ) response_data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(response_data["detail"], 'Method "POST" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(response_data["detail"], 'Method "POST" not allowed.') def test_patch(self): response = self.client.patch(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PATCH" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PATCH" not allowed.') def test_put(self): response = self.client.put(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PUT" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PUT" not allowed.') def test_delete(self): response = self.client.delete(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "DELETE" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "DELETE" not allowed.') def test_categories_with_unauthenticated_user(self): self.client.logout() @@ -105,7 +99,7 @@ class CategoryListViewTestCase(TestCase): response = self.client.get(reverse("api:news:core:categories-list")) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_categories_with_unauthorized_user(self): other_user = UserFactory() @@ -114,8 +108,8 @@ class CategoryListViewTestCase(TestCase): response = self.client.get(reverse("api:news:core:categories-list")) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 0) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 0) class NestedCategoryListViewTestCase(TestCase): @@ -132,8 +126,8 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 5) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 5) self.assertTrue("id" in data[0]) self.assertTrue("name" in data[0]) @@ -149,16 +143,16 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 0) - self.assertEquals(data, []) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 0) + self.assertEqual(data, []) def test_not_known(self): response = self.client.get( reverse("api:news:core:categories-nested-rules", kwargs={"pk": 100}) ) - self.assertEquals(response.status_code, 404) + self.assertEqual(response.status_code, 404) def test_post(self): response = self.client.post( @@ -168,8 +162,8 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "POST" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "POST" not allowed.') def test_patch(self): category = CategoryFactory.create(user=self.user) @@ -183,8 +177,8 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PATCH" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PATCH" not allowed.') def test_put(self): category = CategoryFactory.create(user=self.user) @@ -198,8 +192,8 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PUT" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PUT" not allowed.') def test_delete(self): category = CategoryFactory.create(user=self.user) @@ -212,8 +206,8 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "DELETE" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "DELETE" not allowed.') def test_with_unauthenticated_user(self): self.client.logout() @@ -225,7 +219,7 @@ class NestedCategoryListViewTestCase(TestCase): reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk}) ) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_with_unauthorized_user(self): other_user = UserFactory.create() @@ -237,7 +231,7 @@ class NestedCategoryListViewTestCase(TestCase): reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk}) ) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_ordering(self): category = CategoryFactory.create(user=self.user) @@ -252,12 +246,12 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 3) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 3) - self.assertEquals(data[0]["id"], rules[2].pk) - self.assertEquals(data[1]["id"], rules[0].pk) - self.assertEquals(data[2]["id"], rules[1].pk) + self.assertEqual(data[0]["id"], rules[2].pk) + self.assertEqual(data[1]["id"], rules[0].pk) + self.assertEqual(data[2]["id"], rules[1].pk) def test_only_rules_from_category_are_returned(self): other_category = CategoryFactory(user=self.user) @@ -275,12 +269,12 @@ class NestedCategoryListViewTestCase(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data), 3) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data), 3) - self.assertEquals(data[0]["id"], rules[2].pk) - self.assertEquals(data[1]["id"], rules[0].pk) - self.assertEquals(data[2]["id"], rules[1].pk) + self.assertEqual(data[0]["id"], rules[2].pk) + self.assertEqual(data[1]["id"], rules[0].pk) + self.assertEqual(data[2]["id"], rules[1].pk) class NestedCategoryPostView(TestCase): @@ -301,16 +295,15 @@ class NestedCategoryPostView(TestCase): reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) ) data = response.json() - posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 25) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 25) - self.assertTrue("id" in posts[0]) - self.assertTrue("title" in posts[0]) - self.assertTrue("body" in posts[0]) - self.assertTrue("rule" in posts[0]) - self.assertTrue("url" in posts[0]) + self.assertTrue("id" in data["results"][0]) + self.assertTrue("title" in data["results"][0]) + self.assertTrue("body" in data["results"][0]) + self.assertTrue("rule" in data["results"][0]) + self.assertTrue("url" in data["results"][0]) def test_no_rules(self): category = CategoryFactory.create(user=self.user) @@ -321,9 +314,9 @@ class NestedCategoryPostView(TestCase): data = response.json() posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 0) - self.assertEquals(posts, []) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 0) + self.assertEqual(posts, []) def test_no_posts(self): category = CategoryFactory.create(user=self.user) @@ -335,16 +328,16 @@ class NestedCategoryPostView(TestCase): data = response.json() posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 0) - self.assertEquals(posts, []) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 0) + self.assertEqual(posts, []) def test_not_known(self): response = self.client.get( reverse("api:news:core:categories-nested-posts", kwargs={"pk": 100}) ) - self.assertEquals(response.status_code, 404) + self.assertEqual(response.status_code, 404) def test_post(self): response = self.client.post( @@ -354,8 +347,8 @@ class NestedCategoryPostView(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "POST" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "POST" not allowed.') def test_patch(self): category = CategoryFactory.create(user=self.user) @@ -369,8 +362,8 @@ class NestedCategoryPostView(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PATCH" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PATCH" not allowed.') def test_put(self): category = CategoryFactory.create(user=self.user) @@ -384,8 +377,8 @@ class NestedCategoryPostView(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PUT" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "PUT" not allowed.') def test_delete(self): category = CategoryFactory.create(user=self.user) @@ -398,8 +391,8 @@ class NestedCategoryPostView(TestCase): ) data = response.json() - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "DELETE" not allowed.') + self.assertEqual(response.status_code, 405) + self.assertEqual(data["detail"], 'Method "DELETE" not allowed.') def test_with_unauthenticated_user(self): self.client.logout() @@ -410,7 +403,7 @@ class NestedCategoryPostView(TestCase): reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) ) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_with_unauthorized_user(self): other_user = UserFactory.create() @@ -420,7 +413,7 @@ class NestedCategoryPostView(TestCase): reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) ) - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 403) def test_ordering(self): category = CategoryFactory.create(user=self.user) @@ -437,16 +430,12 @@ class NestedCategoryPostView(TestCase): FeedPostFactory.create( title="Second Reuters post", rule=reuters_rule, - publication_date=datetime.combine( - date(2019, 5, 21), time(hour=16, minute=7, second=37), pytz.utc - ), + publication_date=datetime(2019, 5, 21, 15, tzinfo=pytz.utc), ), FeedPostFactory.create( title="First Reuters post", rule=reuters_rule, - publication_date=datetime.combine( - date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc - ), + publication_date=datetime(2019, 5, 20, 12, tzinfo=pytz.utc), ), ] @@ -454,16 +443,12 @@ class NestedCategoryPostView(TestCase): FeedPostFactory.create( title="Second Guardian post", rule=guardian_rule, - publication_date=datetime.combine( - date(2019, 5, 21), time(hour=16, minute=7, second=37), pytz.utc - ), + publication_date=datetime(2019, 5, 21, 14, tzinfo=pytz.utc), ), FeedPostFactory.create( title="First Guardian post", rule=guardian_rule, - publication_date=datetime.combine( - date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc - ), + publication_date=datetime(2019, 5, 20, 11, tzinfo=pytz.utc), ), ] @@ -471,16 +456,12 @@ class NestedCategoryPostView(TestCase): FeedPostFactory.create( title="Second BBC post", rule=bbc_rule, - publication_date=datetime.combine( - date(2019, 5, 21), time(hour=16, minute=7, second=37), pytz.utc - ), + publication_date=datetime(2019, 5, 21, 16, tzinfo=pytz.utc), ), FeedPostFactory.create( title="First BBC post", rule=bbc_rule, - publication_date=datetime.combine( - date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc - ), + publication_date=datetime(2019, 5, 20, 13, tzinfo=pytz.utc), ), ] @@ -490,16 +471,16 @@ class NestedCategoryPostView(TestCase): data = response.json() posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 6) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 6) - self.assertEquals(posts[0]["title"], "Second BBC post") - self.assertEquals(posts[1]["title"], "Second Reuters post") - self.assertEquals(posts[2]["title"], "Second Guardian post") + self.assertEqual(posts[0]["title"], "Second BBC post") + self.assertEqual(posts[1]["title"], "Second Reuters post") + self.assertEqual(posts[2]["title"], "Second Guardian post") - self.assertEquals(posts[3]["title"], "First BBC post") - self.assertEquals(posts[4]["title"], "First Reuters post") - self.assertEquals(posts[5]["title"], "First Guardian post") + self.assertEqual(posts[3]["title"], "First BBC post") + self.assertEqual(posts[4]["title"], "First Reuters post") + self.assertEqual(posts[5]["title"], "First Guardian post") def test_only_posts_from_category_are_returned(self): category = CategoryFactory.create(user=self.user) @@ -526,11 +507,11 @@ class NestedCategoryPostView(TestCase): data = response.json() posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 2) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 2) - self.assertEquals(posts[0]["rule"], guardian_rule.pk) - self.assertEquals(posts[1]["rule"], guardian_rule.pk) + self.assertEqual(posts[0]["rule"], guardian_rule.pk) + self.assertEqual(posts[1]["rule"], guardian_rule.pk) def test_unread_posts(self): category = CategoryFactory.create(user=self.user) @@ -549,11 +530,11 @@ class NestedCategoryPostView(TestCase): data = response.json() posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 10) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 10) for post in posts: - self.assertEquals(post["read"], False) + self.assertEqual(post["read"], False) def test_read_posts(self): category = CategoryFactory.create(user=self.user) @@ -572,8 +553,8 @@ class NestedCategoryPostView(TestCase): data = response.json() posts = data["results"] - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 10) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(data["results"]), 10) for post in posts: - self.assertEquals(post["read"], True) + self.assertEqual(post["read"], True) diff --git a/src/newsreader/news/core/tests/endpoints/post/list/__init__.py b/src/newsreader/news/core/tests/endpoints/post/list/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/newsreader/news/core/tests/endpoints/post/list/tests.py b/src/newsreader/news/core/tests/endpoints/post/list/tests.py deleted file mode 100644 index 3bf9d17..0000000 --- a/src/newsreader/news/core/tests/endpoints/post/list/tests.py +++ /dev/null @@ -1,234 +0,0 @@ -from datetime import date, datetime, time - -from django.test import TestCase -from django.urls import reverse - -import pytz - -from newsreader.accounts.tests.factories import UserFactory -from newsreader.news.collection.tests.factories import FeedFactory -from newsreader.news.core.tests.factories import CategoryFactory, FeedPostFactory - - -class PostListViewTestCase(TestCase): - def setUp(self): - self.user = UserFactory(is_staff=True, password="test") - self.client.force_login(self.user) - - def test_simple(self): - rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) - FeedPostFactory.create_batch(size=3, rule=rule) - - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 3) - - def test_ordering(self): - rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) - - posts = [ - FeedPostFactory( - title="I'm the first post", - rule=rule, - publication_date=datetime.combine( - date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc - ), - ), - FeedPostFactory( - title="I'm the second post", - rule=rule, - publication_date=datetime.combine( - date(2019, 7, 20), time(hour=18, minute=7, second=37), pytz.utc - ), - ), - FeedPostFactory( - title="I'm the third post", - rule=rule, - publication_date=datetime.combine( - date(2019, 7, 20), time(hour=16, minute=7, second=37), pytz.utc - ), - ), - ] - - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 3) - - self.assertEquals(data["results"][0]["id"], posts[1].pk) - self.assertEquals(data["results"][1]["id"], posts[2].pk) - self.assertEquals(data["results"][2]["id"], posts[0].pk) - - def test_pagination_count(self): - rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) - FeedPostFactory.create_batch(size=80, rule=rule) - page_size = 50 - - response = self.client.get(reverse("api:news:core:posts-list"), {"count": 50}) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 80) - self.assertEquals(len(data["results"]), page_size) - - def test_empty(self): - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - - self.assertEquals(data["count"], 0) - self.assertEquals(len(data["results"]), 0) - - def test_post(self): - response = self.client.post(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "POST" not allowed.') - - def test_patch(self): - response = self.client.patch(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PATCH" not allowed.') - - def test_put(self): - response = self.client.put(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "PUT" not allowed.') - - def test_delete(self): - response = self.client.delete(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 405) - self.assertEquals(data["detail"], 'Method "DELETE" not allowed.') - - def test_posts_with_unauthenticated_user_without_category(self): - self.client.logout() - - FeedPostFactory.create_batch(size=3, rule=FeedFactory(user=self.user)) - - response = self.client.get(reverse("api:news:core:posts-list")) - - self.assertEquals(response.status_code, 403) - - def test_posts_with_unauthenticated_user_with_category(self): - self.client.logout() - - category = CategoryFactory(user=self.user) - - FeedPostFactory.create_batch( - size=3, rule=FeedFactory(user=self.user, category=category) - ) - - response = self.client.get(reverse("api:news:core:posts-list")) - - self.assertEquals(response.status_code, 403) - - def test_posts_with_unauthorized_user_without_category(self): - other_user = UserFactory() - - rule = FeedFactory(user=other_user, category=None) - FeedPostFactory.create_batch(size=3, rule=rule) - - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data["results"]), 0) - self.assertEquals(data["count"], 0) - - def test_posts_with_unauthorized_user_with_category(self): - other_user = UserFactory() - category = CategoryFactory(user=other_user) - - FeedPostFactory.create_batch( - size=3, rule=FeedFactory(user=other_user, category=category) - ) - - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertEquals(len(data["results"]), 0) - self.assertEquals(data["count"], 0) - - # Note that this situation should not be possible, due to the user not being able - # to specify the user when creating categories/rules - def test_posts_with_authorized_rule_unauthorized_category(self): - other_user = UserFactory() - - rule = FeedFactory(user=self.user, category=CategoryFactory(user=other_user)) - FeedPostFactory.create_batch(size=3, rule=rule) - - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 0) - - def test_posts_with_authorized_user_without_category(self): - rule = FeedFactory(user=self.user, category=None) - FeedPostFactory.create_batch(size=3, rule=rule) - - response = self.client.get(reverse("api:news:core:posts-list")) - data = response.json() - - self.assertEquals(response.status_code, 200) - self.assertTrue("results" in data) - self.assertTrue("count" in data) - self.assertEquals(data["count"], 3) - - def test_unread_posts(self): - rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) - - FeedPostFactory.create_batch(size=10, rule=rule, read=False) - FeedPostFactory.create_batch(size=10, rule=rule, read=True) - - response = self.client.get( - reverse("api:news:core:posts-list"), {"read": "false"} - ) - - data = response.json() - posts = data["results"] - - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 10) - - for post in posts: - self.assertEquals(post["read"], False) - - def test_read_posts(self): - rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) - - FeedPostFactory.create_batch(size=20, rule=rule, read=False) - FeedPostFactory.create_batch(size=10, rule=rule, read=True) - - response = self.client.get( - reverse("api:news:core:posts-list"), {"read": "true"} - ) - - data = response.json() - posts = data["results"] - - self.assertEquals(response.status_code, 200) - self.assertEquals(data["count"], 10) - - for post in posts: - self.assertEquals(post["read"], True) diff --git a/src/newsreader/news/core/urls.py b/src/newsreader/news/core/urls.py index 8096cf8..21db59d 100644 --- a/src/newsreader/news/core/urls.py +++ b/src/newsreader/news/core/urls.py @@ -6,7 +6,6 @@ from newsreader.news.core.endpoints import ( DetailCategoryView, DetailPostView, ListCategoryView, - ListPostView, NestedPostCategoryView, NestedRuleCategoryView, ) @@ -33,7 +32,6 @@ urlpatterns = [ ] endpoints = [ - path("posts/", ListPostView.as_view(), name="posts-list"), path("posts//", DetailPostView.as_view(), name="posts-detail"), path("categories/", ListCategoryView.as_view(), name="categories-list"), path(