PKr+Jvplugs_newsletter/views.py""" Newsletter views """ from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from rest_framework import viewsets from rest_framework import status from rest_framework.response import Response from rest_framework import permissions from rest_framework.decorators import api_view, permission_classes from plugs_newsletter import models from plugs_newsletter import serializers from plugs_newsletter import signals @api_view(['POST']) @permission_classes([permissions.AllowAny]) def subscribe(request): """ Subscribe newsletter """ serializer = serializers.SubscriptionSerializer(data=request.data) serializer.is_valid(raise_exception=True) subscription = serializer.save() subscription.send_subscribed_email() signals.email_added_to_newsletter.send(sender=subscription) data = {"message": _("Subscribed successfuly.")} return Response(data=data, status=status.HTTP_201_CREATED) @api_view(['POST']) @permission_classes([permissions.AllowAny]) def unsubscribe(request): """ Unsubscribe from the newsletter """ kwargs = {'email': request.data.get('email')} subscription = get_object_or_404(models.Subscription, **kwargs) signals.email_removed_from_newsletter.send(sender=subscription) subscription.delete() subscription.send_unsubscribed_email() data = {"message": _("Unsubscribed successfuly from the newsletter")} return Response(data=data, status=status.HTTP_200_OK) PKz+J6plugs_newsletter/serializers.py""" Newsletter Serializers """ from rest_framework import serializers from plugs_newsletter import models class SubscriptionSerializer(serializers.ModelSerializer): """ Subscription Serializer """ # pylint: disable=R0903 class Meta: """ Metaclass definition """ model = models.Subscription fields = ('id', 'email', 'first_name', 'last_name', 'created', 'updated') PKr+Japlugs_newsletter/signals.py""" Plugs Newsletter Signals """ from django.dispatch import Signal # sent when an email was added to the newsletter email_added_to_newsletter = Signal() # sent when an email was removed from the newsletter email_removed_from_newsletter = Signal() PK +J" plugs_newsletter/models.py""" Plugs Newsletter Models """ from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import get_user_model from django.db import IntegrityError, transaction from plugs_newsletter.settings import plugs_newsletter_settings from plugs_core import mixins from plugs_mail.utils import to_email from plugs_newsletter import emails class Subscription(mixins.Timestampable, models.Model): """ Newsletter subscription """ error_msgs = {'unique': _('This email is already registered.')} email = models.EmailField(unique=True, error_messages=error_msgs) first_name = models.CharField(max_length=35, null=True) last_name = models.CharField(max_length=35, null=True) def __str__(self): return self.email def send_subscribed_email(self): """ Send newsletter subscribed successfuly to subscriber """ to_email(emails.NewsletterSubscribed, self.email) def send_unsubscribed_email(self): """ Send newsletter unsubscribed successfuly to subscriber """ to_email(emails.NewsletterUnsubscribed, self.email) # pylint: disable=R0903 class Meta: """ Providing verbose names is recommended if we want to use i18n in admin site """ verbose_name = _('subscription') verbose_name_plural = _('subscriptions') @receiver(post_save, sender=settings.AUTH_USER_MODEL) def update_newsletter_settings(instance, created, **kwargs): """ Every time the user is saved we need to check the newsletter flag and update it accordingly """ send_newsletter = getattr(instance, plugs_newsletter_settings['NEWSLETTER_ENDPOINT']) if send_newsletter: kwargs = { 'email': instance.email, 'first_name': instance.first_name, 'last_name': instance.last_name } try: with transaction.atomic(): subscription = Subscription.objects.create(**kwargs) except IntegrityError: pass else: try: subscription = Subscription.objects.get(email=instance.email) subscription.delete() except Subscription.DoesNotExist: pass @receiver(post_save, sender=Subscription) def update_user_settings_start(instance, **kwargs): """ Every time a newsletter subscription occurs update user settings if the subscriber is a user """ try: user = get_user_model().objects.get(email=instance.email) setattr(user, plugs_newsletter_settings['AUTH_MODEL_NEWSLETTER_FIELD'], True) user.save() except get_user_model().DoesNotExist: pass @receiver(post_delete, sender=Subscription) def update_user_settings_stop(instance, **kwargs): """ Every time a newsletter unsubscription occurs update user settings if the subscriber is a user """ try: user = get_user_model().objects.get(email=instance.email) setattr(user, plugs_newsletter_settings['AUTH_MODEL_NEWSLETTER_FIELD'], False) user.save() except get_user_model().DoesNotExist: pass PKHJ^xplugs_newsletter/__init__.py__version__ = '0.1.4' PKz+JAAplugs_newsletter/admin.pyfrom django.contrib import admin from plugs_newsletter.models import Subscription class SubscriptionAdmin(admin.ModelAdmin): """ Subscription Model Admin """ fields = ('email', 'first_name', 'last_name', 'created', 'updated') readonly_fields = ('id', 'created', 'updated') list_display = ('email', 'first_name', 'last_name') search_fields = ('email', 'first_name', 'last_name') admin.site.register(Subscription, SubscriptionAdmin) PKz+JPplugs_newsletter/emails.py""" Plugs Newsletter Emails """ from plugs_mail.mail import PlugsMail class NewsletterSubscribed(PlugsMail): """ Email sent to subscriber after newsletter subscription """ template = 'NEWSLETTER_SUBSCRIBED' description = 'Email sent to subscriber after newsletter subscription' class NewsletterUnsubscribed(PlugsMail): """ Email sent to subscriber after newsletter unsubscription """ template = 'NEWSLETTER_UNSUBSCRIBED' description = 'Email sent to subscriber after newsletter unsubscription' PKz+J$cooplugs_newsletter/urls.py""" Newsletter APP urls """ from django.conf.urls import url from plugs_newsletter import views from plugs_newsletter.settings import plugs_newsletter_settings endpoint = plugs_newsletter_settings['NEWSLETTER_ENDPOINT'] urlpatterns = [ url(r'^' + endpoint + r'/subscribe/$', views.subscribe), url(r'^' + endpoint + r'/unsubscribe/$', views.unsubscribe) ] PK +J^166plugs_newsletter/settings.py""" Newsletter APP Settings """ from django.conf import settings PROJECT_SETTINGS = getattr(settings, 'PLUGS_NEWSLETTER', {}) DEFAULTS = { 'NEWSLETTER_ENDPOINT': 'newsletter', 'AUTH_MODEL_NEWSLETTER_FIELD': 'newsletter' } if not PROJECT_SETTINGS.get('NEWSLETTER_ENDPOINT'): PROJECT_SETTINGS['NEWSLETTER_ENDPOINT'] = DEFAULTS['NEWSLETTER_ENDPOINT'] if not PROJECT_SETTINGS.get('AUTH_MODEL_NEWSLETTER_FIELD'): PROJECT_SETTINGS['AUTH_MODEL_NEWSLETTER_FIELD'] = DEFAULTS['AUTH_MODEL_NEWSLETTER_FIELD'] plugs_newsletter_settings = PROJECT_SETTINGS PKz+Ji;plugs_newsletter/runtests.py#!/usr/bin/env python import os import sys import django from django.conf import settings from django.test.utils import get_runner if __name__ == "__main__": os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings' django.setup() TestRunner = get_runner(settings) test_runner = TestRunner() failures = test_runner.run_tests(["tests"]) sys.exit(bool(failures)) PKz+J$!HH'plugs_newsletter/tests/test_settings.pySECRET_KEY = 'afakesecret' INSTALLED_APPS = [ "plugs_newsletter", ] PKz+JqK32*plugs_newsletter/tests/test_serializers.py""" Tests Serializers """ from django.test import TestCase from plugs_newsletter import serializers class TestSerializers(TestCase): """ Tests Serializers """ def test_email_is_required_to_subscribe(self): """ Ensures email is required in subscription serializer """ serializer = serializers.SubscriptionSerializer(data={}) self.assertFalse(serializer.is_valid()) self.assertIn('email', serializer.errors.keys()) def test_subscription_serializer(self): """ Ensures subscription serializer """ data = {'email': 'janedoe@example.com', 'first_name': 'Jane', 'last_name': 'Doe'} serializer = serializers.SubscriptionSerializer(data=data) self.assertTrue(serializer.is_valid()) self.assertEqual(serializer.validated_data, data) self.assertEqual(serializer.errors, {}) PKz+Ju%=$plugs_newsletter/tests/test_views.py""" Testing Views """ from rest_framework.test import APIRequestFactory from plugs_core.testcases import PlugsAPITestCase from plugs_newsletter import views factory = APIRequestFactory() class TestsViews(PlugsAPITestCase): """ Testing Views """ def test_subscribe_using_valid_email(self): """ Ensures guest can subscribe with valid email """ request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.subscribe(request) self.assert201(response) def test_subscribe_using_valid_email(self): """ Ensures guest can subscribe with valid email """ request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.subscribe(request) self.assert201(response) def test_cannot_subscribe_twice_with_same_email(self): """ Ensures cannot subscribe twice with same email """ request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.subscribe(request) self.assert201(response) request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.subscribe(request) self.assert400(response) def test_can_unsubscribe(self): """ Ensures guest can unsubscribe """ request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.subscribe(request) self.assert201(response) request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.unsubscribe(request) self.assert200(response) def test_cannot_unsubscribe(self): """ Ensures guest cannot unsubscribe """ request = factory.post('', data={'email': 'janedoe@example.com'}) response = views.unsubscribe(request) self.assert404(response) PKz+J"plugs_newsletter/tests/__init__.pyPKz+J'plugs_newsletter/migrations/__init__.pyPKz+J̷r''+plugs_newsletter/migrations/0001_initial.py# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-22 17:59 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Subscription', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('email', models.EmailField(error_messages={'unique': 'This email is already registered.'}, max_length=254, unique=True)), ('first_name', models.CharField(max_length=35, null=True)), ('last_name', models.CharField(max_length=35, null=True)), ], options={ 'verbose_name': 'subscription', 'verbose_name_plural': 'subscriptions', }, ), ] PKz+J0t?plugs_newsletter/templates/emails/newsletter_subscribed_en.htmlEmail successfully added to the newsletter {% extends "base.html" %} {% block content %}

