diff --git a/newsletter/__init__.py b/newsletter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/newsletter/admin.py b/newsletter/admin.py new file mode 100644 index 00000000..a81345d0 --- /dev/null +++ b/newsletter/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from newsletter.models import NewsletterCache + + +class NewsletterCacheAdmin(admin.ModelAdmin): + pass + + +admin.site.register(NewsletterCache, NewsletterCacheAdmin) diff --git a/newsletter/apps.py b/newsletter/apps.py new file mode 100644 index 00000000..e4a2ad5b --- /dev/null +++ b/newsletter/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class WeeklyNewsletterConfig(AppConfig): + name = 'newsletter' diff --git a/newsletter/management/__init__.py b/newsletter/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/newsletter/management/commands/__init__.py b/newsletter/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/newsletter/management/commands/send_newsletter.py b/newsletter/management/commands/send_newsletter.py new file mode 100644 index 00000000..019d5dbe --- /dev/null +++ b/newsletter/management/commands/send_newsletter.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand + +from newsletter.services import Newsletter + + +class Command(BaseCommand): + help = 'tbd' + + def handle(self, *args, **kwargs): + Newsletter() diff --git a/newsletter/migrations/0001_initial.py b/newsletter/migrations/0001_initial.py new file mode 100644 index 00000000..2b5389d3 --- /dev/null +++ b/newsletter/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-02-19 03:26 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='NewsletterCache', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('last_time_sent', models.DateTimeField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/newsletter/migrations/0002_auto_20180219_0326.py b/newsletter/migrations/0002_auto_20180219_0326.py new file mode 100644 index 00000000..bd9dda20 --- /dev/null +++ b/newsletter/migrations/0002_auto_20180219_0326.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-02-19 03:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('newsletter', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='newslettercache', + name='last_time_sent', + field=models.DateTimeField(null=True), + ), + ] diff --git a/newsletter/migrations/0003_newslettercache_subscribes.py b/newsletter/migrations/0003_newslettercache_subscribes.py new file mode 100644 index 00000000..7e4df564 --- /dev/null +++ b/newsletter/migrations/0003_newslettercache_subscribes.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-02-22 05:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('newsletter', '0002_auto_20180219_0326'), + ] + + operations = [ + migrations.AddField( + model_name='newslettercache', + name='subscribes', + field=models.BooleanField(default=False), + ), + ] diff --git a/newsletter/migrations/__init__.py b/newsletter/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/newsletter/models.py b/newsletter/models.py new file mode 100644 index 00000000..fa5d9fdb --- /dev/null +++ b/newsletter/models.py @@ -0,0 +1,8 @@ +from django.contrib.auth.models import User +from django.db import models + + +class NewsletterCache(models.Model): + user = models.ForeignKey(User) + last_time_sent = models.DateTimeField(null=True) + subscribes = models.BooleanField(default=False) diff --git a/newsletter/services.py b/newsletter/services.py new file mode 100644 index 00000000..446d86e4 --- /dev/null +++ b/newsletter/services.py @@ -0,0 +1,80 @@ +from datetime import datetime, timedelta + +from django.conf import settings +from django.contrib.auth.models import User +from django.core.mail import EmailMultiAlternatives +from django.core.signing import Signer +from django.template.loader import get_template +from django.urls import reverse + +from newsletter.models import NewsletterCache +from package.models import TimelineEvent, Project + + +class Newsletter: + NEWSLETTER_FREQUENCY_IN_DAYS = 7 + AMOUNT_OF_LATEST_PROJECTS_IN_NEWSLETTER = 3 + + @staticmethod + def get_unsubscribe_link(user): + token = Signer().sign(user.username).split(':')[1] + return reverse('unsubscribe', kwargs={'username': user.username, 'token': token}) + + @staticmethod + def get_user_favorite_projects(user): + return user.project_set.all() + + def __init__(self): + for user in User.objects.all(): + newsletter_cache, _ = NewsletterCache.objects.get_or_create(user=user) + newsletter_cache.subscribes = True + if not newsletter_cache.subscribes: + continue + # newsletter_cache.last_time_sent = datetime.now() + newsletter_cache.save() + self.send_newsletter(user) + + @staticmethod + def get_favorite_project_events(user): + newsletter_cache, _ = NewsletterCache.objects.get_or_create(user=user) + favorite_projects = Newsletter.get_user_favorite_projects(user) + timeline_events = TimelineEvent.objects.filter( + project__in=favorite_projects, + date__gte=datetime.now() - timedelta(days=Newsletter.NEWSLETTER_FREQUENCY_IN_DAYS)) + return timeline_events + + @staticmethod + def get_latest_projects(): + return Project.objects.all().order_by('-id')[:Newsletter.AMOUNT_OF_LATEST_PROJECTS_IN_NEWSLETTER] + + @staticmethod + def send_newsletter(user): + favorite_project_events = Newsletter.get_favorite_project_events(user) + if not favorite_project_events: + return False + + latest_projects = Newsletter.get_latest_projects() + + unsubscribe_link = Newsletter.get_unsubscribe_link(user) + + plain_template = get_template('newsletter.txt') + html_template = get_template('newsletter.html') + + d = {'username': user.username, + 'favorite_project_events': favorite_project_events, + 'latest_projects': latest_projects, + 'unsubscribe_link': unsubscribe_link, + } + + plain_content = plain_template.render(d) + html_content = html_template.render(d) + + # msg = EmailMultiAlternatives( + # subject='{0} Newsletter'.format(settings.EMAIL_SUBJECT_PREFIX), + # body=plain_content, + # from_email=settings.VALIDATION_EMAIL_SENDER, + # to=['patryk@perduta.net'], + # ) + # msg.attach_alternative(html_content, 'text/html') + # msg.esp_extra = {"sender_domain": settings.EMAIL_SENDER_DOMAIN} + # msg.send() diff --git a/newsletter/templates/newsletter.html b/newsletter/templates/newsletter.html new file mode 100644 index 00000000..42930b96 --- /dev/null +++ b/newsletter/templates/newsletter.html @@ -0,0 +1,119 @@ + + + + Happy email + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+   +
+

