Fix moar mypy
This commit is contained in:
parent
643dc7f4a1
commit
880985dfe0
30 changed files with 234 additions and 106 deletions
|
@ -78,4 +78,3 @@ repos:
|
|||
entry: mypy
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
exclude: tasks.py$
|
||||
|
|
63
poetry.lock
generated
63
poetry.lock
generated
|
@ -253,6 +253,35 @@ Django = ">=2.2"
|
|||
phonenumbers = ["phonenumbers (>=7.0.2)"]
|
||||
phonenumberslite = ["phonenumberslite (>=7.0.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "django-stubs"
|
||||
version = "1.9.0"
|
||||
description = "Mypy stubs for Django"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
django-stubs-ext = ">=0.3.0"
|
||||
mypy = ">=0.910"
|
||||
toml = "*"
|
||||
types-pytz = "*"
|
||||
types-PyYAML = "*"
|
||||
typing-extensions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "django-stubs-ext"
|
||||
version = "0.3.1"
|
||||
description = "Monkey-patching and extensions for django-stubs"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
typing-extensions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "django-two-factor-auth"
|
||||
version = "1.13"
|
||||
|
@ -807,6 +836,22 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pytz"
|
||||
version = "2021.3.3"
|
||||
description = "Typing stubs for pytz"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.1"
|
||||
description = "Typing stubs for PyYAML"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.26.3"
|
||||
|
@ -930,7 +975,7 @@ multidict = ">=4.0"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "17e06d5348b12d6c12a95b1a9ba60278cddae6d15f80d4eba7173a67e5b5b123"
|
||||
content-hash = "35d359b39bfd7c907a1119797a4badf33b3f3c964a5ec90e85c781cf99fecf7e"
|
||||
|
||||
[metadata.files]
|
||||
asgiref = [
|
||||
|
@ -1096,6 +1141,14 @@ django-phonenumber-field = [
|
|||
{file = "django-phonenumber-field-5.2.0.tar.gz", hash = "sha256:52b2e5970133ec5ab701218b802f7ab237229854dc95fd239b7e9e77dc43731d"},
|
||||
{file = "django_phonenumber_field-5.2.0-py3-none-any.whl", hash = "sha256:5547fb2b2cc690a306ba77a5038419afc8fa8298a486fb7895008e9067cc7e75"},
|
||||
]
|
||||
django-stubs = [
|
||||
{file = "django-stubs-1.9.0.tar.gz", hash = "sha256:664843091636a917faf5256d028476559dc360fdef9050b6df87ab61b21607bf"},
|
||||
{file = "django_stubs-1.9.0-py3-none-any.whl", hash = "sha256:59c9f81af64d214b1954eaf90f037778c8d2b9c2de946a3cda177fefcf588fbd"},
|
||||
]
|
||||
django-stubs-ext = [
|
||||
{file = "django-stubs-ext-0.3.1.tar.gz", hash = "sha256:783c198d7e39a41be0b90fd843fa2770243a642922af679be4b19e03b82c8c28"},
|
||||
{file = "django_stubs_ext-0.3.1-py3-none-any.whl", hash = "sha256:a51a3e9e844d4e1cacaaedbb33bf3def78a3956eed5d9575a640bd97ccd99cec"},
|
||||
]
|
||||
django-two-factor-auth = []
|
||||
filelock = [
|
||||
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
|
||||
|
@ -1525,6 +1578,14 @@ types-pillow = [
|
|||
{file = "types-Pillow-8.3.11.tar.gz", hash = "sha256:aa96a739184f48f69e6f30218400623fc5a95f5fec199c447663a32538440405"},
|
||||
{file = "types_Pillow-8.3.11-py3-none-any.whl", hash = "sha256:998189334e616b1dd42c9634669efbf726184039e96e9a23ec95246e0ecff3fc"},
|
||||
]
|
||||
types-pytz = [
|
||||
{file = "types-pytz-2021.3.3.tar.gz", hash = "sha256:f6d21d6687935a1615db464b1e1df800d19502c36bc0486f43be7dfd2c404947"},
|
||||
{file = "types_pytz-2021.3.3-py3-none-any.whl", hash = "sha256:75859c64c9a97d68259af6da208e8f5aaf4be4536e4d431a82a6e8b848fc183d"},
|
||||
]
|
||||
types-pyyaml = [
|
||||
{file = "types-PyYAML-6.0.1.tar.gz", hash = "sha256:2e27b0118ca4248a646101c5c318dc02e4ca2866d6bc42e84045dbb851555a76"},
|
||||
{file = "types_PyYAML-6.0.1-py3-none-any.whl", hash = "sha256:d5b318269652e809b5c30a5fe666c50159ab80bfd41cd6bafe655bf20b29fcba"},
|
||||
]
|
||||
types-requests = [
|
||||
{file = "types-requests-2.26.3.tar.gz", hash = "sha256:d63fa617846dcefff5aa2d59e47ab4ffd806e4bb0567115f7adbb5e438302fe4"},
|
||||
{file = "types_requests-2.26.3-py3-none-any.whl", hash = "sha256:ad18284931c5ddbf050ccdd138f200d18fd56f88aa3567019d8da9b2d4fe0344"},
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
FROM python:3.8.6-buster
|
||||
RUN python3 -m pip install pre-commit==2.9.3
|
|
@ -43,6 +43,7 @@ types-setuptools = "^57.4.5"
|
|||
types-toml = "^0.10.1"
|
||||
types-beautifulsoup4 = "^4.10.7"
|
||||
types-Pillow = "^8.3.11"
|
||||
django-stubs = "^1.9.0"
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38']
|
||||
|
@ -63,6 +64,16 @@ env = [
|
|||
|
||||
[tool.mypy]
|
||||
mypy_path = "$MYPY_CONFIG_FILE_DIR/stubs:$MYPY_CONFIG_FILE_DIR/src"
|
||||
plugins = ["mypy_django_plugin.main"]
|
||||
disallow_untyped_defs = true
|
||||
warn_redundant_casts = true
|
||||
strict_equality = true
|
||||
disallow_untyped_calls = true
|
||||
warn_unreachable = true
|
||||
enable_error_code = ["redundant-expr", "truthy-bool"]
|
||||
|
||||
[tool.django-stubs]
|
||||
django_settings_module = "blog.settings"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
|
@ -71,7 +82,8 @@ module = [
|
|||
"django_otp.plugins.otp_static.models",
|
||||
"two_factor.models",
|
||||
"django_otp.plugins.otp_totp.models",
|
||||
"model_bakery"
|
||||
"model_bakery",
|
||||
"invoke",
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
from typing import cast
|
||||
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.admin import register
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from .models import Article, Tag, User
|
||||
|
@ -73,13 +77,13 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
autocomplete_fields = ["tags"]
|
||||
show_full_result_count = False
|
||||
|
||||
def get_queryset(self, request):
|
||||
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
||||
queryset = super().get_queryset(request)
|
||||
queryset = queryset.prefetch_related("tags")
|
||||
return queryset
|
||||
|
||||
@admin.action(description="Publish selected articles")
|
||||
def publish(self, request, queryset):
|
||||
def publish(self, request: HttpRequest, queryset: QuerySet) -> None:
|
||||
if not request.user.has_perm("articles.change_article"):
|
||||
messages.warning(request, "You're not allowed to do this.")
|
||||
return
|
||||
|
@ -88,7 +92,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
messages.success(request, f"{len(queryset)} articles published.")
|
||||
|
||||
@admin.action(description="Unpublish selected articles")
|
||||
def unpublish(self, request, queryset):
|
||||
def unpublish(self, request: HttpRequest, queryset: QuerySet) -> None:
|
||||
if not request.user.has_perm("articles.change_article"):
|
||||
messages.warning(request, "You're not allowed to do this.")
|
||||
return
|
||||
|
@ -97,7 +101,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
messages.success(request, f"{len(queryset)} articles unpublished.")
|
||||
|
||||
@admin.action(description="Refresh draft key of selected articles")
|
||||
def refresh_draft_key(self, request, queryset):
|
||||
def refresh_draft_key(self, request: HttpRequest, queryset: QuerySet) -> None:
|
||||
if not request.user.has_perm("articles.change_article"):
|
||||
messages.warning(request, "You're not allowed to do this.")
|
||||
return
|
||||
|
@ -110,12 +114,14 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
class Media:
|
||||
css = {"all": ("admin_articles.css",)}
|
||||
|
||||
def response_post_save_add(self, request, obj: Article):
|
||||
def response_post_save_add(
|
||||
self, request: HttpRequest, obj: Article
|
||||
) -> HttpResponseRedirect:
|
||||
if "_preview" in request.POST:
|
||||
return redirect("article-detail", slug=obj.slug)
|
||||
return cast(HttpResponseRedirect, redirect("article-detail", slug=obj.slug))
|
||||
return super().response_post_save_add(request, obj)
|
||||
|
||||
def response_change(self, request, obj: Article):
|
||||
def response_change(self, request: HttpRequest, obj: Article) -> HttpResponse:
|
||||
if "_preview" in request.POST:
|
||||
obj.save()
|
||||
return redirect("article-detail", slug=obj.slug)
|
||||
|
@ -129,11 +135,11 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
return redirect(".")
|
||||
return super().response_change(request, obj)
|
||||
|
||||
def read_time(self, instance: Article):
|
||||
def read_time(self, instance: Article) -> str:
|
||||
return f"{instance.get_read_time()} min"
|
||||
|
||||
@admin.display(boolean=True)
|
||||
def has_custom_css(self, instance: Article):
|
||||
def has_custom_css(self, instance: Article) -> bool:
|
||||
return bool(instance.custom_css)
|
||||
|
||||
|
||||
|
|
|
@ -9,15 +9,15 @@ from markdown.inlinepatterns import (
|
|||
|
||||
|
||||
class LazyImageInlineProcessor(ImageInlineProcessor):
|
||||
def handleMatch(self, m, data):
|
||||
def handleMatch(self, m, data): # type: ignore
|
||||
el, match_start, index = super().handleMatch(m, data)
|
||||
if el is not None:
|
||||
el.set("loading", "lazy")
|
||||
return el, match_start, index
|
||||
return el, match_start, index # type: ignore
|
||||
|
||||
|
||||
class LazyImageReferenceInlineProcessor(ImageReferenceInlineProcessor):
|
||||
def makeTag(self, href, title, text):
|
||||
def makeTag(self, href, title, text): # type: ignore
|
||||
el = super().makeTag(href, title, text)
|
||||
if el is not None:
|
||||
el.set("loading", "lazy")
|
||||
|
@ -25,7 +25,7 @@ class LazyImageReferenceInlineProcessor(ImageReferenceInlineProcessor):
|
|||
|
||||
|
||||
class LazyLoadingImageExtension(Extension):
|
||||
def extendMarkdown(self, md: Markdown):
|
||||
def extendMarkdown(self, md: Markdown) -> None:
|
||||
md.inlinePatterns.register(
|
||||
LazyImageInlineProcessor(IMAGE_LINK_RE, md), "image_link", 150
|
||||
)
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
# Generated by Django 3.1.3 on 2020-12-24 16:46
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
def forwards_func(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
|
||||
Article = apps.get_model("articles", "Article")
|
||||
db_alias = schema_editor.connection.alias
|
||||
Article.objects.using(db_alias).filter(slug="about-me").update(is_home=True)
|
||||
|
||||
|
||||
def reverse_func(apps, schema_editor):
|
||||
def reverse_func(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-03 15:33
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
def forwards(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
|
||||
Tag = apps.get_model("articles", "Tag")
|
||||
Article = apps.get_model("articles", "Article")
|
||||
db_alias = schema_editor.connection.alias
|
||||
articles = Article.objects.using(db_alias).all()
|
||||
for article in articles:
|
||||
tags = []
|
||||
keyword: str
|
||||
for keyword in list(
|
||||
filter(None, map(lambda k: k.strip(), article.keywords.split(",")))
|
||||
):
|
||||
|
@ -22,7 +24,7 @@ def forwards(apps, schema_editor):
|
|||
Article.objects.bulk_update(articles, ["keywords"])
|
||||
|
||||
|
||||
def backwards(apps, schema_editor):
|
||||
def backwards(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
|
||||
Article = apps.get_model("articles", "Article")
|
||||
db_alias = schema_editor.connection.alias
|
||||
articles = Article.objects.using(db_alias).all()
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-04 17:17
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.utils.text import slugify
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
def forwards(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
|
||||
Tag = apps.get_model("articles", "Tag")
|
||||
db_alias = schema_editor.connection.alias
|
||||
tags = Tag.objects.using(db_alias).all()
|
||||
|
@ -13,7 +14,7 @@ def forwards(apps, schema_editor):
|
|||
Tag.objects.bulk_update(tags, ["slug"])
|
||||
|
||||
|
||||
def backwards(apps, schema_editor):
|
||||
def backwards(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
|
||||
Tag = apps.get_model("articles", "Tag")
|
||||
db_alias = schema_editor.connection.alias
|
||||
Tag.objects.using(db_alias).update(slug="")
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import random
|
||||
import uuid
|
||||
from functools import cached_property
|
||||
from typing import Sequence
|
||||
from typing import Any, Sequence
|
||||
|
||||
import rcssmin
|
||||
import readtime
|
||||
|
@ -115,7 +115,7 @@ class Article(models.Model):
|
|||
self.save()
|
||||
return self
|
||||
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
def save(self, *args: Any, **kwargs: Any) -> None:
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
return super().save(*args, **kwargs)
|
||||
|
|
|
@ -55,5 +55,5 @@ def unpublished_article(author: User) -> Article:
|
|||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def _collect_static():
|
||||
def _collect_static() -> None:
|
||||
call_command("collectstatic", "--no-input", "--clear")
|
||||
|
|
|
@ -8,7 +8,7 @@ from articles.models import User
|
|||
@pytest.mark.django_db()
|
||||
# @pytest.mark.skip("Fails for no apparent reason")
|
||||
@pytest.mark.flaky(reruns=5, reruns_delay=3)
|
||||
def test_can_access_add_article(client: Client, author: User):
|
||||
def test_can_access_add_article(client: Client, author: User) -> None:
|
||||
client.force_login(author)
|
||||
url = reverse("admin:articles_article_add")
|
||||
res = client.get(url)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
from django.http import HttpResponse
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
|
||||
|
@ -7,7 +8,9 @@ from articles.utils import format_article_content
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_unauthenticated_render_redirects(published_article: Article, client: Client):
|
||||
def test_unauthenticated_render_redirects(
|
||||
published_article: Article, client: Client
|
||||
) -> None:
|
||||
api_res = client.post(
|
||||
reverse("api-render-article", kwargs={"article_pk": published_article.pk}),
|
||||
data={"content": published_article.content},
|
||||
|
@ -16,7 +19,9 @@ def test_unauthenticated_render_redirects(published_article: Article, client: Cl
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_render_article_same_content(published_article: Article, client: Client):
|
||||
def test_render_article_same_content(
|
||||
published_article: Article, client: Client
|
||||
) -> None:
|
||||
client.force_login(published_article.author)
|
||||
api_res = post_article(client, published_article, published_article.content)
|
||||
standard_res = client.get(
|
||||
|
@ -36,7 +41,9 @@ def test_render_article_same_content(published_article: Article, client: Client)
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_render_article_change_content(published_article: Article, client: Client):
|
||||
def test_render_article_change_content(
|
||||
published_article: Article, client: Client
|
||||
) -> None:
|
||||
client.force_login(published_article.author)
|
||||
preview_content = "This is a different content **with strong emphasis**"
|
||||
api_res = post_article(client, published_article, preview_content)
|
||||
|
@ -47,7 +54,7 @@ def test_render_article_change_content(published_article: Article, client: Clien
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_render_article_doesnt_save(published_article, client: Client):
|
||||
def test_render_article_doesnt_save(published_article: Article, client: Client) -> None:
|
||||
client.force_login(published_article.author)
|
||||
original_content = published_article.content
|
||||
preview_content = "This is a different content **with strong emphasis**"
|
||||
|
@ -58,7 +65,7 @@ def test_render_article_doesnt_save(published_article, client: Client):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_render_article_no_tags(published_article, client: Client):
|
||||
def test_render_article_no_tags(published_article: Article, client: Client) -> None:
|
||||
client.force_login(published_article.author)
|
||||
api_res = client.post(
|
||||
reverse("api-render-article", kwargs={"article_pk": published_article.pk}),
|
||||
|
@ -67,7 +74,7 @@ def test_render_article_no_tags(published_article, client: Client):
|
|||
assert api_res.status_code == 200
|
||||
|
||||
|
||||
def post_article(client: Client, article: Article, content: str):
|
||||
def post_article(client: Client, article: Article, content: str) -> HttpResponse:
|
||||
return client.post(
|
||||
reverse("api-render-article", kwargs={"article_pk": article.pk}),
|
||||
data={
|
||||
|
|
|
@ -8,7 +8,7 @@ from articles.views.feeds import CompleteFeed
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_can_access_feed(client: Client, published_article):
|
||||
def test_can_access_feed(client: Client, published_article: Article) -> None:
|
||||
res = client.get(reverse("complete-feed"))
|
||||
assert res.status_code == 200
|
||||
assert "application/rss+xml" in res["content-type"]
|
||||
|
@ -17,7 +17,7 @@ def test_can_access_feed(client: Client, published_article):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_feed_limits_number_of_articles(client: Client, author: User):
|
||||
def test_feed_limits_number_of_articles(client: Client, author: User) -> None:
|
||||
baker.make(Article, 100, status=Article.PUBLISHED, author=author)
|
||||
res = client.get(reverse("complete-feed"))
|
||||
content = res.content.decode("utf-8")
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import pytest
|
||||
from django.http import HttpResponse
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from model_bakery import baker
|
||||
from pytest_django.fixtures import SettingsWrapper
|
||||
|
||||
from articles.models import Article, User
|
||||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_can_access_list(client: Client, published_article: Article):
|
||||
def test_can_access_list(client: Client, published_article: Article) -> None:
|
||||
res = client.get(reverse("articles-list"))
|
||||
assert res.status_code == 200
|
||||
content = res.content.decode("utf-8")
|
||||
|
@ -16,7 +18,7 @@ def test_can_access_list(client: Client, published_article: Article):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_only_title_shown_on_list(client: Client, author: User):
|
||||
def test_only_title_shown_on_list(client: Client, author: User) -> None:
|
||||
title = "This is a very long title mouahahaha"
|
||||
abstract = "Some abstract"
|
||||
after = "Some content after abstract"
|
||||
|
@ -35,16 +37,16 @@ def test_only_title_shown_on_list(client: Client, author: User):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_access_article_by_slug(client: Client, published_article: Article):
|
||||
def test_access_article_by_slug(client: Client, published_article: Article) -> None:
|
||||
_test_access_article_by_slug(client, published_article)
|
||||
|
||||
|
||||
def _test_access_article_by_slug(client: Client, item: Article):
|
||||
def _test_access_article_by_slug(client: Client, item: Article) -> None:
|
||||
res = client.get(reverse("article-detail", kwargs={"slug": item.slug}))
|
||||
_assert_article_is_rendered(item, res)
|
||||
|
||||
|
||||
def _assert_article_is_rendered(item: Article, res):
|
||||
def _assert_article_is_rendered(item: Article, res: HttpResponse) -> None:
|
||||
assert res.status_code == 200
|
||||
content = res.content.decode("utf-8")
|
||||
assert item.title in content
|
||||
|
@ -55,7 +57,7 @@ def _assert_article_is_rendered(item: Article, res):
|
|||
@pytest.mark.django_db()
|
||||
def test_anonymous_cant_access_draft_detail(
|
||||
client: Client, unpublished_article: Article
|
||||
):
|
||||
) -> None:
|
||||
res = client.get(
|
||||
reverse("article-detail", kwargs={"slug": unpublished_article.slug})
|
||||
)
|
||||
|
@ -65,7 +67,7 @@ def test_anonymous_cant_access_draft_detail(
|
|||
@pytest.mark.django_db()
|
||||
def test_anonymous_can_access_draft_detail_with_key(
|
||||
client: Client, unpublished_article: Article
|
||||
):
|
||||
) -> None:
|
||||
res = client.get(
|
||||
reverse("article-detail", kwargs={"slug": unpublished_article.slug})
|
||||
+ f"?draft_key={unpublished_article.draft_key}"
|
||||
|
@ -76,7 +78,7 @@ def test_anonymous_can_access_draft_detail_with_key(
|
|||
@pytest.mark.django_db()
|
||||
def test_user_can_access_draft_detail(
|
||||
client: Client, author: User, unpublished_article: Article
|
||||
):
|
||||
) -> None:
|
||||
client.force_login(author)
|
||||
_test_access_article_by_slug(client, unpublished_article)
|
||||
|
||||
|
@ -84,7 +86,7 @@ def test_user_can_access_draft_detail(
|
|||
@pytest.mark.django_db()
|
||||
def test_anonymous_cant_access_drafts_list(
|
||||
client: Client, unpublished_article: Article
|
||||
):
|
||||
) -> None:
|
||||
res = client.get(reverse("drafts-list"))
|
||||
assert res.status_code == 302
|
||||
|
||||
|
@ -92,7 +94,7 @@ def test_anonymous_cant_access_drafts_list(
|
|||
@pytest.mark.django_db()
|
||||
def test_user_can_access_drafts_list(
|
||||
client: Client, author: User, unpublished_article: Article
|
||||
):
|
||||
) -> None:
|
||||
client.force_login(author)
|
||||
res = client.get(reverse("drafts-list"))
|
||||
assert res.status_code == 200
|
||||
|
@ -101,7 +103,7 @@ def test_user_can_access_drafts_list(
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_has_goatcounter_if_set(client: Client, settings):
|
||||
def test_has_goatcounter_if_set(client: Client, settings: SettingsWrapper) -> None:
|
||||
settings.GOATCOUNTER_DOMAIN = "gc.gabnotes.org"
|
||||
res = client.get(reverse("articles-list"))
|
||||
content = res.content.decode("utf-8")
|
||||
|
@ -110,7 +112,9 @@ def test_has_goatcounter_if_set(client: Client, settings):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_doesnt_have_goatcounter_if_unset(client: Client, settings):
|
||||
def test_doesnt_have_goatcounter_if_unset(
|
||||
client: Client, settings: SettingsWrapper
|
||||
) -> None:
|
||||
settings.GOATCOUNTER_DOMAIN = None
|
||||
res = client.get(reverse("articles-list"))
|
||||
content = res.content.decode("utf-8")
|
||||
|
@ -119,7 +123,9 @@ def test_doesnt_have_goatcounter_if_unset(client: Client, settings):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_logged_in_user_doesnt_have_goatcounter(client: Client, author: User, settings):
|
||||
def test_logged_in_user_doesnt_have_goatcounter(
|
||||
client: Client, author: User, settings: SettingsWrapper
|
||||
) -> None:
|
||||
client.force_login(author)
|
||||
settings.GOATCOUNTER_DOMAIN = "gc.gabnotes.org"
|
||||
res = client.get(reverse("articles-list"))
|
||||
|
@ -129,7 +135,7 @@ def test_logged_in_user_doesnt_have_goatcounter(client: Client, author: User, se
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_image_is_lazy(client: Client, published_article: Article):
|
||||
def test_image_is_lazy(client: Client, published_article: Article) -> None:
|
||||
res = client.get(reverse("article-detail", kwargs={"slug": published_article.slug}))
|
||||
assert res.status_code == 200
|
||||
content = res.content.decode("utf-8")
|
||||
|
|
|
@ -3,5 +3,5 @@ from django.core.management import call_command
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_missing_migrations():
|
||||
def test_missing_migrations() -> None:
|
||||
call_command("makemigrations", "--check")
|
||||
|
|
|
@ -4,7 +4,7 @@ from articles.models import Article, User
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_publish_article(unpublished_article: Article):
|
||||
def test_publish_article(unpublished_article: Article) -> None:
|
||||
assert unpublished_article.status == Article.DRAFT
|
||||
assert unpublished_article.published_at is None
|
||||
published_article = unpublished_article.publish()
|
||||
|
@ -13,7 +13,7 @@ def test_publish_article(unpublished_article: Article):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_unpublish_article(published_article: Article):
|
||||
def test_unpublish_article(published_article: Article) -> None:
|
||||
assert published_article.status == Article.PUBLISHED
|
||||
assert published_article.published_at is not None
|
||||
unpublished_article = published_article.unpublish()
|
||||
|
@ -22,7 +22,7 @@ def test_unpublish_article(published_article: Article):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_save_article_adds_missing_slug(author: User):
|
||||
def test_save_article_adds_missing_slug(author: User) -> None:
|
||||
# Explicitly calling bulk_create with one article because it doesn't call save().
|
||||
articles = Article.objects.bulk_create(
|
||||
[Article(author=author, title="noice title", slug="", status=Article.DRAFT)]
|
||||
|
@ -34,7 +34,7 @@ def test_save_article_adds_missing_slug(author: User):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_save_article_doesnt_change_existing_slug(published_article: Article):
|
||||
def test_save_article_doesnt_change_existing_slug(published_article: Article) -> None:
|
||||
original_slug = published_article.slug
|
||||
published_article.title = "This is a brand new title"
|
||||
published_article.save()
|
||||
|
@ -42,19 +42,19 @@ def test_save_article_doesnt_change_existing_slug(published_article: Article):
|
|||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_empty_custom_css_minified(published_article):
|
||||
def test_empty_custom_css_minified(published_article: Article) -> None:
|
||||
published_article.custom_css = ""
|
||||
assert published_article.get_minified_custom_css == ""
|
||||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_simple_custom_css_minified(published_article):
|
||||
def test_simple_custom_css_minified(published_article: Article) -> None:
|
||||
published_article.custom_css = ".cls {\n background-color: red;\n}"
|
||||
assert published_article.get_minified_custom_css == ".cls{background-color:red}"
|
||||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_larger_custom_css_minified(published_article):
|
||||
def test_larger_custom_css_minified(published_article: Article) -> None:
|
||||
published_article.custom_css = """\
|
||||
.profile {
|
||||
display: flex;
|
||||
|
|
|
@ -3,13 +3,14 @@ import re
|
|||
import markdown
|
||||
from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
from markdown.extensions.codehilite import CodeHiliteExtension
|
||||
from markdown.extensions.toc import TocExtension
|
||||
|
||||
from articles.markdown import LazyLoadingImageExtension
|
||||
|
||||
|
||||
def build_full_absolute_url(request, url: str) -> str:
|
||||
def build_full_absolute_url(request: HttpRequest | None, url: str) -> str:
|
||||
if request:
|
||||
return request.build_absolute_uri(url)
|
||||
else:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Any
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
|
@ -10,7 +10,7 @@ from articles.models import Article, Tag
|
|||
|
||||
@login_required
|
||||
@require_POST
|
||||
def render_article(request, article_pk: int) -> HttpResponse:
|
||||
def render_article(request: HttpRequest, article_pk: int) -> HttpResponse:
|
||||
template = "articles/article_detail.html"
|
||||
article = Article.objects.get(pk=article_pk)
|
||||
article.content = request.POST.get("content", article.content)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from datetime import datetime
|
||||
from typing import Iterable
|
||||
from typing import Any, Iterable
|
||||
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
|
||||
from articles.models import Article, Tag
|
||||
from blog import settings
|
||||
|
@ -33,7 +34,7 @@ class CompleteFeed(BaseFeed):
|
|||
|
||||
|
||||
class TagFeed(BaseFeed):
|
||||
def get_object(self, request, *args, **kwargs) -> Tag:
|
||||
def get_object(self, request: HttpRequest, *args: Any, **kwargs: Any) -> Tag:
|
||||
return Tag.objects.get(slug=kwargs.get("slug"))
|
||||
|
||||
def title(self, tag: Tag) -> str:
|
||||
|
|
|
@ -5,7 +5,9 @@ from typing import Any
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.paginator import Page
|
||||
from django.db.models import F, Q
|
||||
from django.db.models import F, Q, QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.http.response import HttpResponseBase
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views import generic
|
||||
from django.views.generic import DetailView
|
||||
|
@ -20,7 +22,7 @@ class BaseArticleListView(generic.ListView):
|
|||
main_title = "Blog posts"
|
||||
html_title = ""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["blog_title"] = settings.BLOG["title"]
|
||||
context["blog_description"] = settings.BLOG["description"]
|
||||
|
@ -51,7 +53,7 @@ class PublicArticleListView(BaseArticleListView):
|
|||
|
||||
|
||||
class ArticlesListView(PublicArticleListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
home_article = Article.objects.filter(
|
||||
status=Article.PUBLISHED, is_home=True
|
||||
|
@ -64,12 +66,12 @@ class SearchArticlesListView(PublicArticleListView):
|
|||
template_name = "articles/article_search.html"
|
||||
html_title = "Search"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["search_expression"] = self.request.GET.get("s") or ""
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self) -> QuerySet:
|
||||
queryset = super().get_queryset()
|
||||
search_expression = self.request.GET.get("s")
|
||||
if not search_expression:
|
||||
|
@ -98,25 +100,27 @@ class TagArticlesListView(PublicArticleListView):
|
|||
main_title = ""
|
||||
html_title = ""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
def dispatch(
|
||||
self, request: HttpRequest, *args: Any, **kwargs: Any
|
||||
) -> HttpResponseBase:
|
||||
self.tag = get_object_or_404(Tag, slug=self.kwargs.get("slug"))
|
||||
self.main_title = self.html_title = f"{self.tag.name} articles"
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["feed_title"] = self.tag.get_feed_title()
|
||||
context["feed_url"] = self.tag.get_feed_url()
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self) -> QuerySet:
|
||||
return super().get_queryset().filter(tags=self.tag)
|
||||
|
||||
|
||||
class DraftsListView(LoginRequiredMixin, BaseArticleListView):
|
||||
queryset = Article.objects.filter(status=Article.DRAFT)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Drafts"
|
||||
context["title_header"] = context["title"]
|
||||
|
@ -128,7 +132,7 @@ class ArticleDetailView(DetailView):
|
|||
context_object_name = "article"
|
||||
template_name = "articles/article_detail.html"
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self) -> QuerySet:
|
||||
key = self.request.GET.get("draft_key")
|
||||
if key:
|
||||
return Article.objects.filter(draft_key=key).prefetch_related("tags")
|
||||
|
@ -138,7 +142,7 @@ class ArticleDetailView(DetailView):
|
|||
queryset = queryset.filter(status=Article.PUBLISHED)
|
||||
return queryset
|
||||
|
||||
def get_object(self, queryset=None) -> Article:
|
||||
def get_object(self, queryset: QuerySet | None = None) -> Article:
|
||||
obj: Article = super().get_object(queryset)
|
||||
if not self.request.user.is_authenticated:
|
||||
obj.views_count = F("views_count") + 1
|
||||
|
@ -146,6 +150,6 @@ class ArticleDetailView(DetailView):
|
|||
|
||||
return obj
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
kwargs["tags"] = self.object.tags.all()
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django.contrib import admin, messages
|
||||
from django.contrib.admin import register
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.utils.html import format_html
|
||||
|
||||
from attachments.models import Attachment
|
||||
|
@ -38,7 +40,7 @@ class AttachmentAdmin(admin.ModelAdmin):
|
|||
class Media:
|
||||
js = ["attachments/js/copy_url.js"]
|
||||
|
||||
def processed_file_url(self, instance):
|
||||
def processed_file_url(self, instance: Attachment) -> str:
|
||||
if instance.processed_file:
|
||||
return format_html(
|
||||
'{0} <a class="copy-button" data-to-copy="{1}" href="#">📋</a>',
|
||||
|
@ -47,7 +49,7 @@ class AttachmentAdmin(admin.ModelAdmin):
|
|||
)
|
||||
return ""
|
||||
|
||||
def original_file_url(self, instance):
|
||||
def original_file_url(self, instance: Attachment) -> str:
|
||||
if instance.original_file:
|
||||
return format_html(
|
||||
'{0} <a class="copy-button" data-to-copy="{1}" href="#">📋</a>',
|
||||
|
@ -57,7 +59,7 @@ class AttachmentAdmin(admin.ModelAdmin):
|
|||
return ""
|
||||
|
||||
@admin.action(description="Set as open graph image")
|
||||
def set_as_open_graph_image(self, request, queryset):
|
||||
def set_as_open_graph_image(self, request: HttpRequest, queryset: QuerySet) -> None:
|
||||
if len(queryset) != 1:
|
||||
messages.error(request, "You must select only one attachment")
|
||||
return
|
||||
|
@ -66,7 +68,9 @@ class AttachmentAdmin(admin.ModelAdmin):
|
|||
messages.success(request, "Done")
|
||||
|
||||
@admin.action(description="Reprocess selected attachments")
|
||||
def reprocess_selected_attachments(self, request, queryset):
|
||||
def reprocess_selected_attachments(
|
||||
self, request: HttpRequest, queryset: QuerySet
|
||||
) -> None:
|
||||
if len(queryset) == 0:
|
||||
messages.error(request, "You must select at least one attachment")
|
||||
return
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from attachments.models import Attachment
|
||||
|
@ -6,7 +8,7 @@ from attachments.models import Attachment
|
|||
class Command(BaseCommand):
|
||||
help = "Reprocess all attachments"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
def handle(self, *args: Any, **options: Any) -> None:
|
||||
for attachment in Attachment.objects.all():
|
||||
self.stdout.write(f"Processing {attachment}...")
|
||||
attachment.reprocess()
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.db import models
|
||||
from django.db.models.fields.files import FieldFile
|
||||
from django.http import HttpRequest
|
||||
from PIL import Image
|
||||
|
||||
from articles.utils import build_full_absolute_url
|
||||
|
||||
|
||||
class AbsoluteUrlFieldFile(FieldFile):
|
||||
def get_full_absolute_url(self, request):
|
||||
def get_full_absolute_url(self, request: HttpRequest) -> str:
|
||||
return build_full_absolute_url(request, self.url)
|
||||
|
||||
|
||||
|
@ -22,7 +26,7 @@ class AbsoluteUrlFileField(models.FileField):
|
|||
|
||||
|
||||
class AttachmentManager(models.Manager):
|
||||
def get_open_graph_image(self):
|
||||
def get_open_graph_image(self) -> Attachment | None:
|
||||
return self.filter(open_graph_image=True).first()
|
||||
|
||||
|
||||
|
@ -37,14 +41,14 @@ class Attachment(models.Model):
|
|||
class Meta:
|
||||
ordering = ["description"]
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.description} ({self.original_file.name})"
|
||||
|
||||
def reprocess(self):
|
||||
def reprocess(self) -> None:
|
||||
self.processed_file = None
|
||||
self.save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
def save(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if self.processed_file:
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
from typing import Any, Protocol
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def replace_post_body(request):
|
||||
class Request(Protocol):
|
||||
body: Any
|
||||
|
||||
|
||||
def replace_post_body(request: Request) -> Request:
|
||||
request.body = None
|
||||
return request
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vcr_config():
|
||||
def vcr_config() -> dict[str, Any]:
|
||||
return {
|
||||
"before_record_request": replace_post_body,
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ from attachments.models import Attachment
|
|||
@pytest.mark.block_network()
|
||||
@pytest.mark.vcr()
|
||||
@pytest.mark.django_db()
|
||||
def test_attachment_is_processed_by_shortpixel():
|
||||
def test_attachment_is_processed_by_shortpixel() -> None:
|
||||
# This path manipulation is required to make the test run from this directory
|
||||
# or from upper in the hierarchy (e.g.: settings.BASE_DIR)
|
||||
img_path = Path(__file__).parent / "resources" / "image.png"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
def test_robots_txt(client):
|
||||
from django.test.client import Client
|
||||
|
||||
|
||||
def test_robots_txt(client: Client) -> None:
|
||||
res = client.get("/robots.txt")
|
||||
assert res.status_code == 200
|
||||
assert res["Content-Type"] == "text/plain"
|
||||
|
|
|
@ -4,7 +4,7 @@ import os
|
|||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
"""Run administrative tasks.""" # noqa: DAR401
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blog.settings")
|
||||
try:
|
||||
|
|
|
@ -4,7 +4,7 @@ class Result:
|
|||
delta: timedelta | None
|
||||
wpm: int | None
|
||||
def __init__(self, seconds: int | None = ..., wpm: int | None = ...) -> None: ...
|
||||
def __unicode__(self): ...
|
||||
def __unicode__(self) -> str: ...
|
||||
@property
|
||||
def seconds(self) -> int: ...
|
||||
@property
|
||||
|
|
34
tasks.py
34
tasks.py
|
@ -1,21 +1,31 @@
|
|||
"""
|
||||
Invoke management tasks for the project.
|
||||
|
||||
The current implementation with type annotations is not compatible
|
||||
with invoke 1.6.0 and requires manual patching.
|
||||
|
||||
See https://github.com/pyinvoke/invoke/pull/458/files
|
||||
"""
|
||||
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from invoke import task
|
||||
from invoke import Context, task
|
||||
|
||||
BASE_DIR = Path(__file__).parent.resolve(strict=True)
|
||||
SRC_DIR = BASE_DIR / "src"
|
||||
|
||||
|
||||
@task
|
||||
def test(ctx):
|
||||
def test(ctx: Context) -> None:
|
||||
with ctx.cd(SRC_DIR):
|
||||
ctx.run("pytest", pty=True, echo=True)
|
||||
|
||||
|
||||
@task
|
||||
def test_cov(ctx):
|
||||
def test_cov(ctx: Context) -> None:
|
||||
with ctx.cd(SRC_DIR):
|
||||
ctx.run(
|
||||
"pytest --cov=. --cov-report term-missing:skip-covered",
|
||||
|
@ -25,41 +35,41 @@ def test_cov(ctx):
|
|||
|
||||
|
||||
@task
|
||||
def pre_commit(ctx):
|
||||
def pre_commit(ctx: Context) -> None:
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run("pre-commit run --all-files", pty=True)
|
||||
|
||||
|
||||
@task
|
||||
def mypy(ctx):
|
||||
def mypy(ctx: Context) -> None:
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run("pre-commit run --all-files mypy", pty=True)
|
||||
|
||||
|
||||
@task(pre=[pre_commit, test_cov])
|
||||
def check(ctx):
|
||||
def check(ctx: Context) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@task
|
||||
def build(ctx):
|
||||
def build(ctx: Context) -> None:
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run("docker-compose build django", pty=True, echo=True)
|
||||
|
||||
|
||||
@task
|
||||
def publish(ctx):
|
||||
def publish(ctx: Context) -> None:
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run("docker-compose push django", pty=True, echo=True)
|
||||
|
||||
|
||||
@task
|
||||
def deploy(ctx):
|
||||
def deploy(ctx: Context) -> None:
|
||||
ctx.run("ssh ubuntu /home/gaugendre/blog/update", pty=True, echo=True)
|
||||
|
||||
|
||||
@task
|
||||
def check_alive(ctx):
|
||||
def check_alive(ctx: Context) -> None:
|
||||
for _ in range(5):
|
||||
try:
|
||||
res = requests.get("https://gabnotes.org")
|
||||
|
@ -70,12 +80,12 @@ def check_alive(ctx):
|
|||
|
||||
|
||||
@task(pre=[check, build, publish, deploy], post=[check_alive])
|
||||
def beam(ctx):
|
||||
def beam(ctx: Context) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@task
|
||||
def download_db(ctx):
|
||||
def download_db(ctx: Context) -> None:
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run("scp ubuntu:/home/gaugendre/blog/db/db.sqlite3 ./db/db.sqlite3")
|
||||
ctx.run("rm -rf src/media/")
|
||||
|
|
Reference in a new issue