Email successfully added to the newsletter.

{% endblock %} PKz+JmmAplugs_newsletter/templates/emails/newsletter_unsubscribed_pt.htmlEmail removido da newsletter {% extends "base.html" %} {% block content %}

Removido

{% endblock %} PKz+J헐Aplugs_newsletter/templates/emails/newsletter_unsubscribed_en.htmlEmail successfully removed from the newsletter {% extends "base.html" %} {% block content %}

Email successfully removed from the newsletter

{% endblock %} PKz+J=?plugs_newsletter/templates/emails/newsletter_subscribed_pt.htmlEmail adicionado à newsletter {% extends "base.html" %} {% block content %}

Email adicionado com sucesso à nossa newsletter.

{% endblock %} PK'HJq{#0plugs_newsletter-0.1.4.dist-info/DESCRIPTION.rst============================= Plugs Newsletter ============================= .. image:: https://badge.fury.io/py/plugs-newsletter.png :target: https://badge.fury.io/py/plugs-newsletter .. image:: https://travis-ci.org/ricardolobo/plugs-newsletter.png?branch=master :target: https://travis-ci.org/ricardolobo/plugs-newsletter n Documentation ------------- The full documentation is at https://plugs-newsletter.readthedocs.io. Quickstart ---------- Install Plugs Newsletter:: pip install plugs-newsletter Add it to your `INSTALLED_APPS`: .. code-block:: python INSTALLED_APPS = ( ... 'plugs_newsletter.apps.PlugsNewsletterConfig', ... ) Add Plugs Newsletter's URL patterns: .. code-block:: python from plugs_newsletter import urls as plugs_newsletter_urls urlpatterns = [ ... url(r'^', include(plugs_newsletter_urls)), ... ] Features -------- * TODO Running Tests ------------- Does the code actually work? :: source /bin/activate (myenv) $ pip install tox (myenv) $ tox Credits ------- Tools used in rendering this package: * Cookiecutter_ * `cookiecutter-djangopackage`_ .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage History ------- 0.1.0 (2017-01-11) ++++++++++++++++++ * First release on PyPI. PK'HJb7t.plugs_newsletter-0.1.4.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Framework :: Django", "Framework :: Django :: 1.9", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "ricardolobo@soloweb.pt", "name": "Ricardo Lobo", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/solocompt/plugs-newsletter"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["plugs-newsletter"], "license": "MIT", "metadata_version": "2.0", "name": "plugs-newsletter", "run_requires": [{"requires": ["django-model-utils (>=2.0)"]}], "summary": "n", "version": "0.1.4"}PK'HJj.plugs_newsletter-0.1.4.dist-info/top_level.txtplugs_newsletter PK'HJndnn&plugs_newsletter-0.1.4.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PK'HJ=0)plugs_newsletter-0.1.4.dist-info/METADATAMetadata-Version: 2.0 Name: plugs-newsletter Version: 0.1.4 Summary: n Home-page: https://github.com/solocompt/plugs-newsletter Author: Ricardo Lobo Author-email: ricardolobo@soloweb.pt License: MIT Keywords: plugs-newsletter Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Framework :: Django Classifier: Framework :: Django :: 1.9 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Requires-Dist: django-model-utils (>=2.0) ============================= Plugs Newsletter ============================= .. image:: https://badge.fury.io/py/plugs-newsletter.png :target: https://badge.fury.io/py/plugs-newsletter .. image:: https://travis-ci.org/ricardolobo/plugs-newsletter.png?branch=master :target: https://travis-ci.org/ricardolobo/plugs-newsletter n Documentation ------------- The full documentation is at https://plugs-newsletter.readthedocs.io. Quickstart ---------- Install Plugs Newsletter:: pip install plugs-newsletter Add it to your `INSTALLED_APPS`: .. code-block:: python INSTALLED_APPS = ( ... 'plugs_newsletter.apps.PlugsNewsletterConfig', ... ) Add Plugs Newsletter's URL patterns: .. code-block:: python from plugs_newsletter import urls as plugs_newsletter_urls urlpatterns = [ ... url(r'^', include(plugs_newsletter_urls)), ... ] Features -------- * TODO Running Tests ------------- Does the code actually work? :: source /bin/activate (myenv) $ pip install tox (myenv) $ tox Credits ------- Tools used in rendering this package: * Cookiecutter_ * `cookiecutter-djangopackage`_ .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage History ------- 0.1.0 (2017-01-11) ++++++++++++++++++ * First release on PyPI. PK'HJ7 'plugs_newsletter-0.1.4.dist-info/RECORDplugs_newsletter/__init__.py,sha256=aBEbDvx4LMg8A1TJJR6dEHu8rQODVin528hLS_EDvuA,22 plugs_newsletter/admin.py,sha256=Efwd7g4H78ZqDvn7xywc7s_gUXghSs0GjNc4c3RJz5s,462 plugs_newsletter/emails.py,sha256=O9lo2KK5NXCT1_dZYTXb3VWTIAK9Eir-NrRyot8Aq80,537 plugs_newsletter/models.py,sha256=B3YZIzrulMV8Iu_ANNMsZv_6OgBFnyF2bzpiwlqHStk,3306 plugs_newsletter/runtests.py,sha256=x9CYKK30PzZ6V9DpJSqxT95606k5qgdR7FWPqOz3B6E,390 plugs_newsletter/serializers.py,sha256=qwAnCHux7qiPCCLzTH3VwvHktVr15_sjO-cZosj2C-k,427 plugs_newsletter/settings.py,sha256=v8wjjhOKFi_aEln7q-CuaVOno43rkRAL3pnKb2doKSs,566 plugs_newsletter/signals.py,sha256=3H4bsmmqz-zZfezor39pGcQTsbSbO5UvVOsLTMQx4BY,251 plugs_newsletter/urls.py,sha256=w33Ab2zfZu9Ju71m2AC7jN2oA_LZZkXXVwLIohsHSJ8,367 plugs_newsletter/views.py,sha256=dskYO69f-f8AaewAkgcZTLJDQyjDFBzoeyXkxaB6yFk,1504 plugs_newsletter/migrations/0001_initial.py,sha256=JX1TzH4Sj3Qwnmt4KJHDmQoQ2oxc7tpyXWOnIOCkr1o,1063 plugs_newsletter/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_newsletter/templates/emails/newsletter_subscribed_en.html,sha256=AEMeDcsjFrmXi-F5r6RvKc4gbs70UY3B0Ks2FAou3Tk,158 plugs_newsletter/templates/emails/newsletter_subscribed_pt.html,sha256=Hi13m6NxHBRc6n-GpylIhKLvSyeWCxKluic2y1yu07s,152 plugs_newsletter/templates/emails/newsletter_unsubscribed_en.html,sha256=Lm9oLQH60NBwBXdbwPFykgQe_EruRRAnHfnJAqAG8SY,165 plugs_newsletter/templates/emails/newsletter_unsubscribed_pt.html,sha256=FPrHLIdBvoft91HdSuhwTPDIdMxTyfK1-tWWuQ3EIrQ,109 plugs_newsletter/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_newsletter/tests/test_serializers.py,sha256=pAiX_k2jyVBTDVWFkKp99b2_qYhxqgCU34MYRiOi-BM,901 plugs_newsletter/tests/test_settings.py,sha256=r-IX8phn0NHmDuf20D7Cganmon1MOhdd2B_KAk0RIO8,72 plugs_newsletter/tests/test_views.py,sha256=X60pT1lQPgwNsWBQ1LgyPbMJ8VMlQ0Ru5FT3EfoCdI8,1938 plugs_newsletter-0.1.4.dist-info/DESCRIPTION.rst,sha256=M90CJ5AjW5c4C5R-iP4TNu_AVWPKYs0K0C8C_FyoYBg,1440 plugs_newsletter-0.1.4.dist-info/METADATA,sha256=BZ2Ity0RaLw8A8eZDK_M9kLo8PrHEoWfWvT6ci47dUE,2223 plugs_newsletter-0.1.4.dist-info/RECORD,, plugs_newsletter-0.1.4.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 plugs_newsletter-0.1.4.dist-info/metadata.json,sha256=ameSSSE1RLUYmIpC9sFaP2nEVszq_xgxasmvWaY3sCY,930 plugs_newsletter-0.1.4.dist-info/top_level.txt,sha256=1dg0I1h-EBOn_y4iaj5DYQQP31Mvg9DYxGHYiEDSlDQ,17 PKr+Jvplugs_newsletter/views.pyPKz+J6plugs_newsletter/serializers.pyPKr+Japlugs_newsletter/signals.pyPK +J" 3 plugs_newsletter/models.pyPKHJ^xUplugs_newsletter/__init__.pyPKz+JAAplugs_newsletter/admin.pyPKz+JPplugs_newsletter/emails.pyPKz+J$cooplugs_newsletter/urls.pyPK +J^166plugs_newsletter/settings.pyPKz+Ji;plugs_newsletter/runtests.pyPKz+J$!HH' plugs_newsletter/tests/test_settings.pyPKz+JqK32*]!plugs_newsletter/tests/test_serializers.pyPKz+Ju%=$*%plugs_newsletter/tests/test_views.pyPKz+J",plugs_newsletter/tests/__init__.pyPKz+J'>-plugs_newsletter/migrations/__init__.pyPKz+J̷r''+-plugs_newsletter/migrations/0001_initial.pyPKz+J0t?1plugs_newsletter/templates/emails/newsletter_subscribed_en.htmlPKz+JmmA2plugs_newsletter/templates/emails/newsletter_unsubscribed_pt.htmlPKz+J헐A3plugs_newsletter/templates/emails/newsletter_unsubscribed_en.htmlPKz+J=?4plugs_newsletter/templates/emails/newsletter_subscribed_pt.htmlPK'HJq{#05plugs_newsletter-0.1.4.dist-info/DESCRIPTION.rstPK'HJb7t.;plugs_newsletter-0.1.4.dist-info/metadata.jsonPK'HJj.?plugs_newsletter-0.1.4.dist-info/top_level.txtPK'HJndnn&?plugs_newsletter-0.1.4.dist-info/WHEELPK'HJ=0)@plugs_newsletter-0.1.4.dist-info/METADATAPK'HJ7 'Iplugs_newsletter-0.1.4.dist-info/RECORDPKdS