Added email notifications

This commit is contained in:
Fergal Moran
2014-01-25 23:46:11 +00:00
parent f29173b98b
commit d91d33dee1
17 changed files with 951 additions and 252 deletions

View File

@@ -1,13 +1,17 @@
import urlparse
import re
from django.contrib.sites.models import Site
from django.template.defaultfilters import slugify
__author__ = 'fergalm'
def urlclean(url):
#remove double slashes
ret = urlparse.urljoin(url, urlparse.urlparse(url).path.replace('//','/'))
ret = urlparse.urljoin(url, urlparse.urlparse(url).path.replace('//', '/'))
return ret
def unique_slugify(instance, value, slug_field_name='slug', queryset=None, slug_separator='-'):
"""
Calculates and stores a unique slug of ``value`` for an instance.
@@ -44,7 +48,7 @@ def unique_slugify(instance, value, slug_field_name='slug', queryset=None, slug_
slug = original_slug
end = '%s%s' % (slug_separator, next)
if slug_len and len(slug) + len(end) > slug_len:
slug = slug[:slug_len-len(end)]
slug = slug[:slug_len - len(end)]
slug = _slug_strip(slug, slug_separator)
slug = '%s%s' % (slug, end)
next += 1
@@ -74,4 +78,13 @@ def _slug_strip(value, separator='-'):
if separator != '-':
re_sep = re.escape(separator)
value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
return value
return value
def is_absolute(url):
return bool(urlparse.urlparse(url).scheme)
def wrap_full(url):
if not is_absolute(url):
url = "http://%s%s" % (Site.objects.get_current().domain, url)
return url

View File

@@ -171,6 +171,7 @@ INSTALLED_APPS = (
'django_jenkins',
'dbbackup',
'jfu',
'djrill',
#'backbone_tastypie',
)
@@ -229,17 +230,20 @@ SENDFILE_ROOT = os.path.join(MEDIA_ROOT, 'mixes')
SENDFILE_URL = '/media/mixes'
import mimetypes
mimetypes.add_type("text/xml", ".plist", False)
HTML_MINIFY = not localsettings.DEBUG
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = localsettings.EMAIL_HOST
EMAIL_PORT = localsettings.EMAIL_PORT
DEFAULT_FROM_EMAIL = 'DSS ChatBot <chatbot@deepsouthsounds.com>'
DEFAULT_HTTP_PROTOCOL = 'http'
EMAIL_BACKEND = 'djrill.mail.backends.djrill.DjrillBackend'
MANDRILL_API_KEY = localsettings.MANDRILL_API_KEY
if DEBUG:
import mimetypes
@@ -272,3 +276,4 @@ GEOIP_PATH = localsettings.GEOIP_PATH
from pipelinesettings import *

View File

@@ -29,262 +29,262 @@ logger = logging.getLogger(__name__)
class AjaxHandler(object):
# Get an instance of a logger
# Get an instance of a logger
def __init__(self, api_name="v1"):
self.api_name = api_name
def __init__(self, api_name="v1"):
self.api_name = api_name
@property
def urls(self):
pattern_list = [
url(r'^mix/add_comment/$', 'spa.ajax.mix_add_comment', name='mix_add_comment'),
url(r'^mix/comments/(?P<mix_id>\d+)/$', 'spa.ajax.mix_comments', name='ajax_mix_comments'),
url(r'^header/$', 'spa.ajax.header', name='header'),
url(r'^session_play_count/$', 'spa.ajax.session_play_count'),
url(r'^mix_stream_url/(?P<mix_id>\d+)/$', 'spa.ajax.get_mix_stream_url'),
url(r'^release_player/(?P<release_id>\d+)/$', 'spa.ajax.release_player'),
url(r'^live_now_playing/$', 'spa.ajax.live_now_playing'),
url(r'^mark_read/$', 'spa.ajax.mark_read'),
url(r'^facebook_post_likes_allowed/$', 'spa.ajax.facebook_post_likes_allowed',
name='ajax_facebook_post_likes_allowed'),
url(r'^upload_mix_image/(?P<mix_id>\d+)/$', 'spa.ajax.upload_mix_image', name='ajax_upload_mix_image'),
url(r'^upload_release_image/(?P<release_id>\d+)/$', 'spa.ajax.upload_release_image',
name='ajax_upload_release_image'),
url(r'^upload_avatar_image/$', 'spa.ajax.upload_avatar_image', name='ajax_upload_avatar_image'),
url(r'^lookup/search/$', 'spa.ajax.lookup_search', name='ajax_lookup'),
url(r'^lookup/(?P<source>\w+)/$', 'spa.ajax.lookup', name='ajax_lookup'),
]
return pattern_list
@property
def urls(self):
pattern_list = [
url(r'^mix/add_comment/$', 'spa.ajax.mix_add_comment', name='mix_add_comment'),
url(r'^mix/comments/(?P<mix_id>\d+)/$', 'spa.ajax.mix_comments', name='ajax_mix_comments'),
url(r'^header/$', 'spa.ajax.header', name='header'),
url(r'^session_play_count/$', 'spa.ajax.session_play_count'),
url(r'^mix_stream_url/(?P<mix_id>\d+)/$', 'spa.ajax.get_mix_stream_url'),
url(r'^release_player/(?P<release_id>\d+)/$', 'spa.ajax.release_player'),
url(r'^live_now_playing/$', 'spa.ajax.live_now_playing'),
url(r'^mark_read/$', 'spa.ajax.mark_read'),
url(r'^facebook_post_likes_allowed/$', 'spa.ajax.facebook_post_likes_allowed',
name='ajax_facebook_post_likes_allowed'),
url(r'^upload_mix_image/(?P<mix_id>\d+)/$', 'spa.ajax.upload_mix_image', name='ajax_upload_mix_image'),
url(r'^upload_release_image/(?P<release_id>\d+)/$', 'spa.ajax.upload_release_image',
name='ajax_upload_release_image'),
url(r'^upload_avatar_image/$', 'spa.ajax.upload_avatar_image', name='ajax_upload_avatar_image'),
url(r'^lookup/search/$', 'spa.ajax.lookup_search', name='ajax_lookup'),
url(r'^lookup/(?P<source>\w+)/$', 'spa.ajax.lookup', name='ajax_lookup'),
]
return pattern_list
def _get_json(payload, key='value'):
data = {
key: payload
}
return simplejson.dumps(data)
data = {
key: payload
}
return simplejson.dumps(data)
@render_to('inc/header.html')
def header(request):
return HttpResponse(render_to_response('inc/header.html'))
return HttpResponse(render_to_response('inc/header.html'))
def session_play_count(request):
"""
"""
:param request:
:return: Number of tracks played in this session
"""
if not request.user.is_authenticated():
if 'play_count' in request.session:
result = simplejson.dumps({
'play_count': request.session['play_count']
})
else:
result = simplejson.dumps({
'play_count': '0'
})
else:
result = simplejson.dumps({
'play_count': '0'
})
return HttpResponse(result, mimetype='application/json')
:param request:
:return: Number of tracks played in this session
"""
if not request.user.is_authenticated():
if 'play_count' in request.session:
result = simplejson.dumps({
'play_count': request.session['play_count']
})
else:
result = simplejson.dumps({
'play_count': '0'
})
else:
result = simplejson.dumps({
'play_count': '0'
})
return HttpResponse(result, mimetype='application/json')
def get_mix_stream_url(request, mix_id):
try:
if not request.user.is_authenticated():
if 'play_count' in request.session:
request.session['play_count'] += 1
else:
request.session['play_count'] = 1
try:
if not request.user.is_authenticated():
if 'play_count' in request.session:
request.session['play_count'] += 1
else:
request.session['play_count'] = 1
mix = Mix.objects.get(pk=mix_id)
mix.add_play(request.user)
data = {
'stream_url': mix.get_stream_path(),
'description': mix.description,
'item_url': mix.get_absolute_url(),
'title': mix.title
}
return HttpResponse(simplejson.dumps(data), mimetype="application/json")
except Exception, e:
logger.exception("Error getting mix stream url")
return HttpResponse("Error getting mix stream url", status=401)
mix = Mix.objects.get(pk=mix_id)
mix.add_play(request.user)
data = {
'stream_url': mix.get_stream_path(),
'description': mix.description,
'item_url': mix.get_absolute_url(),
'title': mix.title
}
return HttpResponse(simplejson.dumps(data), mimetype="application/json")
except Exception, e:
logger.exception("Error getting mix stream url")
return HttpResponse("Error getting mix stream url", status=401)
def live_now_playing(request):
now_playing_info = live.get_now_playing(
localsettings.JS_SETTINGS['LIVE_STREAM_URL'],
localsettings.JS_SETTINGS['LIVE_STREAM_PORT'],
localsettings.JS_SETTINGS['LIVE_STREAM_MOUNT'])
try:
if now_playing_info is not None:
return HttpResponse(
simplejson.dumps({
'stream_url': 'http://%s:%s/%s' % (
localsettings.JS_SETTINGS['LIVE_STREAM_URL'],
localsettings.JS_SETTINGS['LIVE_STREAM_PORT'],
localsettings.JS_SETTINGS['LIVE_STREAM_MOUNT']),
'description': now_playing_info['stream_description'],
'title': now_playing_info['current_song']
}), mimetype="application/json")
except:
return HttpResponseNotFound(now_playing_info)
now_playing_info = live.get_now_playing(
localsettings.JS_SETTINGS['LIVE_STREAM_URL'],
localsettings.JS_SETTINGS['LIVE_STREAM_PORT'],
localsettings.JS_SETTINGS['LIVE_STREAM_MOUNT'])
try:
if now_playing_info is not None:
return HttpResponse(
simplejson.dumps({
'stream_url': 'http://%s:%s/%s' % (
localsettings.JS_SETTINGS['LIVE_STREAM_URL'],
localsettings.JS_SETTINGS['LIVE_STREAM_PORT'],
localsettings.JS_SETTINGS['LIVE_STREAM_MOUNT']),
'description': now_playing_info['stream_description'],
'title': now_playing_info['current_song']
}), mimetype="application/json")
except:
return HttpResponseNotFound(now_playing_info)
return None
return None
@render_to('inc/release_player.html')
def release_player(request, release_id):
return HttpResponse('Hello Sailor')
return HttpResponse('Hello Sailor')
def mix_add_comment(request):
if request.POST:
comment = Comment()
comment.mix_id = request.POST['mixid']
comment.user = request.user
comment.comment = request.POST['comment']
comment.time_index = request.POST['position']
comment.save()
if request.POST:
comment = Comment()
comment.mix_id = request.POST['mixid']
comment.user = request.user
comment.comment = request.POST['comment']
comment.time_index = request.POST['position']
comment.save()
return HttpResponse(_get_json('Comment posted', 'description'))
else:
return HttpResponse(_get_json('Error posting', 'description'))
return HttpResponse(_get_json('Comment posted', 'description'))
else:
return HttpResponse(_get_json('Error posting', 'description'))
@render_to('inc/comment_list.html')
def mix_comments(request, mix_id):
return {
"results": Comment.objects.filter(mix_id=mix_id),
}
return {
"results": Comment.objects.filter(mix_id=mix_id),
}
@login_required
def mark_read(request):
profile = request.user.get_profile()
if profile is not None:
Notification.objects.filter(to_user=profile).update(accepted_date=datetime.now())
return HttpResponse('Success', status=200)
pass
profile = request.user.get_profile()
if profile is not None:
Notification.objects.filter(to_user=profile).update(accepted_date=datetime.now())
return HttpResponse('Success', status=200)
pass
return HttpResponse('Unauthorized', status=401)
return HttpResponse('Unauthorized', status=401)
@login_required()
def facebook_post_likes_allowed(request):
profile = request.user.get_profile()
if profile is not None:
likes_allowed = profile.activity_sharing & UserProfile.ACTIVITY_SHARE_LIKES
facebook_allowed = profile.activity_sharing_networks & UserProfile.ACTIVITY_SHARE_NETWORK_FACEBOOK
profile = request.user.get_profile()
if profile is not None:
likes_allowed = profile.activity_sharing & UserProfile.ACTIVITY_SHARE_LIKES
facebook_allowed = profile.activity_sharing_networks & UserProfile.ACTIVITY_SHARE_NETWORK_FACEBOOK
return HttpResponse(_get_json(bool(likes_allowed & facebook_allowed)), mimetype="application/json")
return HttpResponse(_get_json(bool(likes_allowed & facebook_allowed)), mimetype="application/json")
return HttpResponse(_get_json(False), mimetype="application/json")
return HttpResponse(_get_json(False), mimetype="application/json")
@csrf_exempt
def upload_release_image(request, release_id):
try:
if 'release_image' in request.FILES and release_id is not None:
release = Release.objects.get(pk=release_id)
if release is not None:
release.release_image = request.FILES['release_image']
release.save()
return HttpResponse(_get_json("Success"))
except Exception, ex:
logger.exception("Error uploading release image")
return HttpResponse(_get_json("Failed"))
try:
if 'release_image' in request.FILES and release_id is not None:
release = Release.objects.get(pk=release_id)
if release is not None:
release.release_image = request.FILES['release_image']
release.save()
return HttpResponse(_get_json("Success"))
except Exception, ex:
logger.exception("Error uploading release image")
return HttpResponse(_get_json("Failed"))
def upload_mix_image(request, mix_id):
try:
if len(request.FILES) != 0:
mix = Mix.objects.get(pk=mix_id)
if mix:
mix.mix_image = request.FILES[request.FILES.keys()[0]]
mix.save()
return HttpResponse(json.dumps({'status': 'OK', 'url': mix.get_image_url()}))
except Exception, ex:
logger.exception("Error uploading avatar: %s", ex.message)
return HttpResponse(json.dumps({'status': 'failed', 'message': ex.message}))
try:
if len(request.FILES) != 0:
mix = Mix.objects.get(pk=mix_id)
if mix:
mix.mix_image = request.FILES[request.FILES.keys()[0]]
mix.save()
return HttpResponse(json.dumps({'status': 'OK', 'url': mix.get_image_url()}))
except Exception, ex:
logger.exception("Error uploading avatar: %s", ex.message)
return HttpResponse(json.dumps({'status': 'failed', 'message': ex.message}))
return HttpResponse(json.dumps({'status': 'failed', 'message': 'No image file found'}))
return HttpResponse(json.dumps({'status': 'failed', 'message': 'No image file found'}))
def upload_avatar_image(request):
# TODO: fergal.moran@gmail.com
# Problem here that only the current user can update avatar
# Might need to allow staff to change other's avatars?
try:
if len(request.FILES) != 0:
profile = request.user.get_profile()
if profile:
profile.avatar_image = request.FILES[request.FILES.keys()[0]]
profile.avatar_type = 'custom'
profile.save()
return HttpResponse(json.dumps({'status': 'OK', 'url': profile.get_avatar_image()}))
except Exception, ex:
logger.exception("Error uploading avatar: %s", ex.message)
return HttpResponse(json.dumps({'status': 'failed', 'message': ex.message}))
# TODO: fergal.moran@gmail.com
# Problem here that only the current user can update avatar
# Might need to allow staff to change other's avatars?
try:
if len(request.FILES) != 0:
profile = request.user.get_profile()
if profile:
profile.avatar_image = request.FILES[request.FILES.keys()[0]]
profile.avatar_type = 'custom'
profile.save()
return HttpResponse(json.dumps({'status': 'OK', 'url': profile.get_avatar_image()}))
except Exception, ex:
logger.exception("Error uploading avatar: %s", ex.message)
return HttpResponse(json.dumps({'status': 'failed', 'message': ex.message}))
return HttpResponse(json.dumps({'status': 'failed', 'message': 'No image file found'}))
return HttpResponse(json.dumps({'status': 'failed', 'message': 'No image file found'}))
@require_POST
@login_required
def upload(request):
# The assumption here is that jQuery File Upload
# has been configured to send files one at a time.
# If multiple files can be uploaded simultaneously,
# 'file' may be a list of files.
try:
uid = request.POST['upload-hash']
in_file = request.FILES['file'] if request.FILES else None
fileName, extension = os.path.splitext(in_file.name)
# The assumption here is that jQuery File Upload
# has been configured to send files one at a time.
# If multiple files can be uploaded simultaneously,
# 'file' may be a list of files.
try:
uid = request.POST['upload-hash']
in_file = request.FILES['file'] if request.FILES else None
fileName, extension = os.path.splitext(in_file.name)
file_storage = FileSystemStorage(location=os.path.join(settings.CACHE_ROOT, "mixes"))
cache_file = file_storage.save("%s%s" % (uid, extension), ContentFile(in_file.read()))
file_storage = FileSystemStorage(location=os.path.join(settings.CACHE_ROOT, "mixes"))
cache_file = file_storage.save("%s%s" % (uid, extension), ContentFile(in_file.read()))
create_waveform_task.delay(in_file=os.path.join(file_storage.base_location, cache_file), uid=uid)
create_waveform_task.delay(in_file=os.path.join(file_storage.base_location, cache_file), uid=uid)
file_dict = {
'size': in_file.size,
'uid': uid
}
file_dict = {
'size': in_file.size,
'uid': uid
}
return UploadResponse(request, file_dict)
except Exception, ex:
logger.exception(ex.message)
raise
return UploadResponse(request, file_dict)
except Exception, ex:
logger.exception(ex.message)
raise
@csrf_exempt
def lookup_search(request):
query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else ''
if query != '':
filter_field = Mix.get_lookup_filter_field()
kwargs = {
'{0}__{1}'.format(filter_field, 'icontains'): query,
}
rows = Mix.objects.values("title").filter(**kwargs)
#results = serializers.serialize("json", rows, fields="title",)
results = json.dumps(rows)
return HttpResponse(results, mimetype='application/json')
query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else ''
if query != '':
filter_field = Mix.get_lookup_filter_field()
kwargs = {
'{0}__{1}'.format(filter_field, 'icontains'): query,
}
rows = Mix.objects.values("title").filter(**kwargs)
#results = serializers.serialize("json", rows, fields="title",)
results = json.dumps(rows)
return HttpResponse(results, mimetype='application/json')
@csrf_exempt
def lookup(request, source):
query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else ''
if query != '':
model = get_model('spa', source)
if model is not None:
filter_field = model.get_lookup_filter_field()
kwargs = {
'{0}__{1}'.format(filter_field, 'icontains'): query,
}
rows = model.objects.filter(**kwargs)
results = json.to_ajax(rows, filter_field)
return HttpResponse(simplejson.dumps(results), mimetype='application/json')
return HttpResponse(_get_json("Key failure in lookup"), mimetype='application/json')
query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else ''
if query != '':
model = get_model('spa', source)
if model is not None:
filter_field = model.get_lookup_filter_field()
kwargs = {
'{0}__{1}'.format(filter_field, 'icontains'): query,
}
rows = model.objects.filter(**kwargs)
results = json.to_ajax(rows, filter_field)
return HttpResponse(simplejson.dumps(results), mimetype='application/json')
return HttpResponse(_get_json("Key failure in lookup"), mimetype='application/json')

View File

@@ -61,18 +61,6 @@ class UserResource(BackboneCompatibleResource):
return semi_filtered
def _patch_resource(self, bundle):
#Handle the patched items from backbone
if 'is_following' in bundle.data:
if bundle.data['is_following']:
bundle.obj.add_follower(bundle.request.user.get_profile())
activity = ActivityFollow()
activity.user = bundle.request.user.get_profile()
activity.to_user = bundle.obj
activity.save()
else:
bundle.obj.remove_follower(bundle.request.user.get_profile())
def obj_update(self, bundle, skip_errors=False, **kwargs):
"""

