This commit is contained in:
Sonny Bakker 2021-02-18 22:14:47 +01:00
parent 00164bd3b5
commit c61ce0bcb7
17 changed files with 238 additions and 673 deletions

View file

@ -1,5 +1,10 @@
# Changelog
## 0.3.9
- Cursor based pagination
- Updated django version
## 0.3.8
- Update light / dark theme

View file

@ -1,6 +1,6 @@
{
"name": "newsreader",
"version": "0.1.0",
"version": "0.3.9",
"description": "Application for viewing RSS feeds",
"main": "index.js",
"scripts": {

148
poetry.lock generated
View file

@ -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"},

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "newsreader"
version = "0.2"
version = "0.3.9"
description = "Webapplication for reading RSS feeds"
authors = ["Sonny <sonnyba871@gmail.com>"]
license = "GPL-3.0"

View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -14,7 +14,7 @@ if (page) {
const settings = JSON.parse(document.getElementById('homepageSettings').textContent);
const { feedUrl, subredditUrl, timelineUrl, categoriesUrl } = settings;
ReactDOM.render(
const app = (
<Provider store={store}>
<App
feedUrl={feedUrl.substring(1, feedUrl.length - 3)}
@ -24,7 +24,8 @@ if (page) {
timezone={settings.timezone}
autoMarking={settings.autoMarking}
/>
</Provider>,
page
</Provider>
);
ReactDOM.render(app, page);
}

View file

@ -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):

View file

@ -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()

View file

@ -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)

View file

@ -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/<int:pk>/", DetailRuleView.as_view(), name="rules-detail"),
path("rules/<int:pk>/posts/", NestedRuleView.as_view(), name="rules-nested-posts"),
path("rules/<int:pk>/read/", RuleReadView.as_view(), name="rules-read"),
path("rules/", ListRuleView.as_view(), name="rules-list"),
]
urlpatterns = [

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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/<int:pk>/", DetailPostView.as_view(), name="posts-detail"),
path("categories/", ListCategoryView.as_view(), name="categories-list"),
path(