+

Logo steemprojects

+

+ Latest events from your favorite projects +

+


+
+ + + + + +
+ + + SteemConnect 2.0: + Easy, Fast, Efficient Access to the Steem Blockchain +
+
+
+
+ + + + + +
+ + + + Introducing SteemConnect by Busy : Identity, authentication, authorization for + Steem blockchain’s ap + +
+
+
+
+ + + + + +
+ + + + The REALLY gentle guide to becoming a witness + +
+
+ +

+ Your welcome, @steemprojects + Follow us on + steem +

+
+
+
+
+

+ To unsubscribe click this link: unsubscribe. +

+
+
+ + diff --git a/newsletter/templates/newsletter.txt b/newsletter/templates/newsletter.txt new file mode 100644 index 00000000..e69de29b diff --git a/newsletter/tests.py b/newsletter/tests.py new file mode 100644 index 00000000..ae6200ee --- /dev/null +++ b/newsletter/tests.py @@ -0,0 +1,6 @@ +from django.test import TestCase + + +class NewsletterServiceTestCase(TestCase): + def test_get_user_favorite_projects(self): + self.fail() diff --git a/newsletter/urls.py b/newsletter/urls.py new file mode 100644 index 00000000..278c3519 --- /dev/null +++ b/newsletter/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url + +from newsletter.views import unsubscribe, ask_for_newsletter + +urlpatterns = [ + url(regex=r'^unsubscribe/(?P[\w.@+-]+)/(?P[\w.:\-_=]+)/$', view=unsubscribe, name='unsubscribe'), + url(regex=r'^ask/$', view=ask_for_newsletter, name='ask_for_newsletter'), +] diff --git a/newsletter/views.py b/newsletter/views.py new file mode 100644 index 00000000..7e56e047 --- /dev/null +++ b/newsletter/views.py @@ -0,0 +1,42 @@ +from django.contrib import messages +from django.contrib.auth.models import User +from django.core.signing import Signer, BadSignature +from django.shortcuts import render, redirect, get_object_or_404 +from social_django.utils import load_strategy + +from newsletter.models import NewsletterCache +from social_auth_local.decorators import render_to + + +def unsubscribe(request, username, token): + user = get_object_or_404(User, username=username) + newsletter_cache = NewsletterCache.objects.get(user=user) + + try: + key = '{}:{}'.format(username, token) + Signer().unsign(key) + except BadSignature: + messages.add_message(request, messages.ERROR, 'Your subscribtion cancellation link is invalid.') + return redirect('/') + + if newsletter_cache.subscribes: + newsletter_cache.subscribes = False + newsletter_cache.save() + messages.add_message(request, messages.INFO, 'You\'ve been succesfully unsubscribed from our newsletter. ;-(') + return redirect('/') + + messages.add_message(request, messages.INFO, 'You are already have been unsubscribed from our newsletter!') + return redirect('/') + + +@render_to('social_auth_local/ask_for_newsletter.html') +def ask_for_newsletter(request): + strategy = load_strategy() + partial_token = request.GET.get('partial_token') + partial = strategy.partial_load(partial_token) + + return { + 'ask_for_newsletter': True, + 'partial_backend_name': partial.backend if partial else None, + 'partial_token': partial_token, + } diff --git a/settings/base.py b/settings/base.py index af79d567..9bd46ee9 100644 --- a/settings/base.py +++ b/settings/base.py @@ -137,6 +137,7 @@ "apiv3", "social_auth_local", "im", + 'newsletter' ] PREREQ_APPS = [ @@ -311,7 +312,10 @@ 'social_auth_local.pipeline.social_user', # CUSTOM PIPELINE - 'social_auth_local.pipeline.require_email', + # 'social_auth_local.pipeline.require_email', + + # CUSTOM PIPELINE + 'social_auth_local.pipeline.ask_for_newsletter', # Make up a username for this person, appends a random string at the end if # there's any collision. diff --git a/social_auth_local/pipeline.py b/social_auth_local/pipeline.py index 22b978c2..c7cce3f4 100644 --- a/social_auth_local/pipeline.py +++ b/social_auth_local/pipeline.py @@ -61,6 +61,15 @@ def require_email(strategy, details, user=None, is_new=False, *args, **kwargs): ) +@partial +def ask_for_newsletter(strategy, details, *args, **kwargs): + subscription = strategy.request_data().get('subscription') + if subscription: + details['subscription'] = subscription + current_partial = kwargs.get('current_partial') + return strategy.redirect('/newsletter/ask/?partial_token={}'.format(current_partial.token)) + + def save_profile_pipeline(backend, user, response, details, social, *args, **kwargs): try: # profile could be created for a user which previously logged in diff --git a/social_auth_local/views.py b/social_auth_local/views.py index 30676e14..bcb4376c 100644 --- a/social_auth_local/views.py +++ b/social_auth_local/views.py @@ -29,7 +29,6 @@ def merging_accounts(request): } - @render_to('social_auth_local/email_required.html') def require_email(request): """Email required page""" diff --git a/templates/social_auth_local/ask_for_newsletter.html b/templates/social_auth_local/ask_for_newsletter.html new file mode 100644 index 00000000..c7815320 --- /dev/null +++ b/templates/social_auth_local/ask_for_newsletter.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% load i18n static %} + +{% block head_title %}{% trans "Email Required" %}{% endblock %} + +{% block body_class %}email_required{% endblock %} + +{% block extra_head %} + +{% endblock %} + +{% block body %} +
+

Newsletter

+
+ + + Do you really desire to get our newsletter? + +
+
+{% endblock %} + diff --git a/urls.py b/urls.py index bbeee78a..9d2be922 100644 --- a/urls.py +++ b/urls.py @@ -36,6 +36,7 @@ url(r"^projects/", include("package.urls")), url(r"^grids/", include("grid.urls")), url(r"^feeds/", include("feeds.urls")), + url(r'^newsletter/', include('newsletter.urls')), url(r"^categories/(?P[-\w]+)/$", category, name="category"), url(r"^categories/$", homepage, name="categories"),