View File

@@ -0,0 +1,240 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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 'Notification.notification_html'
db.add_column(u'spa_notification', 'notification_html',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Notification.notification_html'
db.delete_column(u'spa_notification', 'notification_html')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'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'}),
u'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'})
},
'spa._lookup': {
'Meta': {'object_name': '_Lookup'},
'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'spa.activity': {
'Meta': {'object_name': 'Activity'},
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['spa.UserProfile']", 'null': 'True', 'blank': 'True'})
},
'spa.activitycomment': {
'Meta': {'object_name': 'ActivityComment', '_ormbases': ['spa.Activity']},
u'activity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa.Activity']", 'unique': 'True', 'primary_key': 'True'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_comments'", 'to': "orm['spa.Mix']"})
},
'spa.activitydownload': {
'Meta': {'object_name': 'ActivityDownload', '_ormbases': ['spa.Activity']},
u'activity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa.Activity']", 'unique': 'True', 'primary_key': 'True'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_downloads'", 'to': "orm['spa.Mix']"})
},
'spa.activityfavourite': {
'Meta': {'object_name': 'ActivityFavourite', '_ormbases': ['spa.Activity']},
u'activity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa.Activity']", 'unique': 'True', 'primary_key': 'True'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_favourites'", 'to': "orm['spa.Mix']"})
},
'spa.activityfollow': {
'Meta': {'object_name': 'ActivityFollow', '_ormbases': ['spa.Activity']},
u'activity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa.Activity']", 'unique': 'True', 'primary_key': 'True'}),
'to_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_follow'", 'to': "orm['spa.UserProfile']"})
},
'spa.activitylike': {
'Meta': {'object_name': 'ActivityLike', '_ormbases': ['spa.Activity']},
u'activity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa.Activity']", 'unique': 'True', 'primary_key': 'True'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_likes'", 'to': "orm['spa.Mix']"})
},
'spa.activityplay': {
'Meta': {'object_name': 'ActivityPlay', '_ormbases': ['spa.Activity']},
u'activity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa.Activity']", 'unique': 'True', 'primary_key': 'True'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_plays'", 'to': "orm['spa.Mix']"})
},
'spa.chatmessage': {
'Meta': {'object_name': 'ChatMessage'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.TextField', [], {}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'chat_messages'", 'null': 'True', 'to': "orm['spa.UserProfile']"})
},
'spa.comment': {
'Meta': {'object_name': 'Comment'},
'comment': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['spa.Mix']"}),
'time_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
'spa.event': {
'Meta': {'object_name': 'Event'},
'attendees': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'attendees'", 'symmetrical': 'False', 'to': u"orm['auth.User']"}),
'date_created': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2014, 1, 25, 0, 0)'}),
'event_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2014, 1, 25, 0, 0)'}),
'event_description': ('tinymce.models.HTMLField', [], {}),
'event_recurrence': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['spa.Recurrence']"}),
'event_time': ('django.db.models.fields.TimeField', [], {'default': 'datetime.datetime(2014, 1, 25, 0, 0)'}),
'event_title': ('django.db.models.fields.CharField', [], {'max_length': '250'}),
'event_venue': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['spa.Venue']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'spa.genre': {
'Meta': {'object_name': 'Genre'},
'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
},
'spa.label': {
'Meta': {'object_name': 'Label'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'spa.mix': {
'Meta': {'object_name': 'Mix'},
'description': ('django.db.models.fields.TextField', [], {}),
'download_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'duration': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'favourites': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'favourites'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['spa.UserProfile']"}),
'filetype': ('django.db.models.fields.CharField', [], {'default': "'mp3'", 'max_length': '10'}),
'genres': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['spa.Genre']", 'symmetrical': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_featured': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'likes'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['spa.UserProfile']"}),
'mix_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '1024', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
'uid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '38', 'blank': 'True'}),
'upload_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 1, 25, 0, 0)'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mixes'", 'to': "orm['spa.UserProfile']"}),
'waveform_generated': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'spa.notification': {
'Meta': {'object_name': 'Notification'},
'accepted_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'from_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notifications'", 'null': 'True', 'to': "orm['spa.UserProfile']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'notification_html': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'notification_text': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'notification_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'target': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
'to_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'to_notications'", 'to': "orm['spa.UserProfile']"}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'})
},
'spa.purchaselink': {
'Meta': {'object_name': 'PurchaseLink'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'track': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'purchase_link'", 'to': "orm['spa.Tracklist']"}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'spa.recurrence': {
'Meta': {'object_name': 'Recurrence', '_ormbases': ['spa._Lookup']},
u'_lookup_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['spa._Lookup']", 'unique': 'True', 'primary_key': 'True'})
},
'spa.release': {
'Meta': {'object_name': 'Release'},
'embed_code': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'release_artist': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'release_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2014, 1, 25, 0, 0)'}),
'release_description': ('django.db.models.fields.TextField', [], {}),
'release_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}),
'release_label': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['spa.Label']"}),
'release_title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['spa.UserProfile']"})
},
'spa.releaseaudio': {
'Meta': {'object_name': 'ReleaseAudio'},
'description': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'release': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'release_audio'", 'null': 'True', 'to': "orm['spa.Release']"})
},
'spa.tracklist': {
'Meta': {'object_name': 'Tracklist'},
'artist': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.SmallIntegerField', [], {}),
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'mix': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tracklist'", 'to': "orm['spa.Mix']"}),
'remixer': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'timeindex': ('django.db.models.fields.TimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'spa.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'activity_sharing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'activity_sharing_networks': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'avatar_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '1024', 'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'social'", 'max_length': '15'}),
'city': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'country': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
'display_name': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}),
'following': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'followers'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['spa.UserProfile']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_known_session': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'default': 'None', 'max_length': '50', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'spa.venue': {
'Meta': {'object_name': 'Venue'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'venue_address': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'venue_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}),
'venue_name': ('django.db.models.fields.CharField', [], {'max_length': '250'})
}
}
complete_apps = ['spa']

