Use ruff for formatting/linting

This commit is contained in:
Sonny Bakker 2024-09-05 06:58:35 +02:00
parent bb74e875e0
commit 57375591b5
36 changed files with 241 additions and 245 deletions

View file

@ -5,14 +5,8 @@ python-linting:
- pip install uv
- uv sync --extra testing --extra ci
script:
- ./.venv/bin/isort --check-only src/
- ./.venv/bin/black --line-length 88 --check src/
- |
./.venv/bin/autoflake --check \
--recursive \
--remove-all-unused-imports \
--ignore-init-module-imports \
src/
- ./.venv/bin/ruff --check src/
- ./.venv/bin/ruff format --check src/
only:
refs:
- development

View file

@ -29,20 +29,30 @@ dependencies = [
testing = [
'factory-boy',
'freezegun',
'black',
'isort',
'autoflake',
'tblib',
"ruff>=0.6.3",
]
development = [
'django-debug-toolbar',
'django-extensions',
]
ci = ['coverage>=5.3.1']
production = ['gunicorn~=20.0', 'sentry-sdk~=1.0']
[tool.uv]
environments = ["sys_platform == 'linux'"]
[tool.ruff]
include = ['pyproject.toml', 'src/**/*.py']
line-length = 88
[tool.ruff.lint.isort]
default-section = 'third-party'
known-first-party = ['newsreader']
lines-between-types=1
lines-after-imports=2
[tool.ruff.lint.isort.sections]
django = ['django']

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys

View file

@ -57,7 +57,7 @@ class ResendActivationTestCase(TestCase):
def test_existing_account(self):
user = UserFactory(is_active=True)
profile = RegistrationProfileFactory(user=user, activated=True)
RegistrationProfileFactory(user=user, activated=True)
response = self.client.post(self.url, {"email": user.email})
self.assertEquals(response.status_code, 200)

View file

@ -25,3 +25,28 @@ from newsreader.accounts.views.registration import (
RegistrationView,
)
from newsreader.accounts.views.settings import SettingsView
__all__ = [
"LoginView",
"LogoutView",
"FaviconRedirectView",
"IntegrationsView",
"RedditRevokeRedirectView",
"RedditTemplateView",
"RedditTokenRedirectView",
"TwitterAuthRedirectView",
"TwitterRevokeRedirectView",
"TwitterTemplateView",
"PasswordChangeView",
"PasswordResetCompleteView",
"PasswordResetConfirmView",
"PasswordResetDoneView",
"PasswordResetView",
"ActivationCompleteView",
"ActivationResendView",
"ActivationView",
"RegistrationClosedView",
"RegistrationCompleteView",
"RegistrationView",
"SettingsView",
]

View file

@ -1,14 +1,14 @@
from .base import * # isort:skip
from .base import * # noqa: F403
from .version import get_current_version
SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl"
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar", "django_extensions"]
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
# Project settings
VERSION = get_current_version()
@ -23,8 +23,8 @@ try:
from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION})
SENTRY_CONFIG.update({"release": VERSION}) # noqa: F405
sentry_init(**SENTRY_CONFIG)
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -1,14 +1,14 @@
from .base import * # isort:skip
from .base import * # noqa: F403
from .version import get_current_version
ALLOWED_HOSTS = ["django", "127.0.0.1"]
INSTALLED_APPS += ["debug_toolbar", "django_extensions"]
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
LOGGING["loggers"].update(
LOGGING["loggers"].update( # noqa: F405
{
"celery.task": {"handlers": ["console", "celery"], "level": "DEBUG"},
}
@ -33,8 +33,8 @@ try:
from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT})
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) # noqa: F405
sentry_init(**SENTRY_CONFIG)
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -1,14 +1,14 @@
from .base import * # isort:skip
from .base import * # noqa: F403
from .version import get_current_version
DEBUG = True
del LOGGING["handlers"]["file"]
del LOGGING["handlers"]["celery"]
del LOGGING["handlers"]["file"] # noqa: F405
del LOGGING["handlers"]["celery"] # noqa: F405
LOGGING["loggers"].update(
LOGGING["loggers"].update( # noqa: F405
{
"celery.task": {"handlers": ["console"], "level": "DEBUG"},
"newsreader": {"handlers": ["console"], "level": "INFO"},
@ -39,8 +39,8 @@ try:
# Optionally use sentry integration
from sentry_sdk import init as sentry_init
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT})
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) # noqa: F405
sentry_init(**SENTRY_CONFIG)
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -3,7 +3,7 @@ import os
from .version import get_current_version
from .base import * # isort:skip
from .base import * # noqa: F403
DEBUG = False
@ -20,7 +20,7 @@ ADMINS = [
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")],
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")], # noqa: F405
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
@ -68,10 +68,10 @@ REGISTRATION_OPEN = False
try:
from sentry_sdk import init as sentry_init
SENTRY_CONFIG.update(
SENTRY_CONFIG.update( # noqa: F405
{"release": VERSION, "environment": ENVIRONMENT, "debug": False}
)
sentry_init(**SENTRY_CONFIG)
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -15,3 +15,20 @@ from newsreader.news.collection.exceptions.stream import (
StreamTimeOutException,
StreamTooManyException,
)
__all__ = [
"BuilderDuplicateException",
"BuilderException",
"BuilderMissingDataException",
"BuilderParseException",
"BuilderSkippedException",
"StreamConnectionException",
"StreamDeniedException",
"StreamException",
"StreamForbiddenException",
"StreamNotFoundException",
"StreamParseException",
"StreamTimeOutException",
"StreamTooManyException",
]

View file

@ -81,7 +81,7 @@ class FaviconBuilder(Builder):
icons = set()
for link in links:
if not "href" in link.attrs:
if "href" not in link.attrs:
continue
if "favicon" in link["href"]:

View file

@ -62,7 +62,7 @@ class FeedBuilder(PostBuilder):
data = {"rule_id": self.stream.rule.pk}
for field, model_field in field_mapping.items():
if not field in entry:
if field not in entry:
continue
value = truncate_text(Post, model_field, entry[field])
@ -77,7 +77,7 @@ class FeedBuilder(PostBuilder):
content_details = self.get_content_details(entry)
# use content details key if it contains more information
if not "body" in data or len(data["body"]) < len(content_details):
if "body" not in data or len(data["body"]) < len(content_details):
data["body"] = content_details
return Post(**data)

View file

@ -2,3 +2,11 @@ from newsreader.news.collection.forms.feed import FeedForm, OPMLImportForm
from newsreader.news.collection.forms.reddit import SubRedditForm
from newsreader.news.collection.forms.rules import CollectionRuleBulkForm
from newsreader.news.collection.forms.twitter import TwitterTimelineForm
__all__ = [
"FeedForm",
"OPMLImportForm",
"SubRedditForm",
"CollectionRuleBulkForm",
"TwitterTimelineForm",
]

View file

@ -120,7 +120,7 @@ class RedditBuilder(PostBuilder):
def build(self):
results = {}
if not "data" in self.payload or not "children" in self.payload["data"]:
if "data" not in self.payload or "children" not in self.payload["data"]:
return
entries = self.payload["data"]["children"]
@ -297,9 +297,7 @@ class RedditStream(PostStream):
def __init__(self, rule):
super().__init__(rule)
self.headers = {
f"Authorization": f"bearer {self.rule.user.reddit_access_token}"
}
self.headers = {"Authorization": f"bearer {self.rule.user.reddit_access_token}"}
def read(self):
response = fetch(self.rule.url, headers=self.headers)

View file

@ -11,6 +11,7 @@ from celery.utils.log import get_task_logger
from newsreader.accounts.models import User
from newsreader.celery import app
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.exceptions.stream import StreamException
from newsreader.news.collection.feed import FeedCollector
from newsreader.news.collection.utils import post
from newsreader.utils.celery import MemCacheLock
@ -57,7 +58,7 @@ class RedditTask(app.Task):
with MemCacheLock("reddit-task", self.app.oid) as acquired:
if acquired:
logger.info(f"Running reddit task")
logger.info("Running reddit task")
scheduler = RedditScheduler()
subreddits = scheduler.get_scheduled_rules()
@ -65,7 +66,7 @@ class RedditTask(app.Task):
collector = RedditCollector()
collector.collect(rules=subreddits)
else:
logger.warning(f"Cancelling task due to existing lock")
logger.warning("Cancelling task due to existing lock")
raise Reject(reason="Task already running", requeue=False)
@ -154,7 +155,7 @@ class TwitterTimelineTask(app.Task):
collector = TwitterCollector()
collector.collect(rules=timelines)
else:
logger.warning(f"Cancelling task due to existing lock")
logger.warning("Cancelling task due to existing lock")
raise Reject(reason="Task already running", requeue=False)
@ -195,7 +196,7 @@ class FaviconTask(app.Task):
rule.favicon = "https://abs.twimg.com/favicons/favicon.ico"
rule.save()
else:
logger.warning(f"Cancelling task due to existing lock")
logger.warning("Cancelling task due to existing lock")
raise Reject(reason="Task already running", requeue=False)

View file

@ -64,22 +64,6 @@ class CollectionRuleDetailViewTestCase(TestCase):
self.assertEquals(response.status_code, 200)
self.assertEquals(data["name"], "The guardian")
def test_category_change(self):
old_category = CategoryFactory(user=self.user)
new_category = CategoryFactory(user=self.user)
rule = FeedFactory(name="BBC", category=old_category, user=self.user)
response = self.client.patch(
reverse("api:news:collection:rules-detail", args=[rule.pk]),
data=json.dumps({"category": absolute_url}),
content_type="application/json",
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["category"], new_category.pk)
def test_identifier_cannot_be_changed(self):
rule = FeedFactory(user=self.user)

View file

@ -4,7 +4,14 @@ from django.test import TestCase
from newsreader.news.collection.favicon import FaviconBuilder
from newsreader.news.collection.tests.factories import CollectionRuleFactory
from newsreader.news.collection.tests.favicon.builder.mocks import *
from newsreader.news.collection.tests.favicon.builder.mocks import (
simple_mock,
mock_without_url,
mock_without_header,
mock_with_weird_path,
mock_with_other_url,
mock_with_multiple_icons,
)
class FaviconBuilderTestCase(TestCase):

View file

@ -134,7 +134,6 @@ feed_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"link": "https://www.bbc.co.uk/news/",
"links": [

View file

@ -108,8 +108,6 @@ mock_without_url = {
"id": "https://www.bbc.co.uk/news/world-us-canada-48338168",
"published": "Mon, 20 May 2019 16:07:37 GMT",
"published_parsed": struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0)),
"published": None,
"published_parsed": None,
"summary": "Foreign Minister Mohammad Javad Zarif says the US "
"president should try showing Iranians some respect.",
"title": "Trump's 'genocidal taunts' will not end Iran - Zarif",

View file

@ -13,7 +13,23 @@ from newsreader.news.collection.tests.factories import FeedFactory
from newsreader.news.core.models import Post
from newsreader.news.core.tests.factories import FeedPostFactory
from .mocks import *
from .mocks import (
multiple_mock,
mock_without_identifier,
mock_without_publish_date,
mock_without_url,
mock_without_body,
mock_without_author,
mock_without_entries,
mock_with_update_entries,
mock_with_html,
mock_with_long_author,
mock_with_long_title,
mock_with_long_exotic_title,
mock_with_longer_content_detail,
mock_with_shorter_content_detail,
mock_with_multiple_content_detail,
)
@freeze_time("2019-10-30 12:30:00")

View file

@ -52,7 +52,6 @@ simple_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"links": [
{

View file

@ -132,7 +132,6 @@ multiple_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"links": [
{
@ -158,7 +157,6 @@ empty_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"links": [
{
@ -302,7 +300,6 @@ duplicate_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"links": [
{
@ -449,7 +446,6 @@ multiple_update_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"links": [
{

View file

@ -142,7 +142,7 @@ class FeedCollectorTestCase(TestCase):
struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0)), pytz.utc
)
first_post = FeedPostFactory(
FeedPostFactory(
url="https://www.bbc.co.uk/news/world-us-canada-48338168",
title="Trump's 'genocidal taunts' will not end Iran - Zarif",
body="Foreign Minister Mohammad Javad Zarif says the US "
@ -155,7 +155,7 @@ class FeedCollectorTestCase(TestCase):
struct_time((2019, 5, 20, 12, 19, 19, 0, 140, 0)), pytz.utc
)
second_post = FeedPostFactory(
FeedPostFactory(
url="https://www.bbc.co.uk/news/technology-48334739",
title="Huawei's Android loss: How it affects you",
body="Google's move to end business ties with Huawei will "
@ -168,7 +168,7 @@ class FeedCollectorTestCase(TestCase):
struct_time((2019, 5, 20, 16, 32, 38, 0, 140, 0)), pytz.utc
)
third_post = FeedPostFactory(
FeedPostFactory(
url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
title="Birmingham head teacher threatened over LGBT lessons",
body="Police are investigating the messages while an MP "

View file

@ -19,7 +19,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_with_remote_identifiers(self):
rule = FeedFactory()
existing_post = FeedPostFactory.create(
FeedPostFactory.create(
remote_identifier="28f79ae4-8f9a-11e9-b143-00163ef6bee7", rule=rule
)
@ -54,7 +54,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_with_different_remote_identifiers(self):
rule = FeedFactory()
existing_post = FeedPostFactory(
FeedPostFactory(
remote_identifier="28f79ae4-8f9a-11e9-b143-00163ef6bee7",
url="https://bbc.com",
title="New post",
@ -100,7 +100,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_in_recent_database(self):
rule = FeedFactory()
existing_post = FeedPostFactory(
FeedPostFactory(
url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
title="Birmingham head teacher threatened over LGBT lessons",
body="Google's move to end business ties with Huawei will affect current devices",
@ -189,7 +189,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_outside_time_slot(self):
rule = FeedFactory()
existing_post = FeedPostFactory(
FeedPostFactory(
url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
title="Birmingham head teacher threatened over LGBT lessons",
body="Google's move to end business ties with Huawei will affect current devices",

View file

@ -17,7 +17,6 @@ simple_feed_mock = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"link": "https://www.bbc.co.uk/news/",
"links": [
@ -43,7 +42,6 @@ feed_mock_without_link = {
"link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home",
"language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
},
"title": "BBC News - Home",
},

View file

@ -677,16 +677,6 @@ empty_mock = {
},
}
unknown_mock = {
"kind": "Comment",
"data": {
"modhash": "rjewztai5w0ab64547311ae1fb1f9cf81cd18949bfb629cb7f",
"dist": 27,
"after": "t3_hmytic",
"before": None,
},
}
unsanitized_mock = {
"kind": "Listing",
"data": {

View file

@ -7,7 +7,26 @@ import pytz
from newsreader.news.collection.reddit import RedditBuilder
from newsreader.news.collection.tests.factories import SubredditFactory
from newsreader.news.collection.tests.reddit.builder.mocks import *
from newsreader.news.collection.tests.reddit.builder.mocks import (
simple_mock,
empty_mock,
unknown_mock,
unsanitized_mock,
author_mock,
title_mock,
duplicate_mock,
image_mock,
external_image_mock,
video_mock,
external_video_mock,
external_gifv_mock,
nsfw_mock,
spoiler_mock,
seen_mock,
upvote_mock,
comment_mock,
downvote_mock,
)
from newsreader.news.core.models import Post
from newsreader.news.core.tests.factories import RedditPostFactory
@ -164,9 +183,7 @@ class RedditBuilderTestCase(TestCase):
subreddit = SubredditFactory()
mock_stream = Mock(rule=subreddit)
duplicate_post = RedditPostFactory(
remote_identifier="hm0qct", rule=subreddit, title="foo"
)
RedditPostFactory(remote_identifier="hm0qct", rule=subreddit, title="foo")
with builder(simple_mock, mock_stream) as builder:
builder.build()

View file

@ -150,7 +150,6 @@ class RedditClientTestCase(TestCase):
def test_client_catches_long_exception_text(self):
subreddit = SubredditFactory()
mock_stream = Mock(rule=subreddit)
self.mocked_read.side_effect = StreamParseException(message=words(1000))

View file

@ -153,7 +153,6 @@ class TwitterClientTestCase(TestCase):
def test_client_catches_long_exception_text(self):
timeline = TwitterTimelineFactory()
mock_stream = Mock(rule=timeline)
self.mocked_read.side_effect = StreamParseException(message=words(1000))

View file

@ -17,3 +17,18 @@ from newsreader.news.collection.views.twitter import (
TwitterTimelineCreateView,
TwitterTimelineUpdateView,
)
__all__ = [
"FeedCreateView",
"FeedUpdateView",
"OPMLImportView",
"SubRedditCreateView",
"SubRedditUpdateView",
"CollectionRuleBulkDeleteView",
"CollectionRuleBulkDisableView",
"CollectionRuleBulkEnableView",
"CollectionRuleListView",
"TwitterTimelineCreateView",
"TwitterTimelineUpdateView",
]

View file

@ -138,10 +138,10 @@ class CategoryReadTestCase(TestCase):
def test_category_read(self):
category = CategoryFactory(user=self.user)
rules = [
rules = FeedFactory.create_batch(size=5, category=category)
for rule in rules:
FeedPostFactory.create_batch(size=5, read=False, rule=rule)
for rule in FeedFactory.create_batch(size=5, category=category)
]
response = self.client.post(
reverse("api:news:core:categories-read", args=[category.pk])
@ -164,12 +164,10 @@ class CategoryReadTestCase(TestCase):
self.client.logout()
category = CategoryFactory(user=self.user)
rules = [
rules = FeedFactory.create_batch(size=5, category=category, user=self.user)
for rule in rules:
FeedPostFactory.create_batch(size=5, read=False, rule=rule)
for rule in FeedFactory.create_batch(
size=5, category=category, user=self.user
)
]
response = self.client.post(
reverse("api:news:core:categories-read", args=[category.pk])
@ -180,13 +178,10 @@ class CategoryReadTestCase(TestCase):
def test_unauthorized_user(self):
other_user = UserFactory()
category = CategoryFactory(user=other_user)
rules = FeedFactory.create_batch(size=5, category=category, user=other_user)
rules = [
for rule in rules:
FeedPostFactory.create_batch(size=5, read=False, rule=rule)
for rule in FeedFactory.create_batch(
size=5, category=category, user=other_user
)
]
response = self.client.post(
reverse("api:news:core:categories-read", args=[category.pk])

View file

@ -119,7 +119,7 @@ class NestedCategoryListViewTestCase(TestCase):
def test_simple(self):
category = CategoryFactory.create(user=self.user)
rules = FeedFactory.create_batch(size=5, category=category)
FeedFactory.create_batch(size=5, category=category)
response = self.client.get(
reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
@ -213,7 +213,7 @@ class NestedCategoryListViewTestCase(TestCase):
self.client.logout()
category = CategoryFactory.create(user=self.user)
rules = FeedFactory.create_batch(size=5, category=category)
FeedFactory.create_batch(size=5, category=category)
response = self.client.get(
reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
@ -225,7 +225,7 @@ class NestedCategoryListViewTestCase(TestCase):
other_user = UserFactory.create()
category = CategoryFactory.create(user=other_user)
rules = FeedFactory.create_batch(size=5, category=category)
FeedFactory.create_batch(size=5, category=category)
response = self.client.get(
reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
@ -284,12 +284,10 @@ class NestedCategoryPostView(TestCase):
def test_simple(self):
category = CategoryFactory.create(user=self.user)
rules = {
rule.pk: FeedPostFactory.create_batch(size=5, rule=rule)
for rule in FeedFactory.create_batch(
size=5, category=category, user=self.user
)
}
rules = FeedFactory.create_batch(size=5, category=category, user=self.user)
for rule in rules:
FeedPostFactory.create_batch(size=5, rule=rule)
response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
@ -320,7 +318,7 @@ class NestedCategoryPostView(TestCase):
def test_no_posts(self):
category = CategoryFactory.create(user=self.user)
rules = FeedFactory.create_batch(size=5, user=self.user, category=category)
FeedFactory.create_batch(size=5, user=self.user, category=category)
response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
@ -439,31 +437,29 @@ class NestedCategoryPostView(TestCase):
),
]
guardian_posts = [
FeedPostFactory.create(
title="Second Guardian post",
rule=guardian_rule,
publication_date=datetime(2019, 5, 21, 14, tzinfo=pytz.utc),
),
)
FeedPostFactory.create(
title="First Guardian post",
rule=guardian_rule,
publication_date=datetime(2019, 5, 20, 11, tzinfo=pytz.utc),
),
]
)
bbc_posts = [
FeedPostFactory.create(
title="Second BBC post",
rule=bbc_rule,
publication_date=datetime(2019, 5, 21, 16, tzinfo=pytz.utc),
),
)
FeedPostFactory.create(
title="First BBC post",
rule=bbc_rule,
publication_date=datetime(2019, 5, 20, 13, tzinfo=pytz.utc),
),
]
)
response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
@ -484,22 +480,18 @@ class NestedCategoryPostView(TestCase):
def test_only_posts_from_category_are_returned(self):
category = CategoryFactory.create(user=self.user)
other_category = CategoryFactory.create(user=self.user)
CategoryFactory.create(user=self.user)
guardian_rule = FeedFactory.create(
name="BBC", category=category, user=self.user
)
other_rule = FeedFactory.create(name="The Guardian", user=self.user)
guardian_posts = [
FeedPostFactory.create(rule=guardian_rule),
FeedPostFactory.create(rule=guardian_rule),
]
FeedPostFactory.create(rule=guardian_rule)
FeedPostFactory.create(rule=guardian_rule)
other_posts = [
FeedPostFactory.create(rule=other_rule),
FeedPostFactory.create(rule=other_rule),
]
FeedPostFactory.create(rule=other_rule)
FeedPostFactory.create(rule=other_rule)
response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})

View file

@ -55,9 +55,7 @@ class CategoryCreateViewTestCase(CategoryViewTestCase, TestCase):
size=4, user=other_user, category=None
)
user_rules = CollectionRuleFactory.create_batch(
size=3, user=self.user, category=None
)
CollectionRuleFactory.create_batch(size=3, user=self.user, category=None)
data = {
"name": "new-category",

View file

@ -21,7 +21,7 @@ def parse_opml(file, user, skip_existing=False):
validate = URLValidator(schemes=["http", "https"])
for element in root.iter(tag="outline"):
if not "xmlUrl" in element.keys():
if "xmlUrl" not in element.keys():
continue
feed_url = element.get("xmlUrl")

114
uv.lock generated
View file

@ -28,18 +28,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 },
]
[[package]]
name = "autoflake"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyflakes", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2a/cb/486f912d6171bc5748c311a2984a301f4e2d054833a1da78485866c71522/autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e", size = 27642 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483 },
]
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
@ -61,30 +49,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/50/8d/6e9fdeeab04d803abc5a715175f87e88893934d5590595eacff23ca12b07/billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d", size = 86720 },
]
[[package]]
name = "black"
version = "24.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", marker = "sys_platform == 'linux'" },
{ name = "mypy-extensions", marker = "sys_platform == 'linux'" },
{ name = "packaging", marker = "sys_platform == 'linux'" },
{ name = "pathspec", marker = "sys_platform == 'linux'" },
{ name = "platformdirs", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", size = 644810 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/a6/0a3aa89de9c283556146dc6dbda20cd63a9c94160a6fbdebaf0918e4a3e1/black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1", size = 1615080 },
{ url = "https://files.pythonhosted.org/packages/db/94/b803d810e14588bb297e565821a947c108390a079e21dbdcb9ab6956cd7a/black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", size = 1438143 },
{ url = "https://files.pythonhosted.org/packages/a5/b5/f485e1bbe31f768e2e5210f52ea3f432256201289fd1a3c0afda693776b0/black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", size = 1738774 },
{ url = "https://files.pythonhosted.org/packages/a8/69/a000fc3736f89d1bdc7f4a879f8aaf516fb03613bb51a0154070383d95d9/black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", size = 1427503 },
{ url = "https://files.pythonhosted.org/packages/a2/a8/05fb14195cfef32b7c8d4585a44b7499c2a4b205e1662c427b941ed87054/black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", size = 1646132 },
{ url = "https://files.pythonhosted.org/packages/41/77/8d9ce42673e5cb9988f6df73c1c5c1d4e9e788053cccd7f5fb14ef100982/black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", size = 1448665 },
{ url = "https://files.pythonhosted.org/packages/cc/94/eff1ddad2ce1d3cc26c162b3693043c6b6b575f538f602f26fe846dfdc75/black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", size = 1762458 },
{ url = "https://files.pythonhosted.org/packages/28/ea/18b8d86a9ca19a6942e4e16759b2fa5fc02bbc0eb33c1b866fcd387640ab/black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", size = 1436109 },
{ url = "https://files.pythonhosted.org/packages/27/1e/83fa8a787180e1632c3d831f7e58994d7aaf23a0961320d21e84f922f919/black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", size = 206504 },
]
[[package]]
name = "bleach"
version = "6.1.0"
@ -497,15 +461,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 },
]
[[package]]
name = "isort"
version = "5.13.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 },
]
[[package]]
name = "kombu"
version = "5.4.0"
@ -578,15 +533,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 },
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
]
[[package]]
name = "newsreader"
version = "0.4.4"
@ -625,19 +571,15 @@ production = [
{ name = "sentry-sdk", marker = "sys_platform == 'linux'" },
]
testing = [
{ name = "autoflake", marker = "sys_platform == 'linux'" },
{ name = "black", marker = "sys_platform == 'linux'" },
{ name = "factory-boy", marker = "sys_platform == 'linux'" },
{ name = "freezegun", marker = "sys_platform == 'linux'" },
{ name = "isort", marker = "sys_platform == 'linux'" },
{ name = "ruff", marker = "sys_platform == 'linux'" },
{ name = "tblib", marker = "sys_platform == 'linux'" },
]
[package.metadata]
requires-dist = [
{ name = "autoflake", marker = "extra == 'testing'" },
{ name = "beautifulsoup4" },
{ name = "black", marker = "extra == 'testing'" },
{ name = "bleach" },
{ name = "celery", specifier = "~=5.0" },
{ name = "coverage", marker = "extra == 'ci'", specifier = ">=5.3.1" },
@ -654,13 +596,13 @@ requires-dist = [
{ name = "freezegun", marker = "extra == 'testing'" },
{ name = "ftfy", specifier = "~=5.8" },
{ name = "gunicorn", marker = "extra == 'production'", specifier = "~=20.0" },
{ name = "isort", marker = "extra == 'testing'" },
{ name = "lxml" },
{ name = "psycopg2" },
{ name = "python-dotenv", specifier = "~=0.12" },
{ name = "python-memcached", specifier = "<=1.59" },
{ name = "requests" },
{ name = "requests-oauthlib" },
{ name = "ruff", marker = "extra == 'testing'", specifier = ">=0.6.3" },
{ name = "sentry-sdk", marker = "extra == 'production'", specifier = "~=1.0" },
{ name = "setuptools", specifier = ">=74.0.0" },
{ name = "tblib", marker = "extra == 'testing'" },
@ -684,24 +626,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
]
[[package]]
name = "platformdirs"
version = "4.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 },
]
[[package]]
name = "prompt-toolkit"
version = "3.0.47"
@ -726,15 +650,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/58/4b/c4a26e191882b60150bfcb639e416524ae7f8249ab7ee854fb5247f16c40/psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693", size = 1163789 },
]
[[package]]
name = "pyflakes"
version = "3.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 },
]
[[package]]
name = "python-crontab"
version = "3.2.0"
@ -852,6 +767,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 },
]
[[package]]
name = "ruff"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5d/f9/0b32e5d1c6f957df49398cd882a011e9488fcbca0d6acfeeea50ccd37a4d/ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983", size = 2463514 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/68/1da6a1e39a03a229ea57c511691d6225072759cc7764206c3f0989521194/ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3", size = 9696928 },
{ url = "https://files.pythonhosted.org/packages/6e/59/3b8b1d3a4271c6eb6ceecd3cef19a6d881639a0f18ad651563d6f619aaae/ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc", size = 9448462 },
{ url = "https://files.pythonhosted.org/packages/35/4f/b942ecb8bbebe53aa9b33e9b96df88acd50b70adaaed3070f1d92131a1cb/ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1", size = 9176190 },
{ url = "https://files.pythonhosted.org/packages/a0/20/b0bcb29d4ee437f3567b73b6905c034e2e94d29b9b826c66daecc1cf6388/ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1", size = 10108892 },
{ url = "https://files.pythonhosted.org/packages/9c/e3/211bc759f424e8823a9937e0f678695ca02113c621dfde1fa756f9f26f6d/ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672", size = 9476471 },
{ url = "https://files.pythonhosted.org/packages/b2/a3/2ec35a2d7a554364864206f0e46812b92a074ad8a014b923d821ead532aa/ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1", size = 10294802 },
{ url = "https://files.pythonhosted.org/packages/03/8b/56ef687b3489c88886dea48c78fb4969b6b65f18007d0ac450070edd1f58/ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384", size = 11022372 },
{ url = "https://files.pythonhosted.org/packages/a5/21/327d147feb442adb88975e81e2263102789eba9ad2afa102c661912a482f/ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a", size = 10596596 },
{ url = "https://files.pythonhosted.org/packages/6c/86/ff386de63729da3e08c8099c57f577a00ec9f3eea711b23ac07cf3588dc5/ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500", size = 11572830 },
{ url = "https://files.pythonhosted.org/packages/38/5d/b33284c108e3f315ddd09b70296fd76bd28ecf8965a520bc93f3bbd8ac40/ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470", size = 10262577 },
{ url = "https://files.pythonhosted.org/packages/29/99/9cdfad0d7f460e66567236eddc691473791afd9aff93a0dfcdef0462a6c7/ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f", size = 10098751 },
{ url = "https://files.pythonhosted.org/packages/a8/9f/f801a1619f5549e552f1f722f1db57eb39e7e1d83d482133142781d450de/ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5", size = 9563859 },
{ url = "https://files.pythonhosted.org/packages/0b/4d/fb2424faf04ffdb960ae2b3a1d991c5183dd981003de727d2d5cc38abc98/ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351", size = 9914291 },
{ url = "https://files.pythonhosted.org/packages/2e/dd/94fddf002a8f6152e8ebfbb51d3f93febc415c1fe694345623c31ce8b33b/ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8", size = 10331549 },
{ url = "https://files.pythonhosted.org/packages/b4/73/ca9c2f9237a430ca423b6dca83b77e9a428afeb7aec80596e86c369123fe/ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521", size = 7962163 },
{ url = "https://files.pythonhosted.org/packages/55/ce/061c605b1dfb52748d59bc0c7a8507546c178801156415773d18febfd71d/ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb", size = 8800901 },
{ url = "https://files.pythonhosted.org/packages/63/28/ae4ffe7d3b6134ca6d31ebef07447ef70097c4a9e8fbbc519b374c5c1559/ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82", size = 8229171 },
]
[[package]]
name = "sentry-sdk"
version = "1.45.1"