diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b5db2af --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:2.7 +ENV PYTHONBUFFERED 1 + +RUN mkdir /code +RUN mkdir /srv/logs +RUN mkdir /files +RUN mkdir /files/static +RUN mkdir /files/media +RUN mkdir /files/cache +RUN mkdir /files/cache/mixes +RUN mkdir /files/cache/waveforms +RUN mkdir /files/tmp + +WORKDIR /code +ADD requirements.txt /code/ +RUN pip install -r requirements.txt +RUN apt-get update && apt-get install -y sox lame vim \ + libboost-program-options-dev libsox-fmt-mp3 postgresql-client + +ADD . /code/ + +RUN adduser --disabled-password --gecos '' djworker +RUN chown djworker /files -R +RUN chown djworker /srv/logs -R +RUN export PATH=$PATH:/mnt/bin/ \ No newline at end of file diff --git a/api/auth.py b/api/auth.py index 201ce3a..df3d5b7 100644 --- a/api/auth.py +++ b/api/auth.py @@ -1,5 +1,6 @@ from calendar import timegm import datetime +import logging from rest_framework import permissions from rest_framework.authtoken.serializers import AuthTokenSerializer from rest_framework.response import Response @@ -15,11 +16,9 @@ from rest_framework import parsers from social.apps.django_app.utils import psa from dss import settings - +logger = logging.getLogger('spa') @psa() def auth_by_token(request, backend): - token = request.data.get('access_token') - user = request.user user = request.backend.do_auth( access_token=request.data.get('access_token') ) @@ -27,7 +26,7 @@ def auth_by_token(request, backend): return user if user else None -class FacebookView(APIView): +class SocialLoginHandler(APIView): permission_classes = (permissions.AllowAny,) def post(self, request, format=None): @@ -38,9 +37,10 @@ class FacebookView(APIView): try: user = auth_by_token(request, backend) except Exception, e: + logger.exception(e) return Response({ 'status': 'Bad request', - 'message': 'Could not authenticate with the provided token' if settings.DEBUG else e.message + 'message': e.message }, status=status.HTTP_400_BAD_REQUEST) if user: @@ -57,7 +57,8 @@ class FacebookView(APIView): ) response_data = { - 'token': jwt_encode_handler(payload) + 'token': jwt_encode_handler(payload), + 'session': user.userprofile.get_session_id() } return Response(response_data) diff --git a/api/helpers.py b/api/helpers.py index 32b3e32..e3e50d5 100644 --- a/api/helpers.py +++ b/api/helpers.py @@ -22,9 +22,16 @@ class ChatHelper(ActivityHelper): # do some persistence stuff with the chat from core.realtime import chat - user = self.get_session(request) + #user = self.get_session(request) + u = request.user + if not u.is_anonymous(): + image = u.userprofile.get_sized_avatar_image(32, 32) + user = u.userprofile.get_nice_name() + else: + image = settings.DEFAULT_USER_IMAGE + user = settings.DEFAULT_USER_NAME - chat.post_chat(request.data['user'], request.data['message']) + chat.post_chat(request.data['user'], image, user, request.data['message']) return Response(request.data['message'], HTTP_201_CREATED) diff --git a/api/serializers.py b/api/serializers.py index e94b610..b51fc02 100755 --- a/api/serializers.py +++ b/api/serializers.py @@ -449,9 +449,11 @@ class ActivitySerializer(serializers.HyperlinkedModelSerializer): class NotificationSerializer(serializers.HyperlinkedModelSerializer): - from_user = UserProfileSerializer(many=False, required=False) - display_name = serializers.SerializerMethodField(method_name='get_display_name') - avatar_image = serializers.SerializerMethodField(method_name='get_avatar_image') + from_user = InlineUserProfileSerializer(source='get_from_user', read_only=True) + notification_url = serializers.ReadOnlyField() + verb = serializers.ReadOnlyField() + target = serializers.ReadOnlyField() + date = serializers.ReadOnlyField() class Meta: model = Notification @@ -459,10 +461,12 @@ class NotificationSerializer(serializers.HyperlinkedModelSerializer): 'id', 'notification_url', 'from_user', - 'display_name', - 'avatar_image', 'verb', 'target', + 'target_desc', + 'type', + 'date', + 'accepted_date', ) def get_display_name(self, obj): diff --git a/api/urls.py b/api/urls.py index 043c535..75701bf 100755 --- a/api/urls.py +++ b/api/urls.py @@ -1,21 +1,44 @@ from django.conf.urls import patterns, url, include +from rest_framework import permissions +from rest_framework.permissions import IsAuthenticated from rest_framework.routers import DefaultRouter +from rest_framework.views import APIView +from rest_framework_jwt.authentication import JSONWebTokenAuthentication from api import views, auth, helpers -from api.auth import FacebookView +from api.auth import SocialLoginHandler +from rest_framework.views import status +from rest_framework.response import Response +from core.realtime import activity router = DefaultRouter() # trailing_slash=True) router.register(r'user', views.UserProfileViewSet) router.register(r'mix', views.MixViewSet) - router.register(r'notification', views.NotificationViewSet) router.register(r'hitlist', views.HitlistViewSet) router.register(r'comments', views.CommentViewSet) router.register(r'activity', views.ActivityViewSet, base_name='activity') router.register(r'genre', views.GenreViewSet, base_name='genre') + +class DebugView(APIView): + permission_classes = (IsAuthenticated, ) + authentication_classes = (JSONWebTokenAuthentication, ) + + def post(self, request, format=None): + try: + activity.post_activity('user:message', request.user.userprofile.get_session_id(), 'Hello Sailor') + except Exception, ex: + print ex.message + + return Response({ + 'status': request.user.first_name, + 'message': 'Sailor' + }, status=status.HTTP_200_OK) + + urlpatterns = patterns( '', url(r'^', include(router.urls)), @@ -26,17 +49,18 @@ urlpatterns = patterns( url(r'_search/$', views.SearchResultsView.as_view()), url(r'^', include(router.urls)), - #url(r'^login/', auth.ObtainAuthToken.as_view()), - #url(r'^logout/', auth.ObtainLogout.as_view()), + url(r'^_login/', SocialLoginHandler.as_view()), + url(r'^token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token'), # url(r'^_tr/', RefreshToken.as_view()), url(r'^__u/checkslug', helpers.UserSlugCheckHelper.as_view()), url(r'^__u/', auth.ObtainUser.as_view()), - url(r'^_act/play', helpers.ActivityPlayHelper.as_view()), url(r'^_chat/', helpers.ChatHelper.as_view()), - url(r'^_login/', FacebookView.as_view()), + + + url(r'^__debug/', DebugView.as_view()), url('', include('social.apps.django_app.urls', namespace='social')), ) diff --git a/api/views.py b/api/views.py index 12948a5..01b8038 100755 --- a/api/views.py +++ b/api/views.py @@ -17,9 +17,8 @@ from rest_framework.status import HTTP_202_ACCEPTED, HTTP_401_UNAUTHORIZED, HTTP HTTP_200_OK, HTTP_204_NO_CONTENT from api import serializers -from core.utils.cdn import upload_to_azure from dss import settings -from core.tasks import create_waveform_task, archive_mix_task +from spa import tasks from spa.models.genre import Genre from spa.models.activity import ActivityPlay from spa.models.mix import Mix @@ -27,7 +26,7 @@ from spa.models.comment import Comment from spa.models.notification import Notification from spa.models.userprofile import UserProfile -logger = logging.getLogger(__name__) +logger = logging.getLogger('spa') class AnonymousWriteUserDelete(BasePermission): @@ -121,10 +120,10 @@ class AttachedImageUploadView(views.APIView): parser_classes = (FileUploadParser,) def post(self, request): - if request.FILES['file'] is None or request.data.get('data') is None: + if request.data['file'] is None or request.data.get('data') is None: return Response(status=HTTP_400_BAD_REQUEST) - file_obj = request.FILES['file'] + file_obj = request.data['file'] file_hash = request.data.get('data') try: mix = Mix.objects.get(uid=file_hash) @@ -134,6 +133,8 @@ class AttachedImageUploadView(views.APIView): return Response(HTTP_202_ACCEPTED) except ObjectDoesNotExist: return Response(status=HTTP_404_NOT_FOUND) + except Exception, ex: + logger.exception(ex) return Response(status=HTTP_401_UNAUTHORIZED) @@ -161,26 +162,43 @@ class PartialMixUploadView(views.APIView): # noinspection PyBroadException def post(self, request): try: + logger.info("Received post file") uid = request.META.get('HTTP_UPLOAD_HASH') - in_file = request.FILES['file'] if request.FILES else None + in_file = request.data['file'] if request.data else None file_name, extension = os.path.splitext(in_file.name) + logger.info("Constructing storage") file_storage = FileSystemStorage(location=os.path.join(settings.CACHE_ROOT, "mixes")) cache_file = file_storage.save("%s%s" % (uid, extension), ContentFile(in_file.read())) response = 'File creation in progress' + logger.info("Storage constructed") + try: + logger.debug("Received input file") + logger.debug("Storage is {0}".format(file_storage.base_location)) input_file = os.path.join(file_storage.base_location, cache_file) # Chain the waveform & archive tasks together # Probably not the best place for them but will do for now - # First argument to archive_mix_task is not specified as it is piped from create_waveform_task - (create_waveform_task.s(input_file, uid) | - archive_mix_task.s(filetype='mp3', uid=uid)).delay() + # First argument to upload_to_cdn_task is not specified as it is piped from create_waveform_task - except Exception: + logger.debug("Processing input_file: {0}".format(input_file)) + logger.debug("Connecting to broker: {0}".format(settings.BROKER_URL)) + + from celery import group, chain + ( + tasks.create_waveform_task.s(input_file, uid) | + tasks.upload_to_cdn_task.subtask(('mp3', uid, 'mixes'), immutable=True) | + tasks.upload_to_cdn_task.subtask(('png', uid, 'waveforms'), immutable=True) | + tasks.notify_subscriber.subtask((request.user.userprofile.get_session_id(), uid), immutable=True) + ).delay() + logger.debug("Waveform task started") + + except Exception, ex: + logger.exception(ex) response = \ - 'Unable to connect to waveform generation task, there may be a delay in getting your mix online' + 'Unable to connect to rabbitmq, there may be a delay in getting your mix online' file_dict = { 'response': response, @@ -209,12 +227,13 @@ class ActivityViewSet(viewsets.ModelViewSet): ret = ActivityPlay.objects.filter(mix__user=user).order_by("-id") - if len(ret) >0: - logger.debug("Activity returned: %s".format(ret[0].get_object_slug())) + if len(ret) > 0: + logger.debug("Activity returned: {0}".format(ret[0].get_object_slug())) return ret else: return [] + class DownloadItemView(views.APIView): def get(self, request, *args, **kwargs): try: @@ -234,7 +253,10 @@ class NotificationViewSet(viewsets.ModelViewSet): if not user.is_authenticated(): raise PermissionDenied("Not allowed") - return Notification.objects.filter(to_user=user).order_by('-date')[0:5] + return Notification.objects.filter(to_user=user).order_by('-date') + + def perform_update(self, serializer): + return super(NotificationViewSet, self).perform_update(serializer) class GenreViewSet(viewsets.ModelViewSet): diff --git a/bin/wav2png b/bin/wav2png new file mode 100755 index 0000000..3caced9 Binary files /dev/null and b/bin/wav2png differ diff --git a/core/realtime/activity.py b/core/realtime/activity.py index 1ed2ef0..3af0813 100755 --- a/core/realtime/activity.py +++ b/core/realtime/activity.py @@ -1,9 +1,10 @@ import redis import json +from dss import settings def post_activity(channel, session, message): - r = redis.StrictRedis(host='localhost', port=6379, db=0) + r = redis.StrictRedis(host=settings.REDIS_HOST, port=6379, db=0) response = r.publish(channel, json.dumps({'session': session, 'message': message})) print "Message sent: {0}".format(response) diff --git a/core/realtime/chat.py b/core/realtime/chat.py index e981a5e..fffefe1 100644 --- a/core/realtime/chat.py +++ b/core/realtime/chat.py @@ -1,8 +1,17 @@ import json +import datetime import redis +from dss import settings -def post_chat(session, message): - r = redis.StrictRedis(host='localhost', port=6379, db=0) - response = r.publish('chat', json.dumps({'session': session, 'message': message})) +def post_chat(session, image, user, message): + r = redis.StrictRedis(host=settings.REDIS_HOST, port=6379, db=0) + payload = json.dumps({ + 'session': session, + 'message': message, + 'user': user, + 'image': image, + 'date': datetime.datetime.now().isoformat() + }) + response = r.publish('chat', payload) print "Message sent: {0}".format(response) diff --git a/core/tasks.py b/core/tasks.py deleted file mode 100755 index 89a37e2..0000000 --- a/core/tasks.py +++ /dev/null @@ -1,53 +0,0 @@ -import shutil -from celery.task import task -import os -from core.utils.cdn import upload_to_azure -from spa.signals import waveform_generated_signal - -try: - from django.contrib.gis.geoip import GeoIP -except ImportError: - pass - -from core.utils.waveform import generate_waveform -from dss import settings - - -@task(time_limit=3600) -def create_waveform_task(in_file, uid): - out_file = os.path.join(settings.MEDIA_ROOT, 'waveforms/%s.png' % uid) - print "Creating waveform \n\tIn: %s\n\tOut: %s" % (in_file, out_file) - generate_waveform(in_file, out_file) - if os.path.isfile(out_file): - print "Waveform generated successfully" - out_file, extension = os.path.splitext(in_file) - new_file = os.path.join(settings.MEDIA_ROOT, "mixes", "%s%s" % (uid, extension)) - print "Moving cache audio clip from %s to %s" % (in_file, new_file) - shutil.move(in_file, new_file) - print "Uid: %s" % uid - waveform_generated_signal.send(sender=None, uid=uid) - return new_file - else: - print "Outfile is missing" - - -@task(time_limit=3600) -def archive_mix_task(in_file, filetype, uid): - print "Sending {0} to azure".format(uid) - try: - upload_to_azure(in_file, filetype, uid) - except Exception, ex: - print "Unable to upload: %s".format(ex.message) - -@task -def update_geo_info_task(ip_address, profile_id): - try: - ip = '188.141.70.110' if ip_address == '127.0.0.1' else ip_address - if ip: - g = GeoIP() - city = g.city(ip) - country = g.country(ip) - print "Updated user location" - except Exception, e: - print e.message - pass diff --git a/core/utils/cdn.py b/core/utils/cdn.py index 09f5a80..5e51b1b 100755 --- a/core/utils/cdn.py +++ b/core/utils/cdn.py @@ -1,41 +1,43 @@ import os + from azure import WindowsAzureMissingResourceError from azure.storage import BlobService -from core.utils.url import url_path_join -from dss import settings -from dss.storagesettings import AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY, AZURE_CONTAINER from libcloud.storage.types import Provider from libcloud.storage.providers import get_driver +from dss import settings +from dss.storagesettings import AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY, AZURE_CONTAINER -def upload_to_azure(in_file, filetype, uid): + +def upload_file_to_azure(in_file, file_name, container_name=settings.AZURE_CONTAINER): if os.path.isfile(in_file): print "Uploading file for: %s" % in_file - file_name = "%s.%s" % (uid, filetype) - cls = get_driver(Provider.AZURE_BLOBS) - driver = cls(settings.AZURE_ACCOUNT_NAME, settings.AZURE_ACCOUNT_KEY) - container = driver.get_container(container_name=settings.AZURE_CONTAINER) - with open(in_file, 'rb') as iterator: - obj = driver.upload_object_via_stream( - iterator=iterator, - container=container, - object_name=file_name - ) - print "Uploaded" - return obj + return upload_stream_to_azure(iterator, file_name, container_name=container_name) else: print "infile not found" return None -def set_azure_details(blob_name, download_name): +def upload_stream_to_azure(iterator, file_name, container_name=settings.AZURE_CONTAINER): + cls = get_driver(Provider.AZURE_BLOBS) + driver = cls(settings.AZURE_ACCOUNT_NAME, settings.AZURE_ACCOUNT_KEY) + container = driver.get_container(container_name) + obj = driver.upload_object_via_stream( + iterator=iterator, + container=container, + object_name=file_name + ) + return obj + + +def set_azure_details(blob_name, download_name, container_name=AZURE_CONTAINER): try: blob_service = BlobService(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY) - blob = blob_service.get_blob(AZURE_CONTAINER, blob_name) + blob = blob_service.get_blob(container_name, blob_name) if blob: blob_service.set_blob_properties( - AZURE_CONTAINER, + container_name, blob_name, x_ms_blob_content_type='application/octet-stream', x_ms_blob_content_disposition='attachment;filename="{0}"'.format(download_name) @@ -47,3 +49,29 @@ def set_azure_details(blob_name, download_name): print "No blob found for: %s" % download_name except Exception, ex: print "Error processing blob %s: %s" % (download_name, ex.message) + + +def file_exists(url): + import httplib + from urlparse import urlparse + p = urlparse(url) + c = httplib.HTTPConnection(p.netloc) + c.request("HEAD", p.path) + + r = c.getresponse() + return r.status == 200 + + +def enumerate_objects(container): + blob_service = BlobService(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY) + blobs = blob_service.list_blobs(container) + items = [] + for blob in blobs: + items.append(blob.name) + + return items + + +def delete_object(container, name): + blob_service = BlobService(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY) + blob_service.delete_blob(container, name) \ No newline at end of file diff --git a/core/utils/url.py b/core/utils/url.py index 3583a9f..4c59ab5 100755 --- a/core/utils/url.py +++ b/core/utils/url.py @@ -5,19 +5,21 @@ from django.template.defaultfilters import slugify __author__ = 'fergalm' + def url_path_join(*parts): """Join and normalize url path parts with a slash.""" schemes, netlocs, paths, queries, fragments = zip(*(urlparse.urlsplit(part) for part in parts)) # Use the first value for everything but path. Join the path on '/' - scheme = next((x for x in schemes if x), '') - netloc = next((x for x in netlocs if x), '') - path = '/'.join(x.strip('/') for x in paths if x) - query = next((x for x in queries if x), '') + scheme = next((x for x in schemes if x), '') + netloc = next((x for x in netlocs if x), '') + path = '/'.join(x.strip('/') for x in paths if x) + query = next((x for x in queries if x), '') fragment = next((x for x in fragments if x), '') return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + def urlclean(url): - #remove double slashes + # remove double slashes ret = urlparse.urljoin(url, urlparse.urlparse(url).path.replace('//', '/')) return ret @@ -90,11 +92,13 @@ def _slug_strip(value, separator='-'): value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', 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 \ No newline at end of file + return url diff --git a/dss/__init__.py b/dss/__init__.py index e69de29..d1956f9 100755 --- a/dss/__init__.py +++ b/dss/__init__.py @@ -0,0 +1,2 @@ +from __future__ import absolute_import +from .celeryconf import app as celery_app diff --git a/dss/celeryconf.py b/dss/celeryconf.py new file mode 100644 index 0000000..198334e --- /dev/null +++ b/dss/celeryconf.py @@ -0,0 +1,19 @@ +from __future__ import absolute_import + +import os +import logging + +from celery import Celery + +logger = logging.getLogger('dss') + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dss.settings') + +from django.conf import settings +app = Celery('dss') + +# Using a string here means the worker will not have to +# pickle the object when using Windows. +app.config_from_object('django.conf:settings') +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) diff --git a/dss/logsettings.py b/dss/logsettings.py index e7d88c2..a98888a 100755 --- a/dss/logsettings.py +++ b/dss/logsettings.py @@ -1,4 +1,5 @@ import os +import sys from dss import localsettings if os.name == 'posix': @@ -20,11 +21,14 @@ LOGGING = { }, 'handlers': { 'file': { - 'level': 'DEBUG', + 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': LOG_FILE, 'formatter': 'verbose' - }, + }, 'console': { + 'class': 'logging.StreamHandler', + 'stream': sys.stdout, + } }, 'loggers': { 'django': { @@ -33,7 +37,7 @@ LOGGING = { 'level': 'DEBUG', }, 'spa': { - 'handlers': ['file'], + 'handlers': ['file', 'console'], 'level': 'DEBUG', }, } diff --git a/dss/settings.py b/dss/settings.py index 605277d..3caa6ea 100755 --- a/dss/settings.py +++ b/dss/settings.py @@ -1,8 +1,8 @@ # e Django settings for dss project. import os import mimetypes +from datetime import timedelta from django.core.urlresolvers import reverse_lazy -import djcelery from django.conf import global_settings from utils import here @@ -29,7 +29,6 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': DATABASE_NAME, - 'ADMINUSER': 'postgres', 'USER': DATABASE_USER, 'PASSWORD': DATABASE_PASSWORD, 'HOST': DATABASE_HOST, @@ -38,7 +37,6 @@ DATABASES = { import sys if 'test' in sys.argv or 'test_coverage' in sys.argv: - print "Testing" DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3' ROOT_URLCONF = 'dss.urls' @@ -70,19 +68,13 @@ TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( 'django.core.context_processors.media', 'django.core.context_processors.static', 'django.contrib.auth.context_processors.auth', - - - # TODO: remove.. - # `allauth` specific context processors - "allauth.account.context_processors.account", - "allauth.socialaccount.context_processors.socialaccount", ) MIDDLEWARE_CLASSES = ( 'django.middleware.gzip.GZipMiddleware', 'django.middleware.common.CommonMiddleware', - 'user_sessions.middleware.SessionMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', @@ -104,7 +96,7 @@ INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', - 'user_sessions', + 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', @@ -112,17 +104,16 @@ INSTALLED_APPS = ( #'django_facebook', 'django_extensions', 'django_gravatar', - 'djcelery', 'corsheaders', 'sorl.thumbnail', + 'djcelery', 'spa', - 'tinymce', 'gunicorn', 'spa.signals', 'core', #'schedule', 'django_user_agents', - + 'storages', 'social.apps.django_app.default', # TODO: remove @@ -138,7 +129,6 @@ INSTALLED_APPS = ( 'djrill', 'rest_framework', 'rest_framework.authtoken', - 'rest_framework_swagger', ) # where to redirect users to after logging in @@ -147,7 +137,6 @@ LOGOUT_URL = reverse_lazy('home') FACEBOOK_APP_ID = '154504534677009' -djcelery.setup_loader() AVATAR_STORAGE_DIR = MEDIA_ROOT + '/avatars/' ACCOUNT_LOGOUT_REDIRECT_URL = '/' @@ -160,7 +149,7 @@ TASTYPIE_ALLOW_MISSING_SLASH = True SENDFILE_ROOT = os.path.join(MEDIA_ROOT, 'mixes') SENDFILE_URL = '/media/mixes' -SESSION_ENGINE = 'user_sessions.backends.db' +#SESSION_ENGINE = 'user_sessions.backends.db' mimetypes.add_type("text/xml", ".plist", False) @@ -216,4 +205,12 @@ DEFAULT_USER_NAME = 'Anonymouse' DEFAULT_USER_TITLE = 'Just another DSS lover' SITE_NAME = 'Deep South Sounds' -THUMBNAIL_PREFIX = 'cache/_tn/' +THUMBNAIL_PREFIX = '_tn/' + +# THUMBNAIL_STORAGE = 'storages.backends.azure_storage.AzureStorage' + +JWT_AUTH = { + 'JWT_EXPIRATION_DELTA': timedelta(seconds=900), + 'JWT_ALLOW_REFRESH': True, + 'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=30), +} diff --git a/dss/urls.py b/dss/urls.py index 61c8f58..78fdc2f 100755 --- a/dss/urls.py +++ b/dss/urls.py @@ -9,13 +9,13 @@ admin.autodiscover() urlpatterns = patterns( '', url(r'^admin/', include(admin.site.urls)), - url(r'^api/docs/', include('rest_framework_swagger.urls')), - url(r'^api/v2/', include('api.urls')), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), (r'^grappelli/', include('grappelli.urls')), (r'^social/', include('spa.social.urls')), + (r'^arges/', include('spa.social.urls')), + url(r'', include('user_sessions.urls', 'user_sessions')), + url(r'^', include('api.urls')), ) -handler500 = 'spa.views.debug_500' if settings.DEBUG: from django.views.static import serve diff --git a/dss/wsgi.py b/dss/wsgi.py index 8765705..c998099 100755 --- a/dss/wsgi.py +++ b/dss/wsgi.py @@ -1,28 +1,6 @@ -""" -WSGI config for dss project. - -This module contains the WSGI application used by Django's development server -and any production WSGI deployments. It should expose a module-level variable -named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover -this application via the ``WSGI_APPLICATION`` setting. - -Usually you will have the standard Django WSGI application here, but it also -might make sense to replace the whole Django WSGI application with a custom one -that later delegates to the Django one. For example, you could introduce WSGI -middleware here, or combine a Django application with an application of another -framework. - -""" import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dss.settings") -# This application object is used by any WSGI server configured to use this -# file. This includes Django's development server, if the WSGI_APPLICATION -# setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() - -# Apply WSGI middleware here. -# from helloworld.wsgi import HelloWorldApplication -# application = HelloWorldApplication(application) diff --git a/requirements.txt b/requirements.txt index c8d12d2..68682e5 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django>=1.6,<1.7 +Django==1.6.11 django-extensions django-sendfile Werkzeug @@ -9,14 +9,15 @@ django-dirtyfields django-storages django-user-sessions django-cors-headers -django-rest-swagger +six==1.6.0 django-filter -django-grappelli +django-grappelli==2.5.7 django-model_utils django-dbbackup django-user-agents south redis +django-celery sorl-thumbnail @@ -26,15 +27,15 @@ git+git://github.com/tschellenbach/Django-facebook.git#django-facebook git+git://github.com/llazzaro/django-scheduler.git#django-scheduler git+git://github.com/omab/python-social-auth.git#egg=python-social-auth django-allauth -django-tinymce apache-libcloud mandrill djrill -djangorestframework -djangorestframework-jwt +celery + +djangorestframework==3.1.3 +djangorestframework-jwt==1.6.0 drf-nested-routers -django-celery pillow django-gravatar2 @@ -43,4 +44,4 @@ mutagen django-enumfield ipython -ipdb \ No newline at end of file +ipdb diff --git a/run_celery.sh b/run_celery.sh new file mode 100755 index 0000000..fe41a9e --- /dev/null +++ b/run_celery.sh @@ -0,0 +1,2 @@ +#!/bin/sh +su -m djworker -c "python manage.py celeryd" \ No newline at end of file diff --git a/run_web.sh b/run_web.sh new file mode 100755 index 0000000..45e0d8f --- /dev/null +++ b/run_web.sh @@ -0,0 +1,2 @@ +#!/bin/sh +su -m djworker -c "python manage.py runserver_plus 0.0.0.0:8000" diff --git a/spa/api/v1/UserResource.py b/spa/api/v1/UserResource.py index 16757f2..3e8f7ea 100755 --- a/spa/api/v1/UserResource.py +++ b/spa/api/v1/UserResource.py @@ -1,21 +1,19 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned -from django.db.models import Count, Q, F +from django.db.models import Count, Q +from django.conf.urls import url + from tastypie import fields from tastypie.authentication import Authentication from tastypie.authorization import Authorization -from django.conf.urls import url from tastypie.constants import ALL, ALL_WITH_RELATIONS from tastypie.http import HttpGone, HttpMultipleChoices from tastypie.utils import trailing_slash from tastypie_msgpack import Serializer - from dss import settings from spa.api.v1.BaseResource import BaseResource from spa.api.v1.PlaylistResource import PlaylistResource -from spa.models.basemodel import BaseModel from spa.models.userprofile import UserProfile from spa.models.mix import Mix -from core.tasks import update_geo_info_task class UserResource(BaseResource): diff --git a/spa/management/commands/azure_util.py b/spa/management/commands/azure_util.py index d86f2b2..c310264 100755 --- a/spa/management/commands/azure_util.py +++ b/spa/management/commands/azure_util.py @@ -1,9 +1,33 @@ -from django.core.management.base import NoArgsCommand, BaseCommand +import os +from django.core.management.base import BaseCommand -from core.utils.cdn import upload_to_azure +from core.utils import cdn from spa.models import Mix +def _update_azure_headers(): + ms = Mix.objects.all() + for m in ms: + cdn.set_azure_details('{0}.mp3'.format(m.uid), 'Deep South Sounds - {0}'.format(m.title), 'mixes') + +def _check_missing_mixes(): + ms = Mix.objects.all() + found = 0 + for m in ms: + url = m.get_download_url() + if not cdn.file_exists(url): + file = '/mnt/dev/working/Dropbox/Development/deepsouthsounds.com/media/mixes/{0}.mp3'.format(m.uid) + if os.path.isfile(file): + print '* {0}'.format(file) + #cdn.upload_file_to_azure(file, '{0}.mp3'.format(m.uid), 'mixes') + found += 1 + else: + found += 1 + print '({0}){1} - {2}'.format(found, m.slug, m.uid) + + print '{0} of {1} missing'.format(found, Mix.objects.count()) + + class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( @@ -18,7 +42,7 @@ class Command(BaseCommand): mixes = Mix.objects.filter(archive_updated=False) for mix in mixes: blob_name, download_name = mix.get_cdn_details() - upload_to_azure(blob_name, "mp3", download_name) + cdn.upload_file_to_azure(blob_name, "mp3", download_name) mix.archive_updated = True mix.save() @@ -26,4 +50,11 @@ class Command(BaseCommand): print "Fatal error, bailing. {0}".format(ex.message) def handle(self, *args, **options): - pass + if len(args) == 0: + print "Commands are \n\t_check_missing_mixes" + elif args[0] == 'check_missing_mix': + _check_missing_mixes() + elif args[0] == 'update_azure_headers': + _update_azure_headers() + else: + print "Commands are \n\tcheck_missing_mix" diff --git a/spa/management/commands/create_notifications.py b/spa/management/commands/create_notifications.py new file mode 100755 index 0000000..9584e90 --- /dev/null +++ b/spa/management/commands/create_notifications.py @@ -0,0 +1,15 @@ +from django.core.management.base import NoArgsCommand +from spa import models + + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + try: + models.Notification.objects.all().delete() + act = models.Activity.objects.all().order_by('-id').select_subclasses() + for a in act: + print "Creating for: {0}".format(a) + a.create_notification(accept=True) + + except Exception, ex: + print "Debug exception: %s" % ex.message \ No newline at end of file diff --git a/spa/management/commands/get_avatars.py b/spa/management/commands/get_avatars.py index 2ff750d..66ed4e9 100755 --- a/spa/management/commands/get_avatars.py +++ b/spa/management/commands/get_avatars.py @@ -1,21 +1,42 @@ +import urllib2 + from allauth.socialaccount.models import SocialAccount -from django.core.files.base import ContentFile +from azure.storage import BlobService +from django.core.files.base import File +from django.core.files.temp import NamedTemporaryFile from django.core.management.base import NoArgsCommand from requests import request, ConnectionError + +from dss import storagesettings from spa.models.userprofile import UserProfile def save_image(profile, url): + + img = NamedTemporaryFile(delete=True) + img.write(urllib2.urlopen(url).read()) + + img.flush() + profile.avatar_image.save(str(profile.id), File(img)) + + +def save_image_to_azure(profile, url): try: response = request('GET', url) response.raise_for_status() except ConnectionError: pass else: - profile.avatar_image.save(u'', - ContentFile(response.content), - save=False) - profile.save() + service = BlobService( + account_name=storagesettings.AZURE_ACCOUNT_NAME, + account_key=storagesettings.AZURE_ACCOUNT_KEY) + + service.put_block_blob_from_bytes( + 'avatars', + profile.id, + response.content, + x_ms_blob_content_type=response.headers['content-type'] + ) class Command(NoArgsCommand): @@ -31,6 +52,7 @@ class Command(NoArgsCommand): if provider_account: avatar_url = provider_account.get_avatar_url() save_image(user, avatar_url) + user.save() except Exception, ex: print ex.message else: diff --git a/spa/management/commands/tidy_cdn.py b/spa/management/commands/tidy_cdn.py new file mode 100755 index 0000000..0e54b5f --- /dev/null +++ b/spa/management/commands/tidy_cdn.py @@ -0,0 +1,22 @@ +import os +from django.core.management.base import NoArgsCommand +from core.utils import cdn +from spa.models import Mix + + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + try: + print 'Enumerating items' + items = cdn.enumerate_objects('mixes') + for item in items: + # Check if we have a corresponding mix + uid, type = os.path.splitext(item) + try: + Mix.objects.get(uid=uid) + except Mix.DoesNotExist: + # no mix found - delete the blob + cdn.delete_object('mixes', item) + print "Deleting blob: {0}".format(uid) + except Exception, ex: + print "Debug exception: %s" % ex.message diff --git a/spa/management/commands/waveforms.py b/spa/management/commands/waveforms.py index 8824b7c..3d90b59 100755 --- a/spa/management/commands/waveforms.py +++ b/spa/management/commands/waveforms.py @@ -1,10 +1,11 @@ from optparse import make_option import os -from django.core.management.base import NoArgsCommand, BaseCommand -from spa.management.commands import helpers +from django.core.management.base import BaseCommand + +from spa.management.commands import helpers from spa.models.mix import Mix -from core.tasks import create_waveform_task +from spa.tasks import create_waveform_task class Command(BaseCommand): @@ -40,7 +41,7 @@ class Command(BaseCommand): return processed_file except Exception, ex: - print "Error generating waveform: %s" % ex.message + print "Error generating waveform: {0}".format(ex.message) return "" diff --git a/spa/migrations/0071_auto__del_field_notification_notification_text__del_field_notification.py b/spa/migrations/0071_auto__del_field_notification_notification_text__del_field_notification.py new file mode 100644 index 0000000..7ecbaa3 --- /dev/null +++ b/spa/migrations/0071_auto__del_field_notification_notification_text__del_field_notification.py @@ -0,0 +1,305 @@ +# -*- 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): + # Deleting field 'Notification.notification_text' + db.delete_column(u'spa_notification', 'notification_text') + + # Deleting field 'Notification.notification_html' + db.delete_column(u'spa_notification', 'notification_html') + + # Deleting field 'Notification.notification_url' + db.delete_column(u'spa_notification', 'notification_url') + + # Adding field 'Notification.type' + db.add_column(u'spa_notification', 'type', + self.gf('django.db.models.fields.CharField')(max_length=200, null=True), + keep_default=False) + + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Notification.notification_text' + raise RuntimeError("Cannot reverse this migration. 'Notification.notification_text' and its values cannot be restored.") + + # The following code is provided here to aid in writing a correct migration # Adding field 'Notification.notification_text' + db.add_column(u'spa_notification', 'notification_text', + self.gf('django.db.models.fields.CharField')(max_length=1024), + keep_default=False) + + + # User chose to not deal with backwards NULL issues for 'Notification.notification_html' + raise RuntimeError("Cannot reverse this migration. 'Notification.notification_html' and its values cannot be restored.") + + # The following code is provided here to aid in writing a correct migration # Adding field 'Notification.notification_html' + db.add_column(u'spa_notification', 'notification_html', + self.gf('django.db.models.fields.CharField')(max_length=1024), + keep_default=False) + + # Adding field 'Notification.notification_url' + db.add_column(u'spa_notification', 'notification_url', + self.gf('django.db.models.fields.URLField')(max_length=200, null=True), + keep_default=False) + + # Deleting field 'Notification.type' + db.delete_column(u'spa_notification', 'type') + + + 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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}) + }, + 'spa.activity': { + 'Meta': {'object_name': 'Activity'}, + 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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', [], {}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + '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_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'liked_comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['spa.UserProfile']"}), + 'mix': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['spa.Mix']"}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + '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.genre': { + 'Meta': {'object_name': 'Genre'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}) + }, + 'spa.mix': { + 'Meta': {'ordering': "('-id',)", 'object_name': 'Mix'}, + 'archive_path': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'archive_updated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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'}), + 'mp3tags_updated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', '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', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mixes'", 'to': "orm['spa.UserProfile']"}), + 'waveform_generated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'waveform_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'spa.notification': { + 'Meta': {'ordering': "('-id',)", 'object_name': 'Notification'}, + 'accepted_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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']"}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'verb': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}) + }, + 'spa.playlist': { + 'Meta': {'object_name': 'Playlist'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mixes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['spa.Mix']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'playlists'", 'to': "orm['spa.UserProfile']"}) + }, + 'spa.purchaselink': { + 'Meta': {'object_name': 'PurchaseLink'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'release_artist': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'release_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2015, 8, 12, 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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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']"}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + '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_facebook': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'activity_sharing_networks': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'activity_sharing_twitter': ('django.db.models.fields.BigIntegerField', [], {'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'}), + 'email_notifications': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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'] \ No newline at end of file diff --git a/spa/migrations/0072_auto__add_field_notification_target_desc.py b/spa/migrations/0072_auto__add_field_notification_target_desc.py new file mode 100644 index 0000000..6dbd40d --- /dev/null +++ b/spa/migrations/0072_auto__add_field_notification_target_desc.py @@ -0,0 +1,274 @@ +# -*- 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.target_desc' + db.add_column(u'spa_notification', 'target_desc', + self.gf('django.db.models.fields.CharField')(max_length=200, null=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Notification.target_desc' + db.delete_column(u'spa_notification', 'target_desc') + + + 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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}) + }, + 'spa.activity': { + 'Meta': {'object_name': 'Activity'}, + 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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', [], {}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + '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_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'liked_comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['spa.UserProfile']"}), + 'mix': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['spa.Mix']"}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + '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.genre': { + 'Meta': {'object_name': 'Genre'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}) + }, + 'spa.mix': { + 'Meta': {'ordering': "('-id',)", 'object_name': 'Mix'}, + 'archive_path': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'archive_updated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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'}), + 'mp3tags_updated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', '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', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mixes'", 'to': "orm['spa.UserProfile']"}), + 'waveform_generated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'waveform_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'spa.notification': { + 'Meta': {'ordering': "('-id',)", 'object_name': 'Notification'}, + 'accepted_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'target': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'target_desc': ('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']"}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'verb': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}) + }, + 'spa.playlist': { + 'Meta': {'object_name': 'Playlist'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mixes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['spa.Mix']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'playlists'", 'to': "orm['spa.UserProfile']"}) + }, + 'spa.purchaselink': { + 'Meta': {'object_name': 'PurchaseLink'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'release_artist': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'release_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2015, 8, 12, 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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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']"}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + '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_facebook': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'activity_sharing_networks': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'activity_sharing_twitter': ('django.db.models.fields.BigIntegerField', [], {'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'}), + 'email_notifications': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': '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'}), + 'object_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'object_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 8, 12, 0, 0)', 'auto_now': 'True', 'db_index': 'True', 'blank': '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'] \ No newline at end of file diff --git a/spa/models/activity.py b/spa/models/activity.py index 0790fe2..c82e607 100755 --- a/spa/models/activity.py +++ b/spa/models/activity.py @@ -1,11 +1,9 @@ import abc -from django.contrib.auth.models import AnonymousUser, User +from datetime import datetime from allauth.socialaccount.models import SocialToken -from datetime import datetime from django.db import models from model_utils.managers import InheritanceManager - from open_facebook import OpenFacebook from core.utils.url import wrap_full @@ -62,27 +60,20 @@ class Activity(BaseModel): print ex.message pass - def create_notification(self): + def create_notification(self, accept=False): try: notification = Notification() notification.from_user = self.user notification.to_user = self.get_target_user() - notification.notification_text = "%s %s %s" % ( - self.user.get_nice_name() if self.user is not None else "Anonymouse", - self.get_verb_past(), - self.get_object_name_for_notification()) - notification.notification_html = "%s %s %s" % ( - wrap_full(self.user.get_profile_url() if self.user is not None else ""), - self.user.get_nice_name() if self.user is not None else "Anonymouse", - self.get_verb_past(), - wrap_full(self.get_object_url()), - self.get_object_name_for_notification() - ) - - notification.notification_url = self.get_object_url() notification.verb = self.get_verb_past() - notification.target = self.get_object_name() + notification.type = self.get_object_type() + notification.target = self.get_object_slug() + notification.target_desc = self.get_object_name() + + if accept: + notification.accepted_date = datetime.now() + notification.save() except Exception, ex: print "Error creating activity notification: %s" % ex.message diff --git a/spa/models/basemodel.py b/spa/models/basemodel.py index 7f15512..779b744 100755 --- a/spa/models/basemodel.py +++ b/spa/models/basemodel.py @@ -10,7 +10,7 @@ from dss import localsettings, settings class BaseModel(models.Model): - logger = logging.getLogger(__name__) + logger = logging.getLogger('dss') object_created = models.DateTimeField(auto_now_add=True, default=datetime.now()) object_updated = models.DateTimeField(auto_now=True, default=datetime.now(), db_index=True) @@ -33,11 +33,8 @@ class BaseModel(models.Model): def get_image_url(self, image, default): try: if os.path.isfile(image.path): - images_root = localsettings.IMAGE_URL if hasattr(localsettings, - 'IMAGE_URL') else "%s" % settings.MEDIA_URL - ret = "%s/%s/%s" % (settings.STATIC_URL, images_root, image) + ret = "{0}/{1}".format(settings.MEDIA_URL, image) return url.urlclean(ret) - except Exception, ex: pass diff --git a/spa/models/fields.py b/spa/models/fields.py index f44c3b2..27896e6 100755 --- a/spa/models/fields.py +++ b/spa/models/fields.py @@ -102,4 +102,6 @@ try: from south.modelsinspector import add_introspection_rules add_introspection_rules([], ['^spa\.models.fields\.MultiSelectField']) except ImportError: - pass \ No newline at end of file + pass + + diff --git a/spa/models/mix.py b/spa/models/mix.py index 1ed76d6..f5a6201 100755 --- a/spa/models/mix.py +++ b/spa/models/mix.py @@ -1,8 +1,7 @@ -import os import rfc822 -import urlparse -from django.utils.encoding import smart_str +import os +from django.utils.encoding import smart_str from sorl.thumbnail import get_thumbnail from django.contrib.sites.models import Site from django.db import models @@ -19,8 +18,7 @@ from dss import settings, localsettings from spa.models.userprofile import UserProfile from spa.models.basemodel import BaseModel from core.utils.file import generate_save_file_name -from PIL import Image -import glob +from core.utils import cdn class Engine(Engine): @@ -104,7 +102,7 @@ class Mix(BaseModel): self.clean_image('mix_image', Mix) # Check for the unlikely event that the waveform has been generated - if os.path.isfile(self.get_waveform_path()): + if cdn.file_exists('{0}{1}.png'.format(localsettings.WAVEFORM_URL, self.uid)): self.waveform_generated = True try: self.duration = mp3_length(self.get_absolute_path()) @@ -170,7 +168,7 @@ class Mix(BaseModel): try: if self.mix_image.name and self.mix_image.storage.exists(self.mix_image.name): ret = get_thumbnail(self.mix_image, size, crop='center') - return "%s/%s" % (settings.MEDIA_URL, ret.name) + return url.urlclean("%s/%s" % (settings.MEDIA_URL, ret.name)) else: return self.user.get_sized_avatar_image(170, 170) except Exception, ex: diff --git a/spa/models/notification.py b/spa/models/notification.py index cb7c71d..2968c71 100755 --- a/spa/models/notification.py +++ b/spa/models/notification.py @@ -13,12 +13,10 @@ class Notification(BaseModel): from_user = models.ForeignKey('spa.UserProfile', related_name='notifications', null=True, blank=True) date = models.DateTimeField(auto_now_add=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) + type = models.CharField(max_length=200, null=True) target = models.CharField(max_length=200, null=True) + target_desc = models.CharField(max_length=200, null=True) accepted_date = models.DateTimeField(null=True) @@ -29,8 +27,7 @@ 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): + def __save(self, force_insert=False, force_update=False, using=None, update_fields=None): if self._activity.should_send_email(): self.send_notification_email() @@ -81,3 +78,6 @@ class Notification(BaseModel): except mandrill.Error, e: # Mandrill errors are thrown as exceptions print 'A mandrill error occurred: %s - %s' % (e.__class__, e) + + def get_from_user(self): + return UserProfile.get_user(self.from_user) diff --git a/spa/models/session.py b/spa/models/session.py new file mode 100644 index 0000000..2d78ba5 --- /dev/null +++ b/spa/models/session.py @@ -0,0 +1,7 @@ +from django.db import models +from spa.models import BaseModel, UserProfile + + +class Session(BaseModel): + jwt_token = models.CharField(max_length=2048) + user = models.ForeignKey(UserProfile) diff --git a/spa/models/userprofile.py b/spa/models/userprofile.py index f3f2aa5..79b9d14 100755 --- a/spa/models/userprofile.py +++ b/spa/models/userprofile.py @@ -139,6 +139,9 @@ class UserProfile(BaseModel): except Exception, e: self.logger.error("Unable to create profile slug: %s", e.message) + def get_session_id(self): + return str(self.id) + def toggle_favourite(self, mix, value): try: if value: diff --git a/spa/signals.py b/spa/signals.py index 5c65bba..1390a55 100755 --- a/spa/signals.py +++ b/spa/signals.py @@ -4,6 +4,7 @@ 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.realtime import activity from core.utils.audio.mp3 import mp3_length from spa.models.activity import ActivityFollow @@ -18,11 +19,12 @@ def _waveform_generated_callback(sender, **kwargs): print "Updating model with waveform" try: uid = kwargs['uid'] + path = kwargs['path'] if uid is not None: mix = Mix.objects.get(uid=uid) if mix is not None: mix.waveform_generated = True - mix.duration = mp3_length(mix.get_absolute_path()) + mix.duration = mp3_length(path) mix.save(update_fields=["waveform_generated", "duration"]) except ObjectDoesNotExist: diff --git a/spa/tasks.py b/spa/tasks.py new file mode 100755 index 0000000..10dbd05 --- /dev/null +++ b/spa/tasks.py @@ -0,0 +1,62 @@ +from celery.task import task +import os +import logging +from core.realtime import activity + +from core.utils import cdn +from spa.signals import waveform_generated_signal + +try: + from django.contrib.gis.geoip import GeoIP +except ImportError: + pass + +from core.utils.waveform import generate_waveform +from dss import settings + +logger = logging.getLogger('dss') + + +@task(time_limit=3600) +def create_waveform_task(in_file, uid): + out_file = os.path.join(settings.CACHE_ROOT, 'waveforms/%s.png' % uid) + logger.info("Creating waveform \n\tIn: %s\n\tOut: %s" % (in_file, out_file)) + generate_waveform(in_file, out_file) + if os.path.isfile(out_file): + logger.info("Waveform generated successfully") + waveform_generated_signal.send(sender=None, uid=uid, path=in_file) + return out_file + else: + logger.error("Outfile is missing") + + +@task(timse_limit=3600) +def upload_to_cdn_task(filetype, uid, container_name): + source_file = os.path.join(settings.CACHE_ROOT, '{0}/{1}.{2}'.format(container_name, uid, filetype)) + logger.info("Sending {0} to azure".format(uid)) + try: + file_name = "{0}.{1}".format(uid, filetype) + cdn.upload_file_to_azure(source_file, file_name, container_name) + return source_file + except Exception, ex: + logger.error("Unable to upload: {0}".format(ex.message)) + + +@task +def update_geo_info_task(ip_address, profile_id): + try: + ip = '188.141.70.110' if ip_address == '127.0.0.1' else ip_address + if ip: + g = GeoIP() + city = g.city(ip) + country = g.country(ip) + print "Updated user location" + except Exception, e: + logger.exception(e) + pass + + +@task +def notify_subscriber(session_id, uid): + if session_id is not None: + activity.post_activity('user:message', session_id, {'type': 'waveform', 'target': uid})