diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a80e77e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,37 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 +continuation_indent_size = 8 +combine_as_imports = true +max_line_length = 88 +multi_line_output = 4 +quote_type = single + +[*.js] +indent_size = 4 + +[*.jsx] +indent_size = 4 + +[*.scss] +indent_size = 4 + +[*.ts] +indent_size = 4 + +[*.tsx] +indent_size = 4 + +[*.yml] +indent_size = 4 + +[*.html] +indent_size = 4 diff --git a/Pipfile b/Pipfile index f88cf08..b888ee0 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,8 @@ django-localflavor = "*" django-extensions = "*" markdown = "*" pillow = "*" +django-anymail = {extras = ["mailgun"], version = "*"} +django-templated-email = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index cc474d4..0d23208 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d96bc4c80d45b75316ad378ebf86a010b0fbbfe651b086caad0d70a037d515b2" + "sha256": "9590e03874968b489838ba3255c583c160e54a777547932a761d550b42ce46df" }, "pipfile-spec": 6, "requires": { @@ -47,6 +47,22 @@ "index": "pypi", "version": "==5.2.7" }, + "certifi": { + "hashes": [ + "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", + "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + ], + "markers": "python_version >= '3.6'", + "version": "==2022.6.15" + }, + "charset-normalizer": { + "hashes": [ + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + ], + "markers": "python_version >= '3.6'", + "version": "==2.1.0" + }, "click": { "hashes": [ "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", @@ -60,7 +76,7 @@ "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" ], - "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'", + "markers": "python_version < '4' and python_full_version >= '3.6.2'", "version": "==0.3.0" }, "click-plugins": { @@ -85,6 +101,17 @@ "index": "pypi", "version": "==4.0.6" }, + "django-anymail": { + "extras": [ + "mailgun" + ], + "hashes": [ + "sha256:49d83d7c16316ca86a624097496881d59b7d71b16bf1c5211cffa5b19ef98d0c", + "sha256:783342d49dd07d68778b81dd12a94c86e1d217463a68a85450a0513fabe31345" + ], + "index": "pypi", + "version": "==8.6" + }, "django-appconf": { "hashes": [ "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d", @@ -133,6 +160,30 @@ "index": "pypi", "version": "==3.1" }, + "django-render-block": { + "hashes": [ + "sha256:a01bfdb839e2f6b3f88a99021597484392bbd15d084f9a796e3e5658bae800f4", + "sha256:fbdd8be56cefcfd794756a2e62117cc031f9c5de3ef4bb53e9a3f877a359a1a7" + ], + "markers": "python_version >= '3.6'", + "version": "==0.9.1" + }, + "django-templated-email": { + "hashes": [ + "sha256:49d61840ec551e640adaf341146e94d6f9058ae01df964480850bf988046e5eb", + "sha256:bf1b68ffe6c8794c0c50e2ce20e3a166c6d511b3879abbd3cf059a3fc2fe2e60" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3.5'", + "version": "==3.3" + }, "kombu": { "hashes": [ "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610", @@ -268,6 +319,14 @@ ], "version": "==1.1.0" }, + "requests": { + "hashes": [ + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + ], + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.1" + }, "rjsmin": { "hashes": [ "sha256:05efa485dfddb6418e3b86d8862463aa15641a61f6ae05e7e6de8f116ee77c69", @@ -309,6 +368,14 @@ "markers": "python_version >= '3.5'", "version": "==0.4.2" }, + "urllib3": { + "hashes": [ + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", + "version": "==1.26.10" + }, "vine": { "hashes": [ "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", diff --git a/src/core/forms.py b/src/core/forms.py index ed2a5fc..cbb1b1f 100644 --- a/src/core/forms.py +++ b/src/core/forms.py @@ -1,7 +1,11 @@ from django import forms -from django.core.mail import send_mail +from django.contrib.sites.shortcuts import get_current_site +from templated_email import send_templated_mail + from .models import Post, Comment +NOTIFICATION_EMAIL_TEMPLATE = 'notification.html' + class CommentCreateForm(forms.ModelForm): class Meta: @@ -22,20 +26,27 @@ class CommentCreateForm(forms.ModelForm): }) } - def send_notification(self): - # subject = self.cleaned_data['content_object'] + def send_notification(self, request): author = self.instance.author message = self.cleaned_data.get('content') post = Post.objects.get(pk=self.cleaned_data['object_id']) + url = get_current_site(request).domain + post.get_absolute_url() recipients = list(post.recipients.all().values_list('email', flat=True)) if author in recipients: recipients.remove(author.email) - send_mail( - post.title, - message, - author.email, - recipients + context = { + 'subject': post.title, + 'author': author, + 'message': message, + 'url': url + } + + send_templated_mail( + template_name=NOTIFICATION_EMAIL_TEMPLATE, + from_email=author.email, + recipient_list=recipients, + context=context ) diff --git a/src/core/migrations/0007_alter_post_recipients.py b/src/core/migrations/0007_alter_post_recipients.py new file mode 100644 index 0000000..a6b3d13 --- /dev/null +++ b/src/core/migrations/0007_alter_post_recipients.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.6 on 2022-07-21 00:19 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0006_alter_post_recipients'), + ] + + operations = [ + migrations.AlterField( + model_name='post', + name='recipients', + field=models.ManyToManyField(blank=True, related_name='subscriptions', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index e405cf3..b755395 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -130,7 +130,6 @@ class Post(models.Model): recipients = models.ManyToManyField( User, blank=True, - null=True, related_name='subscriptions' ) tags = GenericRelation(Tag) diff --git a/src/core/templates/core/topic_detail.html b/src/core/templates/core/topic_detail.html index 747591e..47ac338 100644 --- a/src/core/templates/core/topic_detail.html +++ b/src/core/templates/core/topic_detail.html @@ -45,7 +45,9 @@ {% endif %} {% empty %} -

No posts

+ + No posts + {% endfor %} diff --git a/src/core/templates/core/topic_list.html b/src/core/templates/core/topic_list.html index 3704141..df59428 100644 --- a/src/core/templates/core/topic_list.html +++ b/src/core/templates/core/topic_list.html @@ -34,7 +34,7 @@ {% empty %} -
  • No topics yet
  • + No topics yet {% endfor %} diff --git a/src/core/views.py b/src/core/views.py index 5a15197..7f611d4 100644 --- a/src/core/views.py +++ b/src/core/views.py @@ -34,7 +34,7 @@ class CommentCreateView(LoginRequiredMixin, CreateView): def form_valid(self, form): form.instance.author = self.request.user - form.send_notification() + form.send_notification(self.request) return super().form_valid(form) diff --git a/src/forum/config.py b/src/forum/config.py index 76e23a8..ce00dc1 100644 --- a/src/forum/config.py +++ b/src/forum/config.py @@ -1,19 +1,32 @@ -from dotenv import load_dotenv import os +from dotenv import load_dotenv load_dotenv() -DATABASE_CONFIG={ -'port' : os.environ.get('DATABASE_PORT', ''), -'host' : os.environ.get('DATABASE_HOST', ''), -'password' : os.environ.get('DATABASE_PASSWORD', ''), -'user' : os.environ.get('DATABASE_USER', ''), -'name' : os.environ.get('DATABASE_NAME', ''), -'engine' : os.environ.get('DATABASE_ENGINE', ''), +DEBUG = os.environ.get('DEBUG', 'True') == 'True' + +DATABASE_CONFIG = { + 'port': os.environ.get('DATABASE_PORT', ''), + 'host': os.environ.get('DATABASE_HOST', ''), + 'password': os.environ.get('DATABASE_PASSWORD', ''), + 'user': os.environ.get('DATABASE_USER', ''), + 'name': os.environ.get('DATABASE_NAME', ''), + 'engine': os.environ.get('DATABASE_ENGINE', ''), } SECRET_KEY = os.environ.get('SECRET_KEY', '') -CACHE_CONFIG={ -'location' : os.environ.get('CACHE_LOCATION', ''), -'backend' : os.environ.get('CACHE_BACKEND', ''), + +ANYMAIL_CONFIG = { + 'MAILGUN_API_KEY': os.environ.get('MAILGUN_API_KEY', ''), + 'MAILGUN_SENDER_DOMAIN': os.environ.get('MAILGUN_SENDER_DOMAIN', '') } +DEFAULT_FROM_EMAIL = os.environ.get( + 'DEFAULT_FROM_EMAIL', 'notifications@forum.windmillapps.org' +) +SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'server@forum.windmillapps.org') +ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL', 'debug@nathanjchapman.com') + +CACHE_CONFIG = { + 'location': os.environ.get('CACHE_LOCATION', ''), + 'backend': os.environ.get('CACHE_BACKEND', ''), +} diff --git a/src/forum/settings.py b/src/forum/settings.py index ced4ecf..2049aa2 100644 --- a/src/forum/settings.py +++ b/src/forum/settings.py @@ -8,9 +8,17 @@ from .config import * BASE_DIR = Path(__file__).resolve().parent.parent # Add Your Required Allow Host -ALLOWED_HOSTS = [] +if not DEBUG: + ALLOWED_HOSTS = [ + 'forum.windmillapps.org', + ] +else: + ALLOWED_HOSTS = ['192.168.68.106', '127.0.0.1', 'localhost'] -DEBUG = True +INTERNAL_IPS = [ + '127.0.0.1', + 'localhost', +] # Application definition @@ -22,10 +30,12 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', # 3rd party 'django_filters', 'compressor', + 'anymail', # Local 'accounts.apps.AccountsConfig', @@ -42,6 +52,7 @@ MIDDLEWARE = [ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'forum.middleware.TimezoneMiddleware', + 'django.contrib.sites.middleware.CurrentSiteMiddleware', ] @@ -140,3 +151,17 @@ STATICFILES_FINDERS = ( DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' AUTH_USER_MODEL = 'accounts.User' LOGIN_REDIRECT_URL = reverse_lazy('core:topic-list') + +# Email +ANYMAIL = ANYMAIL_CONFIG +EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' +ADMINS = ( + ('Nathan Chapman', ADMIN_EMAIL), +) + +MANAGERS = ADMINS + +TEMPLATED_EMAIL_BACKEND = 'templated_email.backends.vanilla_django.TemplateBackend' + +# Site ID +SITE_ID = 1 diff --git a/src/templates/templated_email/notification.html.email b/src/templates/templated_email/notification.html.email new file mode 100644 index 0000000..fee5456 --- /dev/null +++ b/src/templates/templated_email/notification.html.email @@ -0,0 +1,12 @@ +{% load helpers %} + +{% block subject %}The Forum: {{ subject }}{% endblock %} +{% block html %} +

    {{ author }} posted:

    + + {{ message|markdown|safe|truncatechars_html:64 }} + +

    + View or reply +

    +{% endblock %}