View File

@@ -1,11 +1,13 @@
import threading
import abc
from django.db import models
from model_utils.managers import InheritanceManager
from core.realtime.activity import post_activity
from core.utils.url import wrap_full
from spa.models.notification import Notification
from spa.models.userprofile import UserProfile
from spa.models._basemodel import _BaseModel
import abc
ACTIVITYTYPES = (
('p', 'played'),
@@ -30,7 +32,15 @@ class Activity(_BaseModel):
notification.notification_text = "%s %s %s" % (
self.user.get_nice_name() or "Anonymouse", self.get_verb_past(), self.get_object_name_for_notification())
notification.notification_url = self.get_object_url()
notification.notification_html = "<a href='%s'>%s</a> %s <a href='%s'>%s</a>" % (
wrap_full(self.user.get_profile_url() or "http://deepsounds.com"),
self.user.get_nice_name() or "Anonymouse",
self.get_verb_past(),
wrap_full(self.get_object_url()),
self.get_object_name_for_notification()
)
notification.notification_url = wrap_full(self.get_object_url())
notification.verb = self.get_verb_past()
notification.target = self.get_object_name()
notification.save()

View File

@@ -1,7 +1,7 @@
from django.contrib.auth.models import User
from django.db import models
from spa.models import _BaseModel, UserProfile
from spa.models.notification import Notification
from spa.models import _BaseModel
from spa.models.mix import Mix

View File

@@ -1,8 +1,12 @@
from django.contrib.auth.models import User
import threading
import mandrill
from django.db import models
from django.template import loader, Context
from core.realtime.notification import post_notification
from spa.models import _BaseModel, UserProfile
from dss import localsettings
from spa.models import _BaseModel
class NotificationThread(threading.Thread):
@@ -18,11 +22,12 @@ class NotificationThread(threading.Thread):
class Notification(_BaseModel):
to_user = models.ForeignKey(UserProfile, related_name='to_notications')
from_user = models.ForeignKey(UserProfile, related_name='notifications', null=True, blank=True)
to_user = models.ForeignKey('spa.UserProfile', related_name='to_notications')
from_user = models.ForeignKey('spa.UserProfile', related_name='notifications', null=True, blank=True)
date = models.DateTimeField(auto_now=True)
notification_text = models.CharField(max_length=1024)
notification_html = models.CharField(max_length=1024)
notification_url = models.URLField(null=True)
verb = models.CharField(max_length=200, null=True)
@@ -32,3 +37,40 @@ class Notification(_BaseModel):
def get_notification_url(self):
return '/api/v1/notification/%s' % self.id
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
self.send_notification_email()
return super(Notification, self).save(force_insert, force_update, using, update_fields)
def send_notification_email(self):
try:
t = loader.get_template('email/notification/new.html')
c = Context({
'user_name': self.to_user.get_nice_name(),
'notification_html': self.notification_html,
'title': self.notification_html
})
rendered = t.render(c)
mandrill_client = mandrill.Mandrill(localsettings.MANDRILL_API_KEY)
message = {
'inline_css': True,
'from_email': 'chatbot@deepsouthsounds.com',
'from_name': 'DSS ChatBot',
'headers': {'Reply-To': 'chatbot@deepsouthsounds.com'},
'metadata': {'website': 'www.deepsouthsounds.com'},
'subject': self.notification_text,
'to': [{'email': 'fergal.moran@gmail.com',
'name': 'Fergal Moran',
'type': 'to'}],
'html': rendered,
'text': 'Get yourself some HTML man!',
}
result = mandrill_client.messages.send(message=message, async=False)
print result
except mandrill.Error, e: # Mandrill errors are thrown as exceptions
print 'A mandrill error occurred: %s - %s' % (e.__class__, e)

View File

@@ -7,15 +7,17 @@ from django.db import models
from django.db.models import Count
from django_gravatar.helpers import has_gravatar, get_gravatar_url
from sorl.thumbnail import get_thumbnail
from allauth.socialaccount.models import SocialAccount
from core.utils.file import generate_save_file_name
from core.utils.url import unique_slugify
from dss import settings
from spa.models._basemodel import _BaseModel
from templated_email import send_templated_mail
from sorl import thumbnail
from sorl.thumbnail.helpers import ThumbnailError
from core.utils.file import generate_save_file_name
from core.utils.url import unique_slugify, wrap_full
from dss import settings
from spa.models.notification import Notification
from spa.models._basemodel import _BaseModel
logger = logging.getLogger(__name__)
@@ -109,24 +111,6 @@ class UserProfile(_BaseModel):
except Exception, ex:
self.logger.error("Exception updating favourite: %s" % ex.message)
def add_follower(self, user):
self.followers.add(user)
user.following.add(self)
if not settings.DEBUG:
try:
send_templated_email([user.user], "notification/new_follower", {"profile": self.user})
except Exception, ex:
self.logger.error("Unable to send email for new follower")
self.logger.error("Exception: %s" % ex.message)
self.logger.error("Host: %s" % settings.EMAIL_HOST)
self.logger.error("Port: %s" % settings.EMAIL_PORT)
self.logger.error("Backend: %s" % settings.EMAIL_BACKEND)
def remove_follower(self, user):
if user in self.followers.all():
self.followers.remove(user)
def is_follower(self, user):
try:
return user.get_profile() in self.followers.all()

View File

@@ -1,10 +1,12 @@
from django.contrib.sessions.models import Session
from django.core import signals
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import post_save, pre_save
from django.db.models.signals import post_save, pre_save, m2m_changed
from django.dispatch import Signal, receiver
from django.contrib.auth.models import User
from core.utils.audio.mp3 import mp3_length
from core.utils.url import wrap_full
from spa.models import Notification
from spa.models.userprofile import UserProfile
from spa.models.mix import Mix
@@ -97,3 +99,29 @@ def session_pre_save(sender, **kwargs):
except ObjectDoesNotExist:
pass
@receiver(m2m_changed, sender=UserProfile.following.through, dispatch_uid='user_followers_changed')
def user_followers_changed(sender, **kwargs):
try:
if kwargs['action'] == 'post_add':
source_user = kwargs['instance']
if source_user:
for i in kwargs['pk_set']:
target_user = UserProfile.objects.get(pk=i)
if target_user:
notification = Notification()
notification.from_user = source_user
notification.to_user = target_user
notification.notification_text = "You have a new follower on Deep South Sounds"
notification.notification_html = "<a href='%s'>%s</a> followed you on <a href='http://deepsouthsounds.com'>Deep South Sounds</a>" % (
wrap_full(source_user.get_profile_url()),
source_user.get_nice_name()
)
notification.notification_url = wrap_full(source_user.get_absolute_url())
notification.verb = "followed"
notification.target = "Lick my balls"
notification.save()
except Exception, ex:
print "Error sending new follower: %s" % ex.message

View File

@@ -0,0 +1,404 @@
{% block content %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- If you delete this meta tag, Half Life 3 will never be released. -->
<meta name="viewport" content="width=device-width"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>{{ title }}</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
* {
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
}
img {
max-width: 100%;
}
.collapse {
margin: 0;
padding: 0;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
}
a {
color: #2BA6CB;
}
.btn {
text-decoration: none;
color: #FFF;
background-color: #666;
padding: 10px 16px;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
}
p.callout {
padding: 15px;
background-color: #ECF8FF;
margin-bottom: 15px;
}
.callout a {
font-weight: bold;
color: #2BA6CB;
}
table.social {
/* padding:15px; */
background-color: #ebebeb;
}
.social .soc-btn {
padding: 3px 7px;
font-size: 12px;
margin-bottom: 10px;
text-decoration: none;
color: #FFF;
font-weight: bold;
display: block;
text-align: center;
}
a.fb {
background-color: #3B5998 !important;
}
a.tw {
background-color: #1daced !important;
}
a.gp {
background-color: #DB4A39 !important;
}
a.ms {
background-color: #000 !important;
}
.sidebar .soc-btn {
display: block;
width: 100%;
}
table.head-wrap {
width: 100%;
}
.header.container table td.logo {
padding: 15px;
}
.header.container table td.label {
padding: 15px;
padding-left: 0px;
}
table.body-wrap {
width: 100%;
}
table.footer-wrap {
width: 100%;
clear: both !important;
}
.footer-wrap .container td.content p {
border-top: 1px solid rgb(215, 215, 215);
padding-top: 15px;
}
.footer-wrap .container td.content p {
font-size: 10px;
font-weight: bold;
}
h1, h2, h3, h4, h5, h6 {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
line-height: 1.1;
margin-bottom: 15px;
color: #000;
}
h1 small, h2 small, h3 small, h4 small, h5 small, h6 small {
font-size: 60%;
color: #6f6f6f;
line-height: 0;
text-transform: none;
}
h1 {
font-weight: 200;
font-size: 44px;
}
h2 {
font-weight: 200;
font-size: 37px;
}
h3 {
font-weight: 500;
font-size: 27px;
}
h4 {
font-weight: 500;
font-size: 23px;
}
h5 {
font-weight: 900;
font-size: 17px;
}
h6 {
font-weight: 900;
font-size: 14px;
text-transform: uppercase;
color: #444;
}
.collapse {
margin: 0 !important;
}
p, ul {
margin-bottom: 10px;
font-weight: normal;
font-size: 14px;
line-height: 1.6;
}
p.lead {
font-size: 17px;
}
p.last {
margin-bottom: 0px;
}
ul li {
margin-left: 5px;
list-style-position: inside;
}
ul.sidebar {
background: #ebebeb;
display: block;
list-style-type: none;
}
ul.sidebar li {
display: block;
margin: 0;
}
ul.sidebar li a {
text-decoration: none;
color: #666;
padding: 10px 16px;
/* font-weight:bold; */
margin-right: 10px;
/* text-align:center; */
cursor: pointer;
border-bottom: 1px solid #777777;
border-top: 1px solid #FFFFFF;
display: block;
margin: 0;
}
ul.sidebar li a.last {
border-bottom-width: 0px;
}
ul.sidebar li a h1, ul.sidebar li a h2, ul.sidebar li a h3, ul.sidebar li a h4, ul.sidebar li a h5, ul.sidebar li a h6, ul.sidebar li a p {
margin-bottom: 0 !important;
}
.container {
display: block !important;
max-width: 600px !important;
margin: 0 auto !important; /* makes it centered */
clear: both !important;
}
.content {
padding: 15px;
max-width: 600px;
margin: 0 auto;
display: block;
}
.content table {
width: 100%;
}
.column {
width: 300px;
float: left;
}
.column tr td {
padding: 15px;
}
.column-wrap {
padding: 0 !important;
margin: 0 auto;
max-width: 600px !important;
}
.column table {
width: 100%;
}
.social .column {
width: 280px;
min-width: 279px;
float: left;
}
.clear {
display: block;
clear: both;
}
@media only screen and (max-width: 600px) {
a[class="btn"] {
display: block !important;
margin-bottom: 10px !important;
background-image: none !important;
margin-right: 0 !important;
}
div[class="column"] {
width: auto !important;
float: none !important;
}
table.social div[class="column"] {
width: auto !important;
}
}
</style>
</head>
<body bgcolor="#FFFFFF">
<!-- HEADER -->
<table class="head-wrap" bgcolor="#999999">
<tr>
<td></td>
<td class="header container">
<div class="content">
<table bgcolor="#999999">
<tr>
<td><img src="http://t-static.deepsouthsounds.com/img/default-avatar-128.png"/></td>
<td align="right"><h6 class="collapse">New Notice From Deep South Sounds</h6></td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table>
<!-- /HEADER -->
<!-- BODY -->
<table class="body-wrap">
<tr>
<td></td>
<td class="container" bgcolor="#FFFFFF">
<div class="content">
<table>
<tr>
<td>
<h3>Hi, {{ user_name }}</h3>
<p class="lead">{{ html_title }}</p>
<p class="callout">
{{ notification_html |safe }}
</p><!-- /Callout Panel -->
<!-- social & contact -->
<table class="social" width="100%">
<tr>
<td>
<!-- column 1 -->
<table align="left" class="column">
<tr>
<td>
<h5 class="">Connect with Us:</h5>
<p class=""><a
href="https://www.facebook.com/pages/Deepsouthsoundscom/134649039964485"
class="soc-btn fb">Facebook</a> <a href="#"
class="soc-btn tw">Twitter</a>
<a href="https://twitter.com/DeepSouthSounds"
class="soc-btn gp">Google+</a></p>
</td>
</tr>
</table>
<table align="left" class="column">
<tr>
<td>
<h5 class="">Contact Info:</h5>
<p>
Email: <strong><a href="emailto:chatbot@deepsouthsounds.com">chatbot@deepsouthsounds.com</a></strong>
</p>
</td>
</tr>
</table>
<span class="clear"></span>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table>
<table class="footer-wrap">
<tr>
<td></td>
<td class="container">
<!-- content -->
<div class="content">
<table>
<tr>
<td align="center">
<p>
<a href="http://deepsouthsounds.com/me">
<unsubscribe>Manage Notifications</unsubscribe>
</a>
</p>
</td>
</tr>
</table>
</div>
<!-- /content -->
</td>
<td></td>
</tr>
</table>
<!-- /FOOTER -->
</body>
</html>
{% endblock %}

View File

@@ -1,5 +0,0 @@
<a href="http://{{ current_site}}/{{ profile.get_absolute_url }}">{{ profile.get_nice_name }}</a> has started following you on Deep South Sounds. <br />
This means they will get notifications about new mixes you upload. <br />
<br />
To see other notices change how you receive notifications, please go to http://{{ current_site }}/me

View File

@@ -1 +0,0 @@
{{ profile.get_nice_name }} has started following you.

View File

@@ -1 +0,0 @@
<a href="{{ profile.get_absolute_url }}">{{ profile.get_nice_name }}</a> has started following you.

View File

@@ -1,7 +0,0 @@
{% load i18n %}
{% url invitations as invitation_page %}
{% url profile_detail username=invitation.from_user.username as user_url %}
{% blocktrans with invitation.from_user as invitation_from_user %}
<a href="{{ user_url }}">{{ invitation_from_user }}</a> has started following you on Deep South Sounds
(see <a href="{{ invitation_page }}">followers</a>)
{% endblocktrans %}

View File

@@ -1 +0,0 @@
You have a new follower