This commit is contained in:
parent
500f4dfbdd
commit
520acb9477
@ -39,7 +39,7 @@ indent_style = tab
|
|||||||
[docs/**.txt]
|
[docs/**.txt]
|
||||||
max_line_length = 79
|
max_line_length = 79
|
||||||
|
|
||||||
[*.yml]
|
[*.yaml]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
|
5
.flake8
Normal file
5
.flake8
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[flake8]
|
||||||
|
max-line-length = 120
|
||||||
|
per-file-ignores =
|
||||||
|
later42/views/index.py: E722
|
||||||
|
later42/views/reader.py: E722
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -139,4 +139,4 @@ dmypy.json
|
|||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
db/*.sqlite3
|
db/*.sqlite3
|
||||||
static/*
|
static/*
|
||||||
|
27
.pre-commit-config.yaml
Normal file
27
.pre-commit-config.yaml
Normal 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
|
@ -29,7 +29,7 @@ SECRET_KEY | | The secret key to make hashes
|
|||||||
DOMAIN | localhost | Domain name where the site is hosted
|
DOMAIN | localhost | Domain name where the site is hosted
|
||||||
DB_TYPE | SQLite | Type of DB (SQLite, Postgres)
|
DB_TYPE | SQLite | Type of DB (SQLite, Postgres)
|
||||||
DB_HOST | None | Host for Postgres DB
|
DB_HOST | None | Host for Postgres DB
|
||||||
DB_NAME | None | Name for Postgres DB
|
DB_NAME | None | Name for Postgres DB
|
||||||
DB_USER | None | User for Postgres DB
|
DB_USER | None | User for Postgres DB
|
||||||
DB_PASS | None | Password for Postgres DB
|
DB_PASS | None | Password for Postgres DB
|
||||||
REDIS_URL | redis://localhost:6379 | Connection string for Redis
|
REDIS_URL | redis://localhost:6379 | Connection string for Redis
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
from .celery import app as celery_app
|
from .celery import app as celery_app
|
||||||
|
|
||||||
__all__ = ('celery_app',)
|
__all__ = ("celery_app",)
|
||||||
|
@ -2,4 +2,4 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from .models.urls import URL
|
from .models.urls import URL
|
||||||
|
|
||||||
admin.site.register(URL)
|
admin.site.register(URL)
|
||||||
|
@ -11,6 +11,6 @@ import os
|
|||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
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()
|
application = get_asgi_application()
|
||||||
|
@ -3,15 +3,15 @@ import os
|
|||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
# Set the default Django settings module for the 'celery' program.
|
# 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
|
# Using a string here means the worker doesn't have to serialize
|
||||||
# the configuration object to child processes.
|
# the configuration object to child processes.
|
||||||
# - namespace='CELERY' means all celery-related configuration keys
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
# should have a `CELERY_` prefix.
|
# 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.
|
# Load task modules from all registered Django apps.
|
||||||
app.autodiscover_tasks()
|
app.autodiscover_tasks()
|
||||||
@ -19,4 +19,4 @@ app.autodiscover_tasks()
|
|||||||
|
|
||||||
@app.task(bind=True)
|
@app.task(bind=True)
|
||||||
def debug_task(self):
|
def debug_task(self):
|
||||||
print(f'Request: {self.request!r}')
|
print(f"Request: {self.request!r}")
|
||||||
|
@ -7,29 +7,24 @@ from django.contrib.auth.forms import UserCreationForm
|
|||||||
class SignUpForm(UserCreationForm):
|
class SignUpForm(UserCreationForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['username'].widget.attrs.update(
|
self.fields["username"].widget.attrs.update({"class": "form-control"})
|
||||||
{'class': 'form-control'}
|
self.fields["password1"].widget.attrs.update({"class": "form-control"})
|
||||||
)
|
self.fields["password2"].widget.attrs.update({"class": "form-control"})
|
||||||
self.fields['password1'].widget.attrs.update(
|
self.fields["username"].label = "Логин"
|
||||||
{'class': 'form-control'}
|
|
||||||
)
|
|
||||||
self.fields['password2'].widget.attrs.update(
|
|
||||||
{'class': 'form-control'}
|
|
||||||
)
|
|
||||||
self.fields['username'].label = 'Логин'
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('username', 'email', 'password1', 'password2',)
|
fields = (
|
||||||
|
"username",
|
||||||
|
"email",
|
||||||
|
"password1",
|
||||||
|
"password2",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomLoginForm(AuthenticationForm):
|
class CustomLoginForm(AuthenticationForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['username'].widget.attrs.update(
|
self.fields["username"].widget.attrs.update({"class": "form-control"})
|
||||||
{'class': 'form-control'}
|
self.fields["password"].widget.attrs.update({"class": "form-control"})
|
||||||
)
|
self.fields["username"].label = "Логин"
|
||||||
self.fields['password'].widget.attrs.update(
|
|
||||||
{'class': 'form-control'}
|
|
||||||
)
|
|
||||||
self.fields['username'].label = 'Логин'
|
|
||||||
|
@ -3,10 +3,10 @@ from newspaper import Article, Config
|
|||||||
|
|
||||||
|
|
||||||
def sanitize_img_size(html: str):
|
def sanitize_img_size(html: str):
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
for img in soup.find_all('img'):
|
for img in soup.find_all("img"):
|
||||||
img['width'] = '100%'
|
img["width"] = "100%"
|
||||||
img['height'] = 'auto'
|
img["height"] = "auto"
|
||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
class ExceptionLoggingMiddleware(object):
|
class ExceptionLoggingMiddleware(object):
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
|
|
||||||
# Code to be executed for each request before
|
# Code to be executed for each request before
|
||||||
# the view (and later middleware) are called.
|
# the view (and later middleware) are called.
|
||||||
print(request.body)
|
print(request.body)
|
||||||
@ -15,4 +13,4 @@ class ExceptionLoggingMiddleware(object):
|
|||||||
|
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@ -15,12 +14,23 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='URL',
|
name="URL",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
(
|
||||||
('url', models.CharField(max_length=2000)),
|
"id",
|
||||||
('title', models.CharField(max_length=2000)),
|
models.AutoField(
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -4,15 +4,14 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('later42', '0001_initial'),
|
("later42", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='url',
|
model_name="url",
|
||||||
name='archived',
|
name="archived",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -4,15 +4,14 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('later42', '0002_url_archived'),
|
("later42", "0002_url_archived"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='url',
|
model_name="url",
|
||||||
name='content',
|
name="content",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -5,18 +5,27 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('later42', '0003_url_content'),
|
("later42", "0003_url_content"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Articles',
|
name="Articles",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
(
|
||||||
('content', models.TextField(blank=True, null=True)),
|
"id",
|
||||||
('url', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='later42.url')),
|
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"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -4,14 +4,13 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('later42', '0004_articles'),
|
("later42", "0004_articles"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RenameModel(
|
migrations.RenameModel(
|
||||||
old_name='Articles',
|
old_name="Articles",
|
||||||
new_name='Article',
|
new_name="Article",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -4,28 +4,27 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('later42', '0005_rename_articles_article'),
|
("later42", "0005_rename_articles_article"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='url',
|
model_name="url",
|
||||||
name='content',
|
name="content",
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='url',
|
model_name="url",
|
||||||
name='title',
|
name="title",
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='article',
|
model_name="article",
|
||||||
name='short',
|
name="short",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='article',
|
model_name="article",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(blank=True, max_length=2000, null=True),
|
field=models.CharField(blank=True, max_length=2000, null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -4,15 +4,14 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
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 = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='article',
|
model_name="article",
|
||||||
name='img',
|
name="img",
|
||||||
field=models.URLField(blank=True, null=True),
|
field=models.URLField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
User._meta.get_field('email')._unique = True
|
User._meta.get_field("email")._unique = True
|
||||||
|
|
||||||
|
|
||||||
class URL(models.Model):
|
class URL(models.Model):
|
||||||
|
@ -5,10 +5,10 @@ from rest_framework import serializers
|
|||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['url', 'username', 'email', 'groups']
|
fields = ["url", "username", "email", "groups"]
|
||||||
|
|
||||||
|
|
||||||
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ['url', 'name']
|
fields = ["url", "name"]
|
||||||
|
@ -21,80 +21,81 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = os.getenv(
|
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!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = os.getenv('DEBUG', False)
|
DEBUG = os.getenv("DEBUG", False)
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ["*"]
|
||||||
CSRF_TRUSTED_ORIGINS = ['https://' + os.getenv('DOMAIN', 'localhost')]
|
CSRF_TRUSTED_ORIGINS = ["https://" + os.getenv("DOMAIN", "localhost")]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'later42',
|
"later42",
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
'rest_framework',
|
"rest_framework",
|
||||||
'rest_framework.authtoken',
|
"rest_framework.authtoken",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
# 'later42.logging.debug.ExceptionLoggingMiddleware',
|
# 'later42.logging.debug.ExceptionLoggingMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'later42.urls'
|
ROOT_URLCONF = "later42.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [],
|
"DIRS": [],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'later42.wsgi.application'
|
WSGI_APPLICATION = "later42.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
'NAME': BASE_DIR / 'db/db.sqlite3',
|
"NAME": BASE_DIR / "db/db.sqlite3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.getenv('DB_TYPE', 'SQLite') == 'postgres':
|
if os.getenv("DB_TYPE", "SQLite") == "postgres":
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
'HOST': os.environ.get('DB_HOST', None),
|
"HOST": os.environ.get("DB_HOST", None),
|
||||||
'NAME': os.environ.get('DB_NAME', None),
|
"NAME": os.environ.get("DB_NAME", None),
|
||||||
'USER': os.environ.get('DB_USER', None),
|
"USER": os.environ.get("DB_USER", None),
|
||||||
'PASSWORD': os.environ.get('DB_PASS', None),
|
"PASSWORD": os.environ.get("DB_PASS", None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,16 +105,16 @@ if os.getenv('DB_TYPE', 'SQLite') == 'postgres':
|
|||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
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
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.1/topics/i18n/
|
# 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
|
USE_I18N = True
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ USE_TZ = True
|
|||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/4.1/howto/static-files/
|
# https://docs.djangoproject.com/en/4.1/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = "static/"
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
BASE_DIR / "later42/static",
|
BASE_DIR / "later42/static",
|
||||||
]
|
]
|
||||||
@ -143,70 +144,68 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
|||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
# 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"
|
LOGIN_REDIRECT_URL = "index"
|
||||||
LOGOUT_REDIRECT_URL = "index"
|
LOGOUT_REDIRECT_URL = "index"
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
||||||
'PAGE_SIZE': 10,
|
"PAGE_SIZE": 10,
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
"rest_framework.authentication.TokenAuthentication",
|
||||||
),
|
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
|
||||||
'rest_framework.permissions.IsAuthenticated',
|
|
||||||
),
|
),
|
||||||
|
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Celery Configuration Options
|
# Celery Configuration Options
|
||||||
CELERY_TIMEZONE = TIME_ZONE
|
CELERY_TIMEZONE = TIME_ZONE
|
||||||
CELERY_TASK_TRACK_STARTED = True
|
CELERY_TASK_TRACK_STARTED = True
|
||||||
CELERY_TASK_TIME_LIMIT = 30 * 60
|
CELERY_TASK_TIME_LIMIT = 30 * 60
|
||||||
CELERY_BROKER_URL = 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')
|
CELERY_RESULT_BACKEND = os.getenv("REDIS_URL", "redis://localhost:6379")
|
||||||
|
|
||||||
URLS_PER_PAGE = 20
|
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:
|
if DEBUG:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', False)
|
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", False)
|
||||||
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp')
|
EMAIL_HOST = os.getenv("EMAIL_HOST", "smtp")
|
||||||
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', None)
|
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", None)
|
||||||
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', None)
|
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", None)
|
||||||
EMAIL_PORT = os.getenv('EMAIL_PORT', '25')
|
EMAIL_PORT = os.getenv("EMAIL_PORT", "25")
|
||||||
EMAIL_FROM = os.getenv('EMAIL_FROM', 'noreply@later42.com')
|
EMAIL_FROM = os.getenv("EMAIL_FROM", "noreply@later42.com")
|
||||||
|
|
||||||
|
|
||||||
AIRBRAKE = dict(
|
AIRBRAKE = dict(
|
||||||
project_id=os.getenv('AIRBRAKE_PROJECT_ID', None),
|
project_id=os.getenv("AIRBRAKE_PROJECT_ID", None),
|
||||||
project_key=os.getenv('AIRBRAKE_PROJECT_KEY', None),
|
project_key=os.getenv("AIRBRAKE_PROJECT_KEY", None),
|
||||||
environment=os.getenv('AIRBRAKE_ENVIRONMENT', 'development'),
|
environment=os.getenv("AIRBRAKE_ENVIRONMENT", "development"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if AIRBRAKE['project_id'] is not None and AIRBRAKE['project_key'] is not None:
|
if AIRBRAKE["project_id"] is not None and AIRBRAKE["project_key"] is not None:
|
||||||
MIDDLEWARE += ['pybrake.middleware.django.AirbrakeMiddleware']
|
MIDDLEWARE += ["pybrake.middleware.django.AirbrakeMiddleware"]
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
"version": 1,
|
||||||
'disable_existing_loggers': False,
|
"disable_existing_loggers": False,
|
||||||
'handlers': {
|
"handlers": {
|
||||||
'airbrake': {
|
"airbrake": {
|
||||||
'level': 'ERROR',
|
"level": "ERROR",
|
||||||
'class': 'pybrake.LoggingHandler',
|
"class": "pybrake.LoggingHandler",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'loggers': {
|
"loggers": {
|
||||||
'app': {
|
"app": {
|
||||||
'handlers': ['airbrake'],
|
"handlers": ["airbrake"],
|
||||||
'level': 'ERROR',
|
"level": "ERROR",
|
||||||
'propagate': True,
|
"propagate": True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SENTRY_DSN = os.getenv('SENTRY_DSN', None)
|
SENTRY_DSN = os.getenv("SENTRY_DSN", None)
|
||||||
|
|
||||||
if SENTRY_DSN is not None:
|
if SENTRY_DSN is not None:
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
@ -1 +1 @@
|
|||||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||||
|
@ -11245,4 +11245,4 @@ header.masthead .site-heading .h1 {
|
|||||||
.post-preview > a > .post-title {
|
.post-preview > a > .post-title {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,11 @@ AIRBRAKE_PROJECT_ID = os.getenv("AIRBRAKE_PROJECT_ID", None)
|
|||||||
AIRBRAKE_PROJECT_KEY = os.getenv("AIRBRAKE_PROJECT_KEY", None)
|
AIRBRAKE_PROJECT_KEY = os.getenv("AIRBRAKE_PROJECT_KEY", None)
|
||||||
|
|
||||||
if AIRBRAKE_PROJECT_ID is not None and AIRBRAKE_PROJECT_KEY is not 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)
|
patch_celery(notifier)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,4 +32,4 @@
|
|||||||
<span class="fa-solid fa-arrow-up-right-from-square"></span> Открыть <a href="{{ url.url }}">оригинал ссылки</a>.
|
<span class="fa-solid fa-arrow-up-right-from-square"></span> Открыть <a href="{{ url.url }}">оригинал ссылки</a>.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
Ссылка для активации аккаунта:
|
Ссылка для активации аккаунта:
|
||||||
https://{{ domain }}{% url 'activate' uidb64=uid token=token %}
|
https://{{ domain }}{% url 'activate' uidb64=uid token=token %}
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Пароль успешно изменен.</p>
|
<p>Пароль успешно изменен.</p>
|
||||||
<p>Перейти в <a href="{% url 'profile' %}">Профиль</a>.</p>
|
<p>Перейти в <a href="{% url 'profile' %}">Профиль</a>.</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Пароль успешно изменен.</p>
|
<p>Пароль успешно изменен.</p>
|
||||||
<p>Вы можете <a href="{% url 'login' %}">войти</a>.</p>
|
<p>Вы можете <a href="{% url 'login' %}">войти</a>.</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -29,4 +29,4 @@
|
|||||||
<p>Ссылка неверная или уже была использована.<br />
|
<p>Ссылка неверная или уже была использована.<br />
|
||||||
Попробуйте сбросить пароль еще раз.</p>
|
Попробуйте сбросить пароль еще раз.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Сообщение успешно отправлено.</p>
|
<p>Сообщение успешно отправлено.</p>
|
||||||
<p>Вы должны получить его в скором времени.</p>
|
<p>Вы должны получить его в скором времени.</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
from django.contrib.auth.models import User
|
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 django.urls import reverse
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
from later42.models.urls import URL
|
|
||||||
|
|
||||||
|
|
||||||
class ApiTests(TestCase):
|
class ApiTests(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.username = 'testuser1'
|
self.username = "testuser1"
|
||||||
self.email = 'testuser1@email.com'
|
self.email = "testuser1@email.com"
|
||||||
self.password = 'password1234567QWERTY'
|
self.password = "password1234567QWERTY"
|
||||||
self.user = User.objects.create(
|
self.user = User.objects.create(username=self.username, email=self.email)
|
||||||
username=self.username,
|
|
||||||
email=self.email
|
|
||||||
)
|
|
||||||
self.user.set_password(self.password)
|
self.user.set_password(self.password)
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
self.url = 'https://google.com'
|
self.url = "https://google.com"
|
||||||
|
|
||||||
self.c = Client()
|
self.c = Client()
|
||||||
self.c.login(username=self.username, password=self.password)
|
self.c.login(username=self.username, password=self.password)
|
||||||
@ -29,7 +24,7 @@ class ApiTests(TestCase):
|
|||||||
def test_url_create(self):
|
def test_url_create(self):
|
||||||
token = Token.objects.get(user=self.user)
|
token = Token.objects.get(user=self.user)
|
||||||
client = APIClient()
|
client = APIClient()
|
||||||
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
|
client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
|
||||||
response = client.post(reverse('urls') + f'?url={self.url}')
|
response = client.post(reverse("urls") + f"?url={self.url}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertContains(response, 'success')
|
self.assertContains(response, "success")
|
||||||
|
@ -6,24 +6,21 @@ from rest_framework.authtoken.models import Token
|
|||||||
|
|
||||||
class ApiKeyTests(TestCase):
|
class ApiKeyTests(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.username = 'testuser1'
|
self.username = "testuser1"
|
||||||
self.email = 'testuser1@email.com'
|
self.email = "testuser1@email.com"
|
||||||
self.password = 'password1234567QWERTY'
|
self.password = "password1234567QWERTY"
|
||||||
self.user = User.objects.create(
|
self.user = User.objects.create(username=self.username, email=self.email)
|
||||||
username=self.username,
|
|
||||||
email=self.email
|
|
||||||
)
|
|
||||||
self.user.set_password(self.password)
|
self.user.set_password(self.password)
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
self.c = Client()
|
self.c = Client()
|
||||||
self.c.login(username=self.username, password=self.password)
|
self.c.login(username=self.username, password=self.password)
|
||||||
self.assertTrue(self.user.is_authenticated)
|
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):
|
def test_api_key_creation(self):
|
||||||
assert self.response.status_code == 302
|
assert self.response.status_code == 302
|
||||||
assert self.response.url == '/profile/'
|
assert self.response.url == "/profile/"
|
||||||
|
|
||||||
def test_api_key_created(self):
|
def test_api_key_created(self):
|
||||||
token = Token.objects.get(user=self.user)
|
token = Token.objects.get(user=self.user)
|
||||||
@ -31,6 +28,6 @@ class ApiKeyTests(TestCase):
|
|||||||
|
|
||||||
def test_api_key_reset(self):
|
def test_api_key_reset(self):
|
||||||
token_old = Token.objects.get(user=self.user)
|
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)
|
token_new = Token.objects.get(user=self.user)
|
||||||
assert token_old != token_new
|
assert token_old != token_new
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
from django.contrib.auth.models import User
|
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 django.urls import reverse
|
||||||
from rest_framework.authtoken.models import Token
|
|
||||||
|
|
||||||
from later42.models.urls import URL
|
from later42.models.urls import URL
|
||||||
|
|
||||||
|
|
||||||
class IndexTests(TestCase):
|
class IndexTests(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.username = 'testuser1'
|
self.username = "testuser1"
|
||||||
self.email = 'testuser1@email.com'
|
self.email = "testuser1@email.com"
|
||||||
self.password = 'password1234567QWERTY'
|
self.password = "password1234567QWERTY"
|
||||||
self.user = User.objects.create(
|
self.user = User.objects.create(username=self.username, email=self.email)
|
||||||
username=self.username,
|
|
||||||
email=self.email
|
|
||||||
)
|
|
||||||
self.user.set_password(self.password)
|
self.user.set_password(self.password)
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
@ -23,11 +19,8 @@ class IndexTests(TestCase):
|
|||||||
self.assertTrue(self.user.is_authenticated)
|
self.assertTrue(self.user.is_authenticated)
|
||||||
|
|
||||||
def test_url_delete(self):
|
def test_url_delete(self):
|
||||||
url = URL.objects.create(
|
url = URL.objects.create(url="https://www.google.com/", user=self.user)
|
||||||
url='https://www.google.com/',
|
|
||||||
user=self.user
|
|
||||||
)
|
|
||||||
url.save()
|
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)
|
url = URL.objects.filter(id=url.id)
|
||||||
assert len(url) == 0
|
assert len(url) == 0
|
||||||
|
@ -5,13 +5,10 @@ from django.urls import reverse
|
|||||||
|
|
||||||
class LoginPageTests(TestCase):
|
class LoginPageTests(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.username = 'testuser1'
|
self.username = "testuser1"
|
||||||
self.email = 'testuser1@email.com'
|
self.email = "testuser1@email.com"
|
||||||
self.password = 'password1234567QWERTY'
|
self.password = "password1234567QWERTY"
|
||||||
self.user = User.objects.create(
|
self.user = User.objects.create(username=self.username, email=self.email)
|
||||||
username=self.username,
|
|
||||||
email=self.email
|
|
||||||
)
|
|
||||||
self.user.set_password(self.password)
|
self.user.set_password(self.password)
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
@ -20,22 +17,22 @@ class LoginPageTests(TestCase):
|
|||||||
self.assertTrue(self.user.is_authenticated)
|
self.assertTrue(self.user.is_authenticated)
|
||||||
|
|
||||||
def test_login_page(self):
|
def test_login_page(self):
|
||||||
response = self.client.get(reverse('login'))
|
response = self.client.get(reverse("login"))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
def test_index_page_after_login(self):
|
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.status_code == 200
|
||||||
assert response.context['user'].is_authenticated
|
assert response.context["user"].is_authenticated
|
||||||
self.assertTemplateUsed(response, 'index.html')
|
self.assertTemplateUsed(response, "index.html")
|
||||||
self.assertContains(response, '/accounts/logout/')
|
self.assertContains(response, "/accounts/logout/")
|
||||||
|
|
||||||
def test_profile_page_template(self):
|
def test_profile_page_template(self):
|
||||||
response = self.c.get(reverse('profile'))
|
response = self.c.get(reverse("profile"))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertTemplateUsed(response, 'profile.html')
|
self.assertTemplateUsed(response, "profile.html")
|
||||||
|
|
||||||
def test_api_key_creation_button(self):
|
def test_api_key_creation_button(self):
|
||||||
response = self.c.get(reverse('profile'))
|
response = self.c.get(reverse("profile"))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertContains(response, 'id="createbutton"')
|
self.assertContains(response, 'id="createbutton"')
|
||||||
|
@ -5,15 +5,15 @@ from django.urls import reverse
|
|||||||
|
|
||||||
class PageTests(TestCase):
|
class PageTests(TestCase):
|
||||||
def test_index_page_url(self):
|
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.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):
|
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.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):
|
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)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
@ -6,27 +6,30 @@ from django.urls import reverse
|
|||||||
|
|
||||||
class SignUpPageTests(TestCase):
|
class SignUpPageTests(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.username = 'testuser'
|
self.username = "testuser"
|
||||||
self.email = 'testuser@email.com'
|
self.email = "testuser@email.com"
|
||||||
self.password = 'password1234567QWERTY'
|
self.password = "password1234567QWERTY"
|
||||||
|
|
||||||
def test_signup_page_url(self):
|
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.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):
|
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.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):
|
def test_signup_form(self):
|
||||||
response = self.client.post(reverse('signup'), data={
|
response = self.client.post(
|
||||||
'username': self.username,
|
reverse("signup"),
|
||||||
'email': self.email,
|
data={
|
||||||
'password1': self.password,
|
"username": self.username,
|
||||||
'password2': self.password
|
"email": self.email,
|
||||||
})
|
"password1": self.password,
|
||||||
|
"password2": self.password,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
users = get_user_model().objects.all()
|
users = get_user_model().objects.all()
|
||||||
|
@ -5,8 +5,9 @@ import six
|
|||||||
class TokenGenerator(PasswordResetTokenGenerator):
|
class TokenGenerator(PasswordResetTokenGenerator):
|
||||||
def _make_hash_value(self, user, timestamp):
|
def _make_hash_value(self, user, timestamp):
|
||||||
return (
|
return (
|
||||||
six.text_type(user.pk) + six.text_type(timestamp) +
|
six.text_type(user.pk)
|
||||||
six.text_type(user.is_active)
|
+ six.text_type(timestamp)
|
||||||
|
+ six.text_type(user.is_active)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,18 +17,29 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
# from django.contrib.auth import views
|
# from django.contrib.auth import views
|
||||||
|
|
||||||
from rest_framework import routers, serializers, viewsets
|
from rest_framework import routers, serializers, viewsets
|
||||||
|
|
||||||
# from later42.forms import CustomLoginForm
|
# 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 UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['url', 'username', 'email', 'is_staff']
|
fields = ["url", "username", "email", "is_staff"]
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
@ -37,22 +48,25 @@ class UserViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'users', UserViewSet)
|
router.register(r"users", UserViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path('signup/', signup.register, name='signup'),
|
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})/$',
|
path(
|
||||||
account_activation.activate, name='activate'),
|
r"^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
|
||||||
path('accounts/', include('django.contrib.auth.urls')),
|
account_activation.activate,
|
||||||
path('profile/', profile.get, name='profile'),
|
name="activate",
|
||||||
path('api/url/', api.URL.as_view(), name='urls'),
|
),
|
||||||
path('delete/<int:url_id>', index.delete, name='delete'),
|
path("accounts/", include("django.contrib.auth.urls")),
|
||||||
path('api_token/', api_token.create, name='api_token'),
|
path("profile/", profile.get, name="profile"),
|
||||||
path('', index.get, name='index'),
|
path("api/url/", api.URL.as_view(), name="urls"),
|
||||||
path('about/', about.get, name='about'),
|
path("delete/<int:url_id>", index.delete, name="delete"),
|
||||||
path('archive/', index.archive, name='archive'),
|
path("api_token/", api_token.create, name="api_token"),
|
||||||
path('archive/<int:url_id>', index.archive, name='archive_url'),
|
path("", index.get, name="index"),
|
||||||
path('reader/<int:url_id>', reader.get, name='reader'),
|
path("about/", about.get, name="about"),
|
||||||
path('search/', search.search, name='search'),
|
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"),
|
||||||
]
|
]
|
||||||
|
@ -2,4 +2,4 @@ from django.shortcuts import render
|
|||||||
|
|
||||||
|
|
||||||
def get(request):
|
def get(request):
|
||||||
return render(request, 'about.html')
|
return render(request, "about.html")
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import django
|
import django
|
||||||
from django.core.mail import EmailMessage
|
from django.contrib.auth import login
|
||||||
from django.contrib.auth.models import User
|
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.http import HttpResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import 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.utils.encoding import force_str
|
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
|
django.utils.encoding.force_text = force_str
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +21,6 @@ def activate(request, uidb64, token):
|
|||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.save()
|
user.save()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect('index')
|
return redirect("index")
|
||||||
else:
|
else:
|
||||||
return HttpResponse('Activation link is invalid!')
|
return HttpResponse("Activation link is invalid!")
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import URLValidator
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.core.validators import URLValidator
|
|
||||||
from later42.models.urls import URL as URLModel
|
from later42.models.urls import URL as URLModel
|
||||||
from later42.tasks import get_url_content_task
|
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()
|
validator = URLValidator()
|
||||||
try:
|
try:
|
||||||
validator(to_validate)
|
validator(to_validate)
|
||||||
return True
|
return True
|
||||||
except ValidationError as exception:
|
except ValidationError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class URL(APIView):
|
class URL(APIView):
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
if validate_url(request.GET.get('url')):
|
if validate_url(request.GET.get("url")):
|
||||||
get_url_content_task.delay(request.GET.get('url'), request.user.id)
|
get_url_content_task.delay(request.GET.get("url"), request.user.id)
|
||||||
return Response({'status': 'success'})
|
return Response({"status": "success"})
|
||||||
else:
|
else:
|
||||||
return Response({'status': 'error'})
|
return Response({"status": "error"})
|
||||||
|
|
||||||
def delete(self, request, format=None):
|
def delete(self, request, format=None):
|
||||||
id = request.GET.get('id')
|
id = request.GET.get("id")
|
||||||
if id:
|
if id:
|
||||||
url = URLModel.objects.filter(id=id).first()
|
url = URLModel.objects.filter(id=id).first()
|
||||||
if url:
|
if url:
|
||||||
url.archived = True
|
url.archived = True
|
||||||
url.save()
|
url.save()
|
||||||
return Response({'status': 'success'})
|
return Response({"status": "success"})
|
||||||
else:
|
else:
|
||||||
return Response({'status': 'error'})
|
return Response({"status": "error"})
|
||||||
|
@ -10,4 +10,4 @@ def create(request):
|
|||||||
token.delete()
|
token.delete()
|
||||||
token = Token.objects.create(user=request.user)
|
token = Token.objects.create(user=request.user)
|
||||||
token.save()
|
token.save()
|
||||||
return redirect('profile')
|
return redirect("profile")
|
||||||
|
@ -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.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.article import Article
|
||||||
from later42.models.urls import URL
|
from later42.models.urls import URL
|
||||||
@ -11,36 +10,36 @@ from later42.models.urls import URL
|
|||||||
def get(request):
|
def get(request):
|
||||||
data = {}
|
data = {}
|
||||||
try:
|
try:
|
||||||
urls = URL.objects.filter(
|
urls = URL.objects.filter(user=request.user, archived=False).order_by("-id")
|
||||||
user=request.user, archived=False).order_by('-id')
|
data = (
|
||||||
data = Article.objects.filter(
|
Article.objects.filter(url__in=urls).select_related("url").order_by("-id")
|
||||||
url__in=urls).select_related('url').order_by('-id')
|
)
|
||||||
except:
|
except:
|
||||||
urls = []
|
urls = []
|
||||||
context = {'data': data}
|
context = {"data": data}
|
||||||
return render(request, 'index.html', context)
|
return render(request, "index.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def archive(request, url_id=None):
|
def archive(request, url_id=None):
|
||||||
if url_id:
|
if url_id:
|
||||||
URL.objects.filter(id=url_id, user=request.user).update(archived=True)
|
URL.objects.filter(id=url_id, user=request.user).update(archived=True)
|
||||||
return redirect('index')
|
return redirect("index")
|
||||||
try:
|
try:
|
||||||
urls = URL.objects.filter(
|
urls = URL.objects.filter(user=request.user, archived=True).order_by("-id")
|
||||||
user=request.user, archived=True).order_by('-id')
|
data = (
|
||||||
data = Article.objects.filter(
|
Article.objects.filter(url__in=urls).select_related("url").order_by("-id")
|
||||||
url__in=urls).select_related('url').order_by('-id')
|
)
|
||||||
paginator = Paginator(data, settings.URLS_PER_PAGE)
|
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)
|
data = paginator.get_page(page_number)
|
||||||
except:
|
except:
|
||||||
data = []
|
data = []
|
||||||
context = {'data': data}
|
context = {"data": data}
|
||||||
return render(request, 'archive.html', context)
|
return render(request, "archive.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def delete(request, url_id):
|
def delete(request, url_id):
|
||||||
URL.objects.filter(id=url_id, user=request.user).delete()
|
URL.objects.filter(id=url_id, user=request.user).delete()
|
||||||
return redirect('archive')
|
return redirect("archive")
|
||||||
|
@ -10,9 +10,8 @@ def get(request):
|
|||||||
user = User.objects.get(username=request.user)
|
user = User.objects.get(username=request.user)
|
||||||
|
|
||||||
if token:
|
if token:
|
||||||
context = {'token': token[0], 'user': user}
|
context = {"token": token[0], "user": user}
|
||||||
else:
|
else:
|
||||||
context = {'token': None, 'user': user}
|
context = {"token": None, "user": user}
|
||||||
|
|
||||||
return render(request, 'profile.html', context)
|
|
||||||
|
|
||||||
|
return render(request, "profile.html", context)
|
||||||
|
@ -7,16 +7,15 @@ from later42.models.urls import URL
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def get(request, url_id=None):
|
def get(request, url_id=None):
|
||||||
url = URL.objects.get(
|
url = URL.objects.get(user=request.user, id=url_id)
|
||||||
user=request.user, id=url_id)
|
|
||||||
content = {}
|
content = {}
|
||||||
try:
|
try:
|
||||||
article = Article.objects.get(url=url)
|
article = Article.objects.get(url=url)
|
||||||
content['title'] = article.title
|
content["title"] = article.title
|
||||||
content['url'] = url.url
|
content["url"] = url.url
|
||||||
content['rich_content'] = sanitize_img_size(article.content)
|
content["rich_content"] = sanitize_img_size(article.content)
|
||||||
except:
|
except:
|
||||||
content = get_content(url.url)
|
content = get_content(url.url)
|
||||||
content['rich_content'] = sanitize_img_size(content.article_html)
|
content["rich_content"] = sanitize_img_size(content.article_html)
|
||||||
context = {'url': url, 'content': content}
|
context = {"url": url, "content": content}
|
||||||
return render(request, 'reader.html', context)
|
return render(request, "reader.html", context)
|
||||||
|
@ -5,25 +5,24 @@ from django.db.models import Q
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from later42.models.article import Article
|
from later42.models.article import Article
|
||||||
from later42.models.urls import URL as URL
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search(request):
|
def search(request):
|
||||||
pattern = request.POST.get('search')
|
pattern = request.POST.get("search")
|
||||||
context = {}
|
context = {}
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
return render(request, 'search.html', context)
|
return render(request, "search.html", context)
|
||||||
elif request.method == 'POST':
|
elif request.method == "POST":
|
||||||
urls = URL.objects.filter(
|
|
||||||
user=request.user).order_by('-id')
|
|
||||||
data = Article.objects.filter(
|
data = Article.objects.filter(
|
||||||
Q(title__contains=pattern) |
|
Q(title__contains=pattern)
|
||||||
Q(content__contains=pattern) |
|
| Q(content__contains=pattern)
|
||||||
Q(short__contains=pattern) |
|
| Q(short__contains=pattern)
|
||||||
Q(url__url__contains=pattern), url__user_id=request.user.id).select_related('url')
|
| Q(url__url__contains=pattern),
|
||||||
|
url__user_id=request.user.id,
|
||||||
|
).select_related("url")
|
||||||
paginator = Paginator(data, settings.URLS_PER_PAGE)
|
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)
|
data = paginator.get_page(page_number)
|
||||||
context = {'data': data}
|
context = {"data": data}
|
||||||
return render(request, 'search.html', context)
|
return render(request, "search.html", context)
|
||||||
|
@ -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.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.forms import SignUpForm
|
||||||
from later42.tokens import account_activation_token
|
from later42.tokens import account_activation_token
|
||||||
|
|
||||||
|
|
||||||
def register(request):
|
def register(request):
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
form = SignUpForm(request.POST)
|
form = SignUpForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save(commit=False)
|
user = form.save(commit=False)
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.save()
|
user.save()
|
||||||
raw_password = form.cleaned_data.get('password1')
|
# raw_password = form.cleaned_data.get("password1")
|
||||||
|
|
||||||
current_site = get_current_site(request)
|
current_site = get_current_site(request)
|
||||||
|
|
||||||
mail_subject = 'Later42: Активация аккаунта'
|
mail_subject = "Later42: Активация аккаунта"
|
||||||
message = render_to_string('registration/email_activation.html', {
|
message = render_to_string(
|
||||||
'user': user,
|
"registration/email_activation.html",
|
||||||
'domain': current_site.domain,
|
{
|
||||||
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
|
"user": user,
|
||||||
'token': account_activation_token.make_token(user),
|
"domain": current_site.domain,
|
||||||
})
|
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
|
||||||
to_email = form.cleaned_data.get('email')
|
"token": account_activation_token.make_token(user),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
to_email = form.cleaned_data.get("email")
|
||||||
email = EmailMessage(
|
email = EmailMessage(
|
||||||
mail_subject, message, to=[
|
mail_subject, message, to=[to_email], from_email=settings.EMAIL_FROM
|
||||||
to_email], from_email=settings.EMAIL_FROM
|
|
||||||
)
|
)
|
||||||
email.send()
|
email.send()
|
||||||
|
|
||||||
return render(request, 'registration/email_sent.html')
|
return render(request, "registration/email_sent.html")
|
||||||
else:
|
else:
|
||||||
form = SignUpForm()
|
form = SignUpForm()
|
||||||
return render(request, 'registration/signup.html', {'form': form})
|
return render(request, "registration/signup.html", {"form": form})
|
||||||
|
@ -11,6 +11,6 @@ import os
|
|||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
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()
|
application = get_wsgi_application()
|
||||||
|
@ -6,7 +6,7 @@ import sys
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run administrative tasks."""
|
"""Run administrative tasks."""
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'later42.settings')
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "later42.settings")
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
@ -18,5 +18,5 @@ def main():
|
|||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
black
|
black
|
||||||
|
django-stubs
|
||||||
isort
|
isort
|
||||||
|
mypy
|
||||||
pylint
|
pylint
|
||||||
pylint-django
|
pylint-django
|
||||||
mypy
|
|
||||||
django-stubs
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
bs4==0.0.1
|
||||||
|
celery[redis]==5.2.7
|
||||||
Django==4.1.3
|
Django==4.1.3
|
||||||
|
djangorestframework==3.14.0
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
loguru==0.6.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
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user