diff --git a/akarpov/blog/migrations/0002_alter_comment_options.py b/akarpov/blog/migrations/0002_alter_comment_options.py
new file mode 100644
index 0000000..b4aa79f
--- /dev/null
+++ b/akarpov/blog/migrations/0002_alter_comment_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.0.8 on 2022-11-23 09:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='comment',
+ options={'ordering': ['-rating', '-created']},
+ ),
+ ]
diff --git a/akarpov/blog/models.py b/akarpov/blog/models.py
index c4f9177..7017119 100644
--- a/akarpov/blog/models.py
+++ b/akarpov/blog/models.py
@@ -98,7 +98,7 @@ def __str__(self):
return f"{self.author.username}'s comment on {self.post.title}"
class Meta:
- ordering = ["-rating"]
+ ordering = ["-rating", "-created"]
class CommentRating(models.Model):
diff --git a/akarpov/blog/signals.py b/akarpov/blog/signals.py
index 4579e52..143da5c 100644
--- a/akarpov/blog/signals.py
+++ b/akarpov/blog/signals.py
@@ -19,7 +19,14 @@ def post_on_save(sender, instance: Post, **kwargs):
{"image_cropped"}
):
if instance.image:
- crop_model_image.delay(instance.pk, "blog", "Post")
+ crop_model_image.apply_async(
+ kwargs={
+ "pk": instance.pk,
+ "app_label": "blog",
+ "model_name": "Post",
+ },
+ countdown=10,
+ )
else:
instance.image_cropped = None
instance.save()
@@ -74,4 +81,11 @@ def post_rating_delete(sender, instance: PostRating, **kwargs):
def post_on_create(sender, instance: Post, created, **kwargs):
if created:
if instance.image:
- crop_model_image.delay(instance.pk, "blog", "Post")
+ crop_model_image.apply_async(
+ kwargs={
+ "pk": instance.pk,
+ "app_label": "blog",
+ "model_name": "Post",
+ },
+ countdown=10,
+ )
diff --git a/akarpov/templates/account/signup.html b/akarpov/templates/account/signup.html
index 189ab9e..7b0a32d 100644
--- a/akarpov/templates/account/signup.html
+++ b/akarpov/templates/account/signup.html
@@ -2,6 +2,7 @@
{% load i18n %}
{% load crispy_forms_tags %}
+{% load socialaccount %}
{% block head_title %}{% translate "Signup" %}{% endblock %}
@@ -16,6 +17,9 @@
{% if redirect_field_value %}
{% endif %}
+
diff --git a/akarpov/templates/blog/post.html b/akarpov/templates/blog/post.html
index 6c6962c..dff8a1a 100644
--- a/akarpov/templates/blog/post.html
+++ b/akarpov/templates/blog/post.html
@@ -74,7 +74,7 @@
{% csrf_token %}
-
+
@@ -95,7 +95,7 @@
{{ comment.author.username }} - {{ comment.created | naturaltime }}
-
+
{{ comment.body }}
diff --git a/akarpov/templates/socialaccount/login.html b/akarpov/templates/socialaccount/login.html
new file mode 100644
index 0000000..9fc1a79
--- /dev/null
+++ b/akarpov/templates/socialaccount/login.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Sign In" %}{% endblock %}
+
+{% block content %}
+{% if process == "connect" %}
+
{% blocktrans with provider.name as provider %}Connect {{ provider }}{% endblocktrans %}
+
+{% blocktrans with provider.name as provider %}You are about to connect a new third party account from {{ provider }}.{% endblocktrans %}
+{% else %}
+{% blocktrans with provider.name as provider %}Sign In Via {{ provider }}{% endblocktrans %}
+
+{% blocktrans with provider.name as provider %}You are about to sign in using a third party account from {{ provider }}.{% endblocktrans %}
+{% endif %}
+
+
+{% endblock %}
diff --git a/akarpov/templates/socialaccount/providers/connections.html b/akarpov/templates/socialaccount/providers/connections.html
new file mode 100644
index 0000000..b0eafa3
--- /dev/null
+++ b/akarpov/templates/socialaccount/providers/connections.html
@@ -0,0 +1,52 @@
+{% load i18n %}
+
+{% block head_title %}{% trans "Account Connections" %}{% endblock %}
+
+{% block content %}
+{% trans "Account Connections" %}
+
+{% if form.accounts %}
+{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}
+
+
+
+
+{% else %}
+{% trans 'You currently have no social network accounts connected to this account.' %}
+{% endif %}
+
+{% trans 'Add a 3rd Party Account' %}
+
+
+{% include "socialaccount/snippets/provider_list.html" with process="connect" %}
+
+
+{% include "socialaccount/snippets/login_extra.html" %}
+
+{% endblock %}
diff --git a/akarpov/templates/socialaccount/snippets/provider_list.html b/akarpov/templates/socialaccount/snippets/provider_list.html
new file mode 100644
index 0000000..54e1d7f
--- /dev/null
+++ b/akarpov/templates/socialaccount/snippets/provider_list.html
@@ -0,0 +1,21 @@
+{% load socialaccount %}
+
+{% get_providers as socialaccount_providers %}
+
+{% for provider in socialaccount_providers %}
+{% if provider.id == "openid" %}
+{% for brand in provider.get_brands %}
+
+ {{brand.name}}
+
+{% endfor %}
+{% endif %}
+
+
+ {{provider.name}}
+
+{% endfor %}
diff --git a/akarpov/templates/users/user_detail.html b/akarpov/templates/users/user_detail.html
index 156f7bb..e7f5d5e 100644
--- a/akarpov/templates/users/user_detail.html
+++ b/akarpov/templates/users/user_detail.html
@@ -35,10 +35,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
{% endif %}
diff --git a/akarpov/users/signals.py b/akarpov/users/signals.py
index 5febbc7..7b057cb 100644
--- a/akarpov/users/signals.py
+++ b/akarpov/users/signals.py
@@ -15,7 +15,14 @@ def on_change(sender, instance: User, **kwargs):
{"image_cropped"}
):
if instance.image:
- crop_model_image.delay(instance.pk, "users", "User")
+ crop_model_image.apply_async(
+ kwargs={
+ "pk": instance.pk,
+ "app_label": "users",
+ "model_name": "User",
+ },
+ countdown=10,
+ )
else:
instance.image_cropped = None
instance.save()
@@ -25,4 +32,12 @@ def on_change(sender, instance: User, **kwargs):
def post_on_create(sender, instance: User, created, **kwargs):
if created:
if instance.image:
- crop_model_image.delay(instance.pk, "users", "User")
+ if instance.image:
+ crop_model_image.apply_async(
+ kwargs={
+ "pk": instance.pk,
+ "app_label": "users",
+ "model_name": "User",
+ },
+ countdown=10,
+ )
diff --git a/config/settings/base.py b/config/settings/base.py
index 10276bc..1dd96eb 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -66,6 +66,7 @@
"django.contrib.admin",
"django.forms",
]
+
THIRD_PARTY_APPS = [
"crispy_forms",
"crispy_bootstrap5",
@@ -82,13 +83,34 @@
"colorfield",
]
+HEALTH_CHECKS = [
+ "health_check", # required
+ "health_check.db", # stock Django health checkers
+ "health_check.cache",
+ "health_check.storage",
+ "health_check.contrib.celery",
+ "health_check.contrib.celery_ping",
+ "health_check.contrib.migrations",
+ "health_check.contrib.psutil", # disk and memory utilization
+ "health_check.contrib.redis",
+]
+
+ALL_AUTH_PROVIDERS = [
+ "allauth.socialaccount.providers.github",
+ # "allauth.socialaccount.providers.google",
+ # "allauth.socialaccount.providers.telegram",
+ # "allauth.socialaccount.providers.yandex",
+]
+
LOCAL_APPS = [
"akarpov.users",
"akarpov.blog",
# Your stuff: custom apps go here
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
-INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
+INSTALLED_APPS = (
+ DJANGO_APPS + THIRD_PARTY_APPS + HEALTH_CHECKS + ALL_AUTH_PROVIDERS + LOCAL_APPS
+)
# MIGRATIONS
# ------------------------------------------------------------------------------
@@ -284,6 +306,7 @@
CELERY_TASK_SOFT_TIME_LIMIT = 60
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-scheduler
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
+
# django-allauth
# ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
@@ -291,6 +314,9 @@
ACCOUNT_AUTHENTICATION_METHOD = "username"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
+ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
+SOCIALACCOUNT_EMAIL_VERIFICATION = False
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
@@ -301,6 +327,23 @@
SOCIALACCOUNT_ADAPTER = "akarpov.users.adapters.SocialAccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/forms.html
SOCIALACCOUNT_FORMS = {"signup": "akarpov.users.forms.UserSocialSignupForm"}
+SOCIALACCOUNT_PROVIDERS = {
+ "github": {
+ "SCOPE": [
+ "user",
+ "read:org",
+ ],
+ },
+ "google": {
+ "SCOPE": [
+ "profile",
+ "email",
+ ],
+ "AUTH_PARAMS": {
+ "access_type": "online",
+ },
+ },
+}
# django-rest-framework
# -------------------------------------------------------------------------------
diff --git a/config/urls.py b/config/urls.py
index 66f5a63..25fe306 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -15,6 +15,7 @@
path(
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
),
+ path("health/", include("health_check.urls")),
# Django Admin, use {% url 'admin:index' %}
path(settings.ADMIN_URL, admin.site.urls),
# User management
diff --git a/requirements/base.txt b/requirements/base.txt
index 09fd1e9..264964b 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,4 +1,5 @@
pytz==2022.6 # https://github.com/stub42/pytz
+psutil==5.9.4
python-slugify==6.1.2 # https://github.com/un33k/python-slugify
Pillow==9.3.0 # https://github.com/python-pillow/Pillow
argon2-cffi==21.3.0 # https://github.com/hynek/argon2_cffi
@@ -12,6 +13,7 @@ flower==1.2.0 # https://github.com/mher/flower
# Django
# ------------------------------------------------------------------------------
django==4.0.8 # pyup: < 4.1 # https://www.djangoproject.com/
+django-health-check==3.17.0
django-environ==0.9.0 # https://github.com/joke2k/django-environ
django-model-utils==4.2.0 # https://github.com/jazzband/django-model-utils
django-allauth==0.51.0 # https://github.com/pennersr/django-allauth