Mass refactoring
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Silver Ghost 2023-02-10 12:16:11 +03:00
parent 500f4dfbdd
commit 520acb9477
No known key found for this signature in database
52 changed files with 402 additions and 365 deletions

View File

@ -39,7 +39,7 @@ indent_style = tab
[docs/**.txt]
max_line_length = 79
[*.yml]
[*.yaml]
indent_size = 2
[*.md]

5
.flake8 Normal file
View File

@ -0,0 +1,5 @@
[flake8]
max-line-length = 120
per-file-ignores =
later42/views/index.py: E722
later42/views/reader.py: E722

27
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,27 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-ast
- id: check-byte-order-marker
- id: check-case-conflict
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-yaml
args: [--allow-multiple-documents]
- id: debug-statements
- id: detect-private-key
- id: end-of-file-fixer
- id: requirements-txt-fixer
- id: trailing-whitespace
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
language_version: python3.11

View File

@ -1,3 +1,3 @@
from .celery import app as celery_app
__all__ = ('celery_app',)
__all__ = ("celery_app",)

View File

@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'later42.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "later42.settings")
application = get_asgi_application()

View File

@ -3,15 +3,15 @@ import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'later42.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "later42.settings")
app = Celery('later42')
app = Celery("later42")
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
app.config_from_object("django.conf:settings", namespace="CELERY")
# Load task modules from all registered Django apps.
app.autodiscover_tasks()
@ -19,4 +19,4 @@ app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')
print(f"Request: {self.request!r}")

View File

@ -7,29 +7,24 @@ from django.contrib.auth.forms import UserCreationForm
class SignUpForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['password1'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['password2'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['username'].label = 'Логин'
self.fields["username"].widget.attrs.update({"class": "form-control"})
self.fields["password1"].widget.attrs.update({"class": "form-control"})
self.fields["password2"].widget.attrs.update({"class": "form-control"})
self.fields["username"].label = "Логин"
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2',)
fields = (
"username",
"email",
"password1",
"password2",
)
class CustomLoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['password'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['username'].label = 'Логин'
self.fields["username"].widget.attrs.update({"class": "form-control"})
self.fields["password"].widget.attrs.update({"class": "form-control"})
self.fields["username"].label = "Логин"

View File

@ -3,10 +3,10 @@ from newspaper import Article, Config
def sanitize_img_size(html: str):
soup = BeautifulSoup(html, 'html.parser')
for img in soup.find_all('img'):
img['width'] = '100%'
img['height'] = 'auto'
soup = BeautifulSoup(html, "html.parser")
for img in soup.find_all("img"):
img["width"] = "100%"
img["height"] = "auto"
return str(soup)

View File

@ -1,10 +1,8 @@
class ExceptionLoggingMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
print(request.body)

View File

@ -6,7 +6,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
@ -15,12 +14,23 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='URL',
name="URL",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('url', models.CharField(max_length=2000)),
('title', models.CharField(max_length=2000)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("url", models.CharField(max_length=2000)),
("title", models.CharField(max_length=2000)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('later42', '0001_initial'),
("later42", "0001_initial"),
]
operations = [
migrations.AddField(
model_name='url',
name='archived',
model_name="url",
name="archived",
field=models.BooleanField(default=False),
),
]

View File

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('later42', '0002_url_archived'),
("later42", "0002_url_archived"),
]
operations = [
migrations.AddField(
model_name='url',
name='content',
model_name="url",
name="content",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -5,18 +5,27 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('later42', '0003_url_content'),
("later42", "0003_url_content"),
]
operations = [
migrations.CreateModel(
name='Articles',
name="Articles",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('content', models.TextField(blank=True, null=True)),
('url', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='later42.url')),
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("content", models.TextField(blank=True, null=True)),
(
"url",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="later42.url"
),
),
],
),
]

View File

@ -4,14 +4,13 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('later42', '0004_articles'),
("later42", "0004_articles"),
]
operations = [
migrations.RenameModel(
old_name='Articles',
new_name='Article',
old_name="Articles",
new_name="Article",
),
]

View File

@ -4,28 +4,27 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('later42', '0005_rename_articles_article'),
("later42", "0005_rename_articles_article"),
]
operations = [
migrations.RemoveField(
model_name='url',
name='content',
model_name="url",
name="content",
),
migrations.RemoveField(
model_name='url',
name='title',
model_name="url",
name="title",
),
migrations.AddField(
model_name='article',
name='short',
model_name="article",
name="short",
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='article',
name='title',
model_name="article",
name="title",
field=models.CharField(blank=True, max_length=2000, null=True),
),
]

View File

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('later42', '0006_remove_url_content_remove_url_title_article_short_and_more'),
("later42", "0006_remove_url_content_remove_url_title_article_short_and_more"),
]
operations = [
migrations.AddField(
model_name='article',
name='img',
model_name="article",
name="img",
field=models.URLField(blank=True, null=True),
),
]

View File

@ -1,7 +1,7 @@
from django.contrib.auth.models import User
from django.db import models
User._meta.get_field('email')._unique = True
User._meta.get_field("email")._unique = True
class URL(models.Model):

View File

@ -5,10 +5,10 @@ from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['url', 'username', 'email', 'groups']
fields = ["url", "username", "email", "groups"]
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ['url', 'name']
fields = ["url", "name"]

View File

@ -21,80 +21,81 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv(
'SECRET', 'django-insecure-c%g@wujt4dco#e%k-!25o3)0%t+wm5=k%l(m!kk^p_g%kknod!')
"SECRET", "django-insecure-c%g@wujt4dco#e%k-!25o3)0%t+wm5=k%l(m!kk^p_g%kknod!"
)
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG', False)
DEBUG = os.getenv("DEBUG", False)
ALLOWED_HOSTS = ['*']
CSRF_TRUSTED_ORIGINS = ['https://' + os.getenv('DOMAIN', 'localhost')]
ALLOWED_HOSTS = ["*"]
CSRF_TRUSTED_ORIGINS = ["https://" + os.getenv("DOMAIN", "localhost")]
# Application definition
INSTALLED_APPS = [
'later42',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
"later42",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"rest_framework.authtoken",
]
MIDDLEWARE = [
# 'later42.logging.debug.ExceptionLoggingMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = 'later42.urls'
ROOT_URLCONF = "later42.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'later42.wsgi.application'
WSGI_APPLICATION = "later42.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db/db.sqlite3',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db/db.sqlite3",
}
}
if os.getenv('DB_TYPE', 'SQLite') == 'postgres':
if os.getenv("DB_TYPE", "SQLite") == "postgres":
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'HOST': os.environ.get('DB_HOST', None),
'NAME': os.environ.get('DB_NAME', None),
'USER': os.environ.get('DB_USER', None),
'PASSWORD': os.environ.get('DB_PASS', None),
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ.get("DB_HOST", None),
"NAME": os.environ.get("DB_NAME", None),
"USER": os.environ.get("DB_USER", None),
"PASSWORD": os.environ.get("DB_PASS", None),
}
}
@ -104,16 +105,16 @@ if os.getenv('DB_TYPE', 'SQLite') == 'postgres':
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
@ -121,9 +122,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'ru-ru'
LANGUAGE_CODE = "ru-ru"
TIME_ZONE = os.getenv('TZ', 'UTC')
TIME_ZONE = os.getenv("TZ", "UTC")
USE_I18N = True
@ -133,7 +134,7 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / "later42/static",
]
@ -143,70 +144,68 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
LOGIN_REDIRECT_URL = "index"
LOGOUT_REDIRECT_URL = "index"
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.TokenAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
}
# Celery Configuration Options
CELERY_TIMEZONE = TIME_ZONE
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_BROKER_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')
CELERY_RESULT_BACKEND = os.getenv('REDIS_URL', 'redis://localhost:6379')
CELERY_BROKER_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
CELERY_RESULT_BACKEND = os.getenv("REDIS_URL", "redis://localhost:6379")
URLS_PER_PAGE = 20
READABILITY_HOST = os.getenv('READABILITY_HOST', "http://localhost:8080/")
READABILITY_HOST = os.getenv("READABILITY_HOST", "http://localhost:8080/")
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', False)
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', None)
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', None)
EMAIL_PORT = os.getenv('EMAIL_PORT', '25')
EMAIL_FROM = os.getenv('EMAIL_FROM', 'noreply@later42.com')
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", False)
EMAIL_HOST = os.getenv("EMAIL_HOST", "smtp")
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", None)
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", None)
EMAIL_PORT = os.getenv("EMAIL_PORT", "25")
EMAIL_FROM = os.getenv("EMAIL_FROM", "noreply@later42.com")
AIRBRAKE = dict(
project_id=os.getenv('AIRBRAKE_PROJECT_ID', None),
project_key=os.getenv('AIRBRAKE_PROJECT_KEY', None),
environment=os.getenv('AIRBRAKE_ENVIRONMENT', 'development'),
project_id=os.getenv("AIRBRAKE_PROJECT_ID", None),
project_key=os.getenv("AIRBRAKE_PROJECT_KEY", None),
environment=os.getenv("AIRBRAKE_ENVIRONMENT", "development"),
)
if AIRBRAKE['project_id'] is not None and AIRBRAKE['project_key'] is not None:
MIDDLEWARE += ['pybrake.middleware.django.AirbrakeMiddleware']
if AIRBRAKE["project_id"] is not None and AIRBRAKE["project_key"] is not None:
MIDDLEWARE += ["pybrake.middleware.django.AirbrakeMiddleware"]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'airbrake': {
'level': 'ERROR',
'class': 'pybrake.LoggingHandler',
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"airbrake": {
"level": "ERROR",
"class": "pybrake.LoggingHandler",
},
},
'loggers': {
'app': {
'handlers': ['airbrake'],
'level': 'ERROR',
'propagate': True,
"loggers": {
"app": {
"handlers": ["airbrake"],
"level": "ERROR",
"propagate": True,
},
},
}
SENTRY_DSN = os.getenv('SENTRY_DSN', None)
SENTRY_DSN = os.getenv("SENTRY_DSN", None)
if SENTRY_DSN is not None:
import sentry_sdk

View File

@ -13,7 +13,11 @@ AIRBRAKE_PROJECT_ID = os.getenv("AIRBRAKE_PROJECT_ID", None)
AIRBRAKE_PROJECT_KEY = os.getenv("AIRBRAKE_PROJECT_KEY", None)
if AIRBRAKE_PROJECT_ID is not None and AIRBRAKE_PROJECT_KEY is not None:
notifier = pybrake.Notifier(project_id=AIRBRAKE_PROJECT_ID, project_key=AIRBRAKE_PROJECT_KEY, environment="celery")
notifier = pybrake.Notifier(
project_id=AIRBRAKE_PROJECT_ID,
project_key=AIRBRAKE_PROJECT_KEY,
environment="celery",
)
patch_celery(notifier)

View File

@ -1,25 +1,20 @@
from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.test import Client, TestCase
from django.urls import reverse
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
from later42.models.urls import URL
class ApiTests(TestCase):
def setUp(self) -> None:
self.username = 'testuser1'
self.email = 'testuser1@email.com'
self.password = 'password1234567QWERTY'
self.user = User.objects.create(
username=self.username,
email=self.email
)
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
self.user = User.objects.create(username=self.username, email=self.email)
self.user.set_password(self.password)
self.user.save()
self.url = 'https://google.com'
self.url = "https://google.com"
self.c = Client()
self.c.login(username=self.username, password=self.password)
@ -29,7 +24,7 @@ class ApiTests(TestCase):
def test_url_create(self):
token = Token.objects.get(user=self.user)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
response = client.post(reverse('urls') + f'?url={self.url}')
client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
response = client.post(reverse("urls") + f"?url={self.url}")
assert response.status_code == 200
self.assertContains(response, 'success')
self.assertContains(response, "success")

View File

@ -6,24 +6,21 @@ from rest_framework.authtoken.models import Token
class ApiKeyTests(TestCase):
def setUp(self) -> None:
self.username = 'testuser1'
self.email = 'testuser1@email.com'
self.password = 'password1234567QWERTY'
self.user = User.objects.create(
username=self.username,
email=self.email
)
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
self.user = User.objects.create(username=self.username, email=self.email)
self.user.set_password(self.password)
self.user.save()
self.c = Client()
self.c.login(username=self.username, password=self.password)
self.assertTrue(self.user.is_authenticated)
self.response = self.c.get(reverse('api_token'))
self.response = self.c.get(reverse("api_token"))
def test_api_key_creation(self):
assert self.response.status_code == 302
assert self.response.url == '/profile/'
assert self.response.url == "/profile/"
def test_api_key_created(self):
token = Token.objects.get(user=self.user)
@ -31,6 +28,6 @@ class ApiKeyTests(TestCase):
def test_api_key_reset(self):
token_old = Token.objects.get(user=self.user)
self.response = self.c.get(reverse('api_token'))
self.response = self.c.get(reverse("api_token"))
token_new = Token.objects.get(user=self.user)
assert token_old != token_new

View File

@ -1,20 +1,16 @@
from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.test import Client, TestCase
from django.urls import reverse
from rest_framework.authtoken.models import Token
from later42.models.urls import URL
class IndexTests(TestCase):
def setUp(self) -> None:
self.username = 'testuser1'
self.email = 'testuser1@email.com'
self.password = 'password1234567QWERTY'
self.user = User.objects.create(
username=self.username,
email=self.email
)
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
self.user = User.objects.create(username=self.username, email=self.email)
self.user.set_password(self.password)
self.user.save()
@ -23,11 +19,8 @@ class IndexTests(TestCase):
self.assertTrue(self.user.is_authenticated)
def test_url_delete(self):
url = URL.objects.create(
url='https://www.google.com/',
user=self.user
)
url = URL.objects.create(url="https://www.google.com/", user=self.user)
url.save()
self.c.delete(reverse('delete', kwargs={'url_id': url.id}))
self.c.delete(reverse("delete", kwargs={"url_id": url.id}))
url = URL.objects.filter(id=url.id)
assert len(url) == 0

View File

@ -5,13 +5,10 @@ from django.urls import reverse
class LoginPageTests(TestCase):
def setUp(self) -> None:
self.username = 'testuser1'
self.email = 'testuser1@email.com'
self.password = 'password1234567QWERTY'
self.user = User.objects.create(
username=self.username,
email=self.email
)
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
self.user = User.objects.create(username=self.username, email=self.email)
self.user.set_password(self.password)
self.user.save()
@ -20,22 +17,22 @@ class LoginPageTests(TestCase):
self.assertTrue(self.user.is_authenticated)
def test_login_page(self):
response = self.client.get(reverse('login'))
response = self.client.get(reverse("login"))
assert response.status_code == 200
def test_index_page_after_login(self):
response = self.c.get(reverse('index'))
response = self.c.get(reverse("index"))
assert response.status_code == 200
assert response.context['user'].is_authenticated
self.assertTemplateUsed(response, 'index.html')
self.assertContains(response, '/accounts/logout/')
assert response.context["user"].is_authenticated
self.assertTemplateUsed(response, "index.html")
self.assertContains(response, "/accounts/logout/")
def test_profile_page_template(self):
response = self.c.get(reverse('profile'))
response = self.c.get(reverse("profile"))
assert response.status_code == 200
self.assertTemplateUsed(response, 'profile.html')
self.assertTemplateUsed(response, "profile.html")
def test_api_key_creation_button(self):
response = self.c.get(reverse('profile'))
response = self.c.get(reverse("profile"))
assert response.status_code == 200
self.assertContains(response, 'id="createbutton"')

View File

@ -5,15 +5,15 @@ from django.urls import reverse
class PageTests(TestCase):
def test_index_page_url(self):
response = self.client.get(reverse('index'))
response = self.client.get(reverse("index"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='index.html')
self.assertTemplateUsed(response, template_name="index.html")
def test_about_page_url(self):
response = self.client.get(reverse('about'))
response = self.client.get(reverse("about"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='about.html')
self.assertTemplateUsed(response, template_name="about.html")
def test_profile_page_url_redirect(self):
response = self.client.get(reverse('profile'))
response = self.client.get(reverse("profile"))
self.assertEqual(response.status_code, 302)

View File

@ -6,27 +6,30 @@ from django.urls import reverse
class SignUpPageTests(TestCase):
def setUp(self) -> None:
self.username = 'testuser'
self.email = 'testuser@email.com'
self.password = 'password1234567QWERTY'
self.username = "testuser"
self.email = "testuser@email.com"
self.password = "password1234567QWERTY"
def test_signup_page_url(self):
response = self.client.get(reverse('signup'))
response = self.client.get(reverse("signup"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='registration/signup.html')
self.assertTemplateUsed(response, template_name="registration/signup.html")
def test_signup_page_view_name(self):
response = self.client.get(reverse('signup'))
response = self.client.get(reverse("signup"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='registration/signup.html')
self.assertTemplateUsed(response, template_name="registration/signup.html")
def test_signup_form(self):
response = self.client.post(reverse('signup'), data={
'username': self.username,
'email': self.email,
'password1': self.password,
'password2': self.password
})
response = self.client.post(
reverse("signup"),
data={
"username": self.username,
"email": self.email,
"password1": self.password,
"password2": self.password,
},
)
self.assertEqual(response.status_code, 200)
users = get_user_model().objects.all()

View File

@ -5,8 +5,9 @@ import six
class TokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (
six.text_type(user.pk) + six.text_type(timestamp) +
six.text_type(user.is_active)
six.text_type(user.pk)
+ six.text_type(timestamp)
+ six.text_type(user.is_active)
)

View File

@ -17,18 +17,29 @@ from django.contrib import admin
from django.urls import path, include
from django.contrib.auth.models import User
# from django.contrib.auth import views
from rest_framework import routers, serializers, viewsets
# from later42.forms import CustomLoginForm
from later42.views import account_activation, index, profile, api, api_token, reader, search, signup, about
from later42.views import (
account_activation,
index,
profile,
api,
api_token,
reader,
search,
signup,
about,
)
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['url', 'username', 'email', 'is_staff']
fields = ["url", "username", "email", "is_staff"]
class UserViewSet(viewsets.ModelViewSet):
@ -37,22 +48,25 @@ class UserViewSet(viewsets.ModelViewSet):
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r"users", UserViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', signup.register, name='signup'),
path(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
account_activation.activate, name='activate'),
path('accounts/', include('django.contrib.auth.urls')),
path('profile/', profile.get, name='profile'),
path('api/url/', api.URL.as_view(), name='urls'),
path('delete/<int:url_id>', index.delete, name='delete'),
path('api_token/', api_token.create, name='api_token'),
path('', index.get, name='index'),
path('about/', about.get, name='about'),
path('archive/', index.archive, name='archive'),
path('archive/<int:url_id>', index.archive, name='archive_url'),
path('reader/<int:url_id>', reader.get, name='reader'),
path('search/', search.search, name='search'),
path("admin/", admin.site.urls),
path("signup/", signup.register, name="signup"),
path(
r"^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
account_activation.activate,
name="activate",
),
path("accounts/", include("django.contrib.auth.urls")),
path("profile/", profile.get, name="profile"),
path("api/url/", api.URL.as_view(), name="urls"),
path("delete/<int:url_id>", index.delete, name="delete"),
path("api_token/", api_token.create, name="api_token"),
path("", index.get, name="index"),
path("about/", about.get, name="about"),
path("archive/", index.archive, name="archive"),
path("archive/<int:url_id>", index.archive, name="archive_url"),
path("reader/<int:url_id>", reader.get, name="reader"),
path("search/", search.search, name="search"),
]

View File

@ -2,4 +2,4 @@ from django.shortcuts import render
def get(request):
return render(request, 'about.html')
return render(request, "about.html")

View File

@ -1,16 +1,13 @@
import django
from django.core.mail import EmailMessage
from django.contrib.auth import login
from django.contrib.auth.models import User
from later42.tokens import account_activation_token
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate
from later42.forms import SignUpForm
from django.contrib.sites.shortcuts import get_current_site
from django.utils.encoding import force_bytes
from django.shortcuts import redirect
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode
from later42.tokens import account_activation_token
django.utils.encoding.force_text = force_str
@ -24,6 +21,6 @@ def activate(request, uidb64, token):
user.is_active = True
user.save()
login(request, user)
return redirect('index')
return redirect("index")
else:
return HttpResponse('Activation link is invalid!')
return HttpResponse("Activation link is invalid!")

View File

@ -1,36 +1,36 @@
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from rest_framework.response import Response
from rest_framework.views import APIView
from django.core.validators import URLValidator
from later42.models.urls import URL as URLModel
from later42.tasks import get_url_content_task
from django.conf import settings
def validate_url(to_validate:str) -> bool:
def validate_url(to_validate: str) -> bool:
validator = URLValidator()
try:
validator(to_validate)
return True
except ValidationError as exception:
except ValidationError:
return False
class URL(APIView):
def post(self, request, format=None):
if validate_url(request.GET.get('url')):
get_url_content_task.delay(request.GET.get('url'), request.user.id)
return Response({'status': 'success'})
if validate_url(request.GET.get("url")):
get_url_content_task.delay(request.GET.get("url"), request.user.id)
return Response({"status": "success"})
else:
return Response({'status': 'error'})
return Response({"status": "error"})
def delete(self, request, format=None):
id = request.GET.get('id')
id = request.GET.get("id")
if id:
url = URLModel.objects.filter(id=id).first()
if url:
url.archived = True
url.save()
return Response({'status': 'success'})
return Response({"status": "success"})
else:
return Response({'status': 'error'})
return Response({"status": "error"})

View File

@ -10,4 +10,4 @@ def create(request):
token.delete()
token = Token.objects.create(user=request.user)
token.save()
return redirect('profile')
return redirect("profile")

View File

@ -1,8 +1,7 @@
from multiprocessing import context
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.shortcuts import redirect, render
from later42.models.article import Article
from later42.models.urls import URL
@ -11,36 +10,36 @@ from later42.models.urls import URL
def get(request):
data = {}
try:
urls = URL.objects.filter(
user=request.user, archived=False).order_by('-id')
data = Article.objects.filter(
url__in=urls).select_related('url').order_by('-id')
urls = URL.objects.filter(user=request.user, archived=False).order_by("-id")
data = (
Article.objects.filter(url__in=urls).select_related("url").order_by("-id")
)
except:
urls = []
context = {'data': data}
return render(request, 'index.html', context)
context = {"data": data}
return render(request, "index.html", context)
@login_required
def archive(request, url_id=None):
if url_id:
URL.objects.filter(id=url_id, user=request.user).update(archived=True)
return redirect('index')
return redirect("index")
try:
urls = URL.objects.filter(
user=request.user, archived=True).order_by('-id')
data = Article.objects.filter(
url__in=urls).select_related('url').order_by('-id')
urls = URL.objects.filter(user=request.user, archived=True).order_by("-id")
data = (
Article.objects.filter(url__in=urls).select_related("url").order_by("-id")
)
paginator = Paginator(data, settings.URLS_PER_PAGE)
page_number = request.GET.get('page')
page_number = request.GET.get("page")
data = paginator.get_page(page_number)
except:
data = []
context = {'data': data}
return render(request, 'archive.html', context)
context = {"data": data}
return render(request, "archive.html", context)
@login_required
def delete(request, url_id):
URL.objects.filter(id=url_id, user=request.user).delete()
return redirect('archive')
return redirect("archive")

View File

@ -10,9 +10,8 @@ def get(request):
user = User.objects.get(username=request.user)
if token:
context = {'token': token[0], 'user': user}
context = {"token": token[0], "user": user}
else:
context = {'token': None, 'user': user}
return render(request, 'profile.html', context)
context = {"token": None, "user": user}
return render(request, "profile.html", context)

View File

@ -7,16 +7,15 @@ from later42.models.urls import URL
@login_required
def get(request, url_id=None):
url = URL.objects.get(
user=request.user, id=url_id)
url = URL.objects.get(user=request.user, id=url_id)
content = {}
try:
article = Article.objects.get(url=url)
content['title'] = article.title
content['url'] = url.url
content['rich_content'] = sanitize_img_size(article.content)
content["title"] = article.title
content["url"] = url.url
content["rich_content"] = sanitize_img_size(article.content)
except:
content = get_content(url.url)
content['rich_content'] = sanitize_img_size(content.article_html)
context = {'url': url, 'content': content}
return render(request, 'reader.html', context)
content["rich_content"] = sanitize_img_size(content.article_html)
context = {"url": url, "content": content}
return render(request, "reader.html", context)

View File

@ -5,25 +5,24 @@ from django.db.models import Q
from django.shortcuts import render
from later42.models.article import Article
from later42.models.urls import URL as URL
@login_required
def search(request):
pattern = request.POST.get('search')
pattern = request.POST.get("search")
context = {}
if request.method == 'GET':
return render(request, 'search.html', context)
elif request.method == 'POST':
urls = URL.objects.filter(
user=request.user).order_by('-id')
if request.method == "GET":
return render(request, "search.html", context)
elif request.method == "POST":
data = Article.objects.filter(
Q(title__contains=pattern) |
Q(content__contains=pattern) |
Q(short__contains=pattern) |
Q(url__url__contains=pattern), url__user_id=request.user.id).select_related('url')
Q(title__contains=pattern)
| Q(content__contains=pattern)
| Q(short__contains=pattern)
| Q(url__url__contains=pattern),
url__user_id=request.user.id,
).select_related("url")
paginator = Paginator(data, settings.URLS_PER_PAGE)
page_number = request.GET.get('page')
page_number = request.GET.get("page")
data = paginator.get_page(page_number)
context = {'data': data}
return render(request, 'search.html', context)
context = {"data": data}
return render(request, "search.html", context)

View File

@ -1,41 +1,43 @@
from django.contrib.auth import authenticate, login
from django.shortcuts import redirect, render
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_text
from django.core.mail import EmailMessage
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from later42.forms import SignUpForm
from later42.tokens import account_activation_token
def register(request):
if request.method == 'POST':
if request.method == "POST":
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False
user.save()
raw_password = form.cleaned_data.get('password1')
# raw_password = form.cleaned_data.get("password1")
current_site = get_current_site(request)
mail_subject = 'Later42: Активация аккаунта'
message = render_to_string('registration/email_activation.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
to_email = form.cleaned_data.get('email')
mail_subject = "Later42: Активация аккаунта"
message = render_to_string(
"registration/email_activation.html",
{
"user": user,
"domain": current_site.domain,
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
"token": account_activation_token.make_token(user),
},
)
to_email = form.cleaned_data.get("email")
email = EmailMessage(
mail_subject, message, to=[
to_email], from_email=settings.EMAIL_FROM
mail_subject, message, to=[to_email], from_email=settings.EMAIL_FROM
)
email.send()
return render(request, 'registration/email_sent.html')
return render(request, "registration/email_sent.html")
else:
form = SignUpForm()
return render(request, 'registration/signup.html', {'form': form})
return render(request, "registration/signup.html", {"form": form})

View File

@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'later42.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "later42.settings")
application = get_wsgi_application()

View File

@ -6,7 +6,7 @@ import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'later42.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "later42.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -18,5 +18,5 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -1,8 +1,8 @@
-r requirements.txt
black
django-stubs
isort
mypy
pylint
pylint-django
mypy
django-stubs

View File

@ -1,13 +1,13 @@
bs4==0.0.1
celery[redis]==5.2.7
Django==4.1.3
djangorestframework==3.14.0
gunicorn==20.1.0
loguru==0.6.0
bs4==0.0.1
requests==2.28.1
djangorestframework==3.14.0
whitenoise==6.2.0
psycopg2-binary==2.9.5
six==1.16.0
celery[redis]==5.2.7
pybrake==1.10.0
sentry-sdk==1.11.0
newspaper3k==0.2.8
psycopg2-binary==2.9.5
pybrake==1.10.0
requests==2.28.1
sentry-sdk==1.11.0
six==1.16.0
whitenoise==6.2.0