From aaeb42d46e0692feb11d8a0d1ea56167919c0b95 Mon Sep 17 00:00:00 2001 From: Jayjay Date: Thu, 12 Mar 2015 23:14:09 +0800 Subject: [PATCH 1/6] added updates to have subscribe and unsubcribe capability on newsletter via groups; added unsubscribe link on the end of newsletter even if not logged in --- ..._is_newsletter_subscribed__add_field_gr.py | 153 ++++++++++++++++++ .../migrations/0010_make_newsletter_keys.py | 147 +++++++++++++++++ tendenci/apps/user_groups/models.py | 37 +++++ .../templates/user_groups/nav.html | 7 + .../user_groups/newsletter_unsubscribe.html | 26 +++ tendenci/apps/user_groups/urls.py | 5 + tendenci/apps/user_groups/views.py | 59 +++++++ .../management/commands/send_newsletter.py | 3 + tendenci/core/newsletters/models.py | 15 +- .../newsletters/newsletter_unsubscribe.txt | 5 + .../default/Big City Newsletter.html | 2 +- .../default/Two Column Left Sidebar.html | 5 + .../default/Two Column Right Sidebar.html | 5 + 13 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 tendenci/apps/user_groups/migrations/0009_auto__add_field_groupmembership_is_newsletter_subscribed__add_field_gr.py create mode 100644 tendenci/apps/user_groups/migrations/0010_make_newsletter_keys.py create mode 100644 tendenci/apps/user_groups/templates/user_groups/newsletter_unsubscribe.html create mode 100644 tendenci/core/newsletters/templates/newsletters/newsletter_unsubscribe.txt diff --git a/tendenci/apps/user_groups/migrations/0009_auto__add_field_groupmembership_is_newsletter_subscribed__add_field_gr.py b/tendenci/apps/user_groups/migrations/0009_auto__add_field_groupmembership_is_newsletter_subscribed__add_field_gr.py new file mode 100644 index 0000000000..960b92c717 --- /dev/null +++ b/tendenci/apps/user_groups/migrations/0009_auto__add_field_groupmembership_is_newsletter_subscribed__add_field_gr.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'GroupMembership.is_newsletter_subscribed' + db.add_column('user_groups_groupmembership', 'is_newsletter_subscribed', + self.gf('django.db.models.fields.BooleanField')(default=True), + keep_default=False) + + # Adding field 'GroupMembership.newsletter_key' + db.add_column('user_groups_groupmembership', 'newsletter_key', + self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'GroupMembership.is_newsletter_subscribed' + db.delete_column('user_groups_groupmembership', 'is_newsletter_subscribed') + + # Deleting field 'GroupMembership.newsletter_key' + db.delete_column('user_groups_groupmembership', 'newsletter_key') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'entities.entity': { + 'Meta': {'ordering': "('entity_name',)", 'object_name': 'Entity'}, + 'admin_notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'allow_anonymous_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_anonymous_view': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_member_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_member_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_user_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_user_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'contact_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'create_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entity_creator'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'creator_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'email': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}), + 'entity_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'entity_parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'entity_children'", 'null': 'True', 'to': "orm['entities.Entity']"}), + 'entity_type': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'fax': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entity_owner'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'owner_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'status': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'status_detail': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '50'}), + 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'update_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}) + }, + 'user_groups.group': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Group'}, + 'allow_anonymous_view': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_member_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_member_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_self_add': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_self_remove': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_user_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_user_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'auto_respond': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'auto_respond_priority': ('django.db.models.fields.FloatField', [], {'default': '0', 'blank': 'True'}), + 'create_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'user_groups_group_creator'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'creator_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'dashboard_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email_recipient': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'entity': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_groups_group_entity'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['entities.Entity']", 'blank': 'True', 'null': 'True'}), + 'group': ('django.db.models.fields.related.OneToOneField', [], {'default': 'None', 'to': "orm['auth.Group']", 'unique': 'True', 'null': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'through': "orm['user_groups.GroupMembership']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'user_groups_group_owner'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'owner_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_permissions'", 'blank': 'True', 'to': "orm['auth.Permission']"}), + 'show_as_option': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('tendenci.core.base.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}), + 'status': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'status_detail': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '50'}), + 'sync_newsletters': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'default': "'distribution'", 'max_length': '75', 'blank': 'True'}), + 'update_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'user_groups.groupmembership': { + 'Meta': {'unique_together': "(('group', 'member'),)", 'object_name': 'GroupMembership'}, + 'create_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator_id': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'creator_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['user_groups.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_newsletter_subscribed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'member': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_member'", 'to': "orm['auth.User']"}), + 'newsletter_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'owner_id': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'owner_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'sort_order': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}), + 'status': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'status_detail': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '50'}), + 'update_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['user_groups'] \ No newline at end of file diff --git a/tendenci/apps/user_groups/migrations/0010_make_newsletter_keys.py b/tendenci/apps/user_groups/migrations/0010_make_newsletter_keys.py new file mode 100644 index 0000000000..a6282b6425 --- /dev/null +++ b/tendenci/apps/user_groups/migrations/0010_make_newsletter_keys.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..." + import uuid + from django.db.models import Q + + for i in orm.GroupMembership.objects.filter(Q(newsletter_key='')|Q(newsletter_key=None)): + i.newsletter_key = uuid.uuid1() + i.save() + + def backwards(self, orm): + "Write your backwards methods here." + pass + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'entities.entity': { + 'Meta': {'ordering': "('entity_name',)", 'object_name': 'Entity'}, + 'admin_notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'allow_anonymous_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_anonymous_view': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_member_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_member_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_user_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_user_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'contact_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'create_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entity_creator'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'creator_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'email': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}), + 'entity_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'entity_parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'entity_children'", 'null': 'True', 'to': "orm['entities.Entity']"}), + 'entity_type': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'fax': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entity_owner'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'owner_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'status': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'status_detail': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '50'}), + 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'update_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}) + }, + 'user_groups.group': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Group'}, + 'allow_anonymous_view': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_member_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_member_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_self_add': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_self_remove': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'allow_user_edit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'allow_user_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'auto_respond': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'auto_respond_priority': ('django.db.models.fields.FloatField', [], {'default': '0', 'blank': 'True'}), + 'create_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'user_groups_group_creator'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'creator_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'dashboard_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email_recipient': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'entity': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_groups_group_entity'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['entities.Entity']", 'blank': 'True', 'null': 'True'}), + 'group': ('django.db.models.fields.related.OneToOneField', [], {'default': 'None', 'to': "orm['auth.Group']", 'unique': 'True', 'null': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'through': "orm['user_groups.GroupMembership']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'user_groups_group_owner'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'owner_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_permissions'", 'blank': 'True', 'to': "orm['auth.Permission']"}), + 'show_as_option': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('tendenci.core.base.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}), + 'status': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'status_detail': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '50'}), + 'sync_newsletters': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'default': "'distribution'", 'max_length': '75', 'blank': 'True'}), + 'update_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'user_groups.groupmembership': { + 'Meta': {'unique_together': "(('group', 'member'),)", 'object_name': 'GroupMembership'}, + 'create_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator_id': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'creator_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['user_groups.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_newsletter_subscribed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'member': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_member'", 'to': "orm['auth.User']"}), + 'newsletter_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'owner_id': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'owner_username': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'sort_order': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}), + 'status': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'status_detail': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '50'}), + 'update_dt': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['user_groups'] + symmetrical = True diff --git a/tendenci/apps/user_groups/models.py b/tendenci/apps/user_groups/models.py index 92449119a4..bd420e650a 100644 --- a/tendenci/apps/user_groups/models.py +++ b/tendenci/apps/user_groups/models.py @@ -5,11 +5,13 @@ from django.utils.translation import ugettext_lazy as _ from django.template.defaultfilters import slugify from django.db.utils import IntegrityError +from django.core.urlresolvers import reverse from tendenci.core.base.fields import SlugField from tendenci.core.perms.models import TendenciBaseModel from tendenci.apps.user_groups.managers import GroupManager from tendenci.apps.entities.models import Entity +from tendenci.core.site_settings.utils import get_setting class Group(TendenciBaseModel): @@ -154,6 +156,11 @@ class GroupMembership(models.Model): create_dt = models.DateTimeField(auto_now_add=True, editable=False) update_dt = models.DateTimeField(auto_now=True) + + # The following fields are for Newletter Subscribe and Unsubscribe + is_newsletter_subscribed = models.BooleanField(default=True) + newsletter_key = models.CharField(max_length=50, null=True, blank=True) # will be the secret key for unsubscribe + def __unicode__(self): return self.group.name @@ -184,3 +191,33 @@ def add_to_group(cls, **kwargs): status=status, status_detail=status_detail ) + + def subscribe_to_newsletter(self): + if not self.is_newsletter_subscribed: + self.is_newsletter_subscribed = True + # change newsletter_key when subscribing + self.newsletter_key = uuid.uuid1() + self.save() + return True + + return False + + def unsubscribe_to_newsletter(self): + if self.is_newsletter_subscribed: + self.is_newsletter_subscribed = False + # change newsletter_key when unsubscribing + self.newsletter_key = uuid.uuid1() + self.save() + return True + + return False + + @property + def noninteractive_unsubscribe_url(self): + site_url = get_setting('site', 'global', 'siteurl') + unsubscribe_path = reverse('group.newsletter_unsubscribe_noninteractive', kwargs={ + 'group_slug': self.group.slug, + 'newsletter_key': self.newsletter_key + }) + + return site_url + unsubscribe_path diff --git a/tendenci/apps/user_groups/templates/user_groups/nav.html b/tendenci/apps/user_groups/templates/user_groups/nav.html index a6a9d2345b..58939dd75b 100644 --- a/tendenci/apps/user_groups/templates/user_groups/nav.html +++ b/tendenci/apps/user_groups/templates/user_groups/nav.html @@ -73,6 +73,13 @@ {% if nav_object.allow_self_add and user.is_authenticated or can_add_member %}
  • {% trans "Add Myself to Group" %}
  • {% endif %} + {% if is_group_member and gm %} + {% if gm.is_newsletter_subscribed %} +
  • {% trans 'Unsubscribe to Newsletter' %}
  • + {% else %} +
  • {% trans 'Subscribe to Newsletter' %}
  • + {% endif %} + {% endif %} {% if can_delete_group %}
  • {% trans "Delete Group" %}
  • diff --git a/tendenci/apps/user_groups/templates/user_groups/newsletter_unsubscribe.html b/tendenci/apps/user_groups/templates/user_groups/newsletter_unsubscribe.html new file mode 100644 index 0000000000..32d7344ce9 --- /dev/null +++ b/tendenci/apps/user_groups/templates/user_groups/newsletter_unsubscribe.html @@ -0,0 +1,26 @@ +{% extends "user_groups/base-wide.html" %} +{% load base_tags %} +{% load base_filters %} +{% load user_group_tags %} +{% load perm_tags %} +{% load pagination_tags %} +{% load i18n %} + +{% block title %}{% blocktrans with g=group.name %}{{ g }} - Group{% endblocktrans %}{% endblock %} + +{% block extra_head %} +{{ block.super }} + +{% endblock %} + +{% block body %} + +
    +
    +

    {% trans 'You have successfully unsubscribed from Newsletters.' %}

    +




    +
    +
    + + +{% endblock %} diff --git a/tendenci/apps/user_groups/urls.py b/tendenci/apps/user_groups/urls.py index 70fc2c4f60..2db356e24f 100644 --- a/tendenci/apps/user_groups/urls.py +++ b/tendenci/apps/user_groups/urls.py @@ -43,4 +43,9 @@ url(r'^%s/(?P[-.\w]+)/selfremove/(?P\d+)/$' % urlpath, 'group_membership_self_remove', name='group.selfremove'), url(r'^%s/(?P[-.\w]+)/deleteuser/(?P\d+)/$' % urlpath, 'groupmembership_delete', name='group.deleteuser'), url(r'^%s/(?P[-.\w]+)/import/status/(?P[-\w]+)/$' % urlpath, "subscribers_import_status", name='subscribers_import_status'), + + # newsletter subscription_urls + url(r'^%s/(?P[-.\w]+)/newsletters/subscribe/interactive/$' % urlpath, 'subscribe_to_newsletter_interactive', name='group.newsletter_subscribe_interactive'), + url(r'^%s/(?P[-.\w]+)/newsletters/unsubscribe/interactive/$' % urlpath, 'unsubscribe_to_newsletter_interactive', name='group.newsletter_unsubscribe_interactive'), + url(r'^%s/(?P[-.\w]+)/(?P[-.\w]+)/newsletters/unsubscribe/noninteractive/$' % urlpath, 'unsubscribe_to_newsletter_noninteractive', name='group.newsletter_unsubscribe_noninteractive'), ) diff --git a/tendenci/apps/user_groups/views.py b/tendenci/apps/user_groups/views.py index 16092c86b7..f6d5799d8c 100644 --- a/tendenci/apps/user_groups/views.py +++ b/tendenci/apps/user_groups/views.py @@ -87,6 +87,13 @@ def group_detail(request, group_slug, template_name="user_groups/detail.html"): if not has_view_perm(request.user,'user_groups.view_group',group): raise Http403 + if group in request.user.profile.get_groups(): + is_group_member = True + gm = GroupMembership.objects.get(group=group, member=request.user) + else: + is_group_member = False + gm = None + EventLog.objects.log(instance=group) groupmemberships = GroupMembership.objects.filter( @@ -1044,3 +1051,55 @@ def import_download_template(request, file_ext='.csv'): data_row_list = [] return render_excel(filename, import_field_list, data_row_list, file_ext) + + +# Newsletter stuff here: +@login_required +def subscribe_to_newsletter_interactive(request, group_slug): + group = get_object_or_404(Group, slug=group_slug) + + groupmembership = get_object_or_404(GroupMembership, + group=group, + member=request.user, + status=True, + status_detail='active') + + if groupmembership.subscribe_to_newsletter(): + messages.success(request, _('Successfully subscribed to Newsletters.')) + + return redirect(reverse('group.detail', kwargs={'group_slug': group_slug})) + + +@login_required +def unsubscribe_to_newsletter_interactive(request, group_slug): + group = get_object_or_404(Group, slug=group_slug) + + groupmembership = get_object_or_404(GroupMembership, + group=group, + member=request.user, + status=True, + status_detail='active') + + if groupmembership.unsubscribe_to_newsletter(): + messages.success(request, _('Successfully unsubscribed to Newsletters.')) + + return redirect(reverse('group.detail', kwargs={'group_slug': group_slug})) + + +def subscribe_to_newsletter_noninteractive(request, group_slug): + pass + + +def unsubscribe_to_newsletter_noninteractive(request, group_slug, newsletter_key): + group = get_object_or_404(Group, slug=group_slug) + + groupmembership = get_object_or_404(GroupMembership, + group=group, + status=True, + status_detail='active', + newsletter_key=newsletter_key) + if not groupmembership.unsubscribe_to_newsletter(): + raise Http404 + + return render(request, 'user_groups/newsletter_unsubscribe.html') + diff --git a/tendenci/core/newsletters/management/commands/send_newsletter.py b/tendenci/core/newsletters/management/commands/send_newsletter.py index aae68c7541..2db1534f5e 100644 --- a/tendenci/core/newsletters/management/commands/send_newsletter.py +++ b/tendenci/core/newsletters/management/commands/send_newsletter.py @@ -71,6 +71,9 @@ def handle(self, *args, **options): if '[firstname]' in body: body = body.replace('[firstname]', recipient.member.first_name) + if '[unsubscribe_url]' in body: + body = body.replace('[unsubscribe_url]', recipient.noninteractive_unsubscribe_url) + email_to_send = Email( subject=subject, body=body, diff --git a/tendenci/core/newsletters/models.py b/tendenci/core/newsletters/models.py index 2a854a6663..899f390f41 100644 --- a/tendenci/core/newsletters/models.py +++ b/tendenci/core/newsletters/models.py @@ -240,7 +240,10 @@ def generate_from_default_template(self, request, template): content = content.replace('[menu]', data.get('jumplink_content')) if '[content]' in content: - full_content = data.get('opening_text') + data.get('login_content') + data.get('footer_text') + full_content = data.get('opening_text') + \ + data.get('login_content') + \ + data.get('footer_text') + \ + data.get('unsubscribe_text') content = content.replace('[content]', full_content) if '[articles]' in content: @@ -276,6 +279,9 @@ def generate_newsletter_contents(self, request): {'newsletter': self }, context_instance=RequestContext(request)) + unsubscribe_txt = render_to_string('newsletters/newsletter_unsubscribe.txt', + context_instance=RequestContext(request)) + login_content = "" if self.include_login: login_content = render_to_string('newsletters/login.txt', @@ -331,6 +337,7 @@ def generate_newsletter_contents(self, request): data = { 'opening_text': opening_txt, 'footer_text' : footer_txt, + 'unsubscribe_text': unsubscribe_txt, 'login_content': login_content, 'jumplink_content': jumplink_content, 'articles_content': articles_content, @@ -376,7 +383,8 @@ def get_recipients(self): if self.member_only: members = GroupMembership.objects.filter( status=True, - status_detail='active').order_by( + status_detail='active', + is_newsletter_subscribed=True).order_by( 'member__email').distinct( 'member__email') @@ -385,7 +393,8 @@ def get_recipients(self): members = GroupMembership.objects.filter( group=group, status=True, - status_detail='active').order_by( + status_detail='active', + is_newsletter_subscribed=True).order_by( 'member__email').distinct( 'member__email') diff --git a/tendenci/core/newsletters/templates/newsletters/newsletter_unsubscribe.txt b/tendenci/core/newsletters/templates/newsletters/newsletter_unsubscribe.txt new file mode 100644 index 0000000000..7d300fce17 --- /dev/null +++ b/tendenci/core/newsletters/templates/newsletters/newsletter_unsubscribe.txt @@ -0,0 +1,5 @@ +{% load i18n %} + + + You are receiving this email because your email is subscribed to our mailing list. Want to to Unsubscribe from this newsletter? Click here. + \ No newline at end of file diff --git a/tendenci/core/newsletters/templates/newsletters/templates/default/Big City Newsletter.html b/tendenci/core/newsletters/templates/newsletters/templates/default/Big City Newsletter.html index 9e410a4a6d..4686a55d7f 100644 --- a/tendenci/core/newsletters/templates/newsletters/templates/default/Big City Newsletter.html +++ b/tendenci/core/newsletters/templates/newsletters/templates/default/Big City Newsletter.html @@ -30,7 +30,7 @@

    [menu] 

    [content]

    - + diff --git a/tendenci/core/newsletters/templates/newsletters/templates/default/Two Column Left Sidebar.html b/tendenci/core/newsletters/templates/newsletters/templates/default/Two Column Left Sidebar.html index 04bc4bbe02..6b1bf32aad 100644 --- a/tendenci/core/newsletters/templates/newsletters/templates/default/Two Column Left Sidebar.html +++ b/tendenci/core/newsletters/templates/newsletters/templates/default/Two Column Left Sidebar.html @@ -306,6 +306,11 @@