diff --git a/api/serializers.py b/api/serializers.py index e377265..c555694 100755 --- a/api/serializers.py +++ b/api/serializers.py @@ -98,6 +98,17 @@ class LikeSerializer(serializers.ModelSerializer): display_name = serializers.ReadOnlyField(source='get_nice_name') +class FavouriteSerializer(serializers.ModelSerializer): + class Meta: + model = UserProfile + fields = ( + 'display_name', + 'slug', + ) + + display_name = serializers.ReadOnlyField(source='get_nice_name') + + class GenreSerializer(serializers.ModelSerializer): class Meta: model = Genre @@ -162,6 +173,7 @@ class MixSerializer(serializers.ModelSerializer): 'plays', 'downloads', 'is_liked', + 'is_favourited', ] @@ -174,10 +186,11 @@ class MixSerializer(serializers.ModelSerializer): genres = GenreSerializer(many=True, required=False, read_only=True) likes = LikeSerializer(many=True, required=False, read_only=True) # slug_field='slug', many=True, read_only=True) - favourites = serializers.SlugRelatedField(slug_field='slug', many=True, read_only=True) + favourites = FavouriteSerializer(many=True, required=False, read_only=True) # slug_field='slug', many=True, read_only=True) plays = InlineActivityPlaySerializer(many=True, read_only=True, source='activity_plays') downloads = InlineActivityDownloadSerializer(read_only=True, source='activity_downloads') is_liked = serializers.SerializerMethodField(read_only=True) + is_favourited = serializers.SerializerMethodField(read_only=True) def update(self, instance, validated_data): # all nested representations need to be serialized separately here @@ -202,6 +215,25 @@ class MixSerializer(serializers.ModelSerializer): except UserProfile.DoesNotExist: pass + favourites = self.initial_data['favourites'] + unfavourited = instance.favourites.exclude(user__userprofile__slug__in=[f['slug'] for f in favourites]) + for uf in unfavourited: + # check that the user removing the like is an instance of the current user + # for now, only the current user can like stuff + if uf == self.context['request'].user.userprofile: + instance.update_favourite(uf, False) + + for favourite in favourites: + # check that the user adding the like is an instance of the current user + # for now, only the current user can like stuff + try: + user = UserProfile.objects.get(slug=favourite['slug']) + if user is not None and user == self.context['request'].user.userprofile: + instance.update_favourite(user, True) + + except UserProfile.DoesNotExist: + pass + genres = self.initial_data['genres'] instance.genres.clear() for genre in genres: @@ -224,6 +256,8 @@ class MixSerializer(serializers.ModelSerializer): return super(MixSerializer, self).update(instance, validated_data) except MixUpdateException as ex: raise ex + except Exception as ex: + raise ex def is_valid(self, raise_exception=False): return super(MixSerializer, self).is_valid(raise_exception) @@ -287,6 +321,7 @@ class UserProfileSerializer(serializers.ModelSerializer): 'profile_image_medium', 'profile_image_header', 'slug', + 'uid', 'likes', 'mix_count', 'isme', diff --git a/dss/settings.py b/dss/settings.py index 27407ed..022d4fe 100755 --- a/dss/settings.py +++ b/dss/settings.py @@ -124,12 +124,12 @@ INSTALLED_APPS = ( 'pipeline', #'dbbackup', + 'gunicorn', 'corsheaders', 'sorl.thumbnail', 'djcelery', 'spa', - 'gunicorn', 'spa.signals', 'core', 'storages', @@ -253,7 +253,12 @@ SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.yahoo.YahooOpenId' ) +""" DBBACKUP_STORAGE = 'dbbackup.storage.dropbox_storage' DBBACKUP_TOKENS_FILEPATH = '._dss_tokens' DBBACKUP_DROPBOX_APP_KEY = localsettings.DSS_DB_BACKUP_KEY DBBACKUP_DROPBOX_APP_SECRET = localsettings.DSS_DB_BACKUP_SECRET +<<<<<<< HEAD +======= +""" +>>>>>>> develop diff --git a/dss/urls.py b/dss/urls.py index 4297d82..2e9a6f5 100755 --- a/dss/urls.py +++ b/dss/urls.py @@ -14,6 +14,8 @@ urlpatterns = patterns( (r'^_embed/', include('spa.embedding.urls')), (r'^__redir/blog/', include('spa.blog.urls')), (r'^__redir/social/', include('spa.social.urls')), + (r'^podcasts/', include('spa.podcast.urls')), + (r'^podcast/', include('spa.podcast.urls')), url(r'', include('user_sessions.urls', 'user_sessions')), url(r'^', include('api.urls')), ) diff --git a/requirements.txt b/requirements.txt index 75e6744..f2b2de3 100755 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,6 @@ google-api-python-client django-celery django-scheduler django-recurrence -django-dbbackup azure diff --git a/spa/management/commands/add_user_uid.py b/spa/management/commands/add_user_uid.py new file mode 100755 index 0000000..dd5d1db --- /dev/null +++ b/spa/management/commands/add_user_uid.py @@ -0,0 +1,16 @@ +import uuid + +from django.core.management.base import NoArgsCommand + +from spa.models import UserProfile + + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + try: + users = UserProfile.objects.exclude(uid__isnull=False) + for user in users: + user.uid = uuid.uuid4() + user.save() + except Exception as ex: + print("Debug exception: %s" % ex) \ No newline at end of file diff --git a/spa/migrations/0026_userprofile_uid.py b/spa/migrations/0026_userprofile_uid.py new file mode 100644 index 0000000..6a37df2 --- /dev/null +++ b/spa/migrations/0026_userprofile_uid.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('spa', '0025_socialaccountlink_provider_data'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='uid', + field=models.UUIDField(editable=False, null=True), + ), + ] diff --git a/spa/models/mix.py b/spa/models/mix.py index 8a0e835..654f2e8 100755 --- a/spa/models/mix.py +++ b/spa/models/mix.py @@ -238,7 +238,7 @@ class Mix(BaseModel): if user.user.is_authenticated(): if value: if self.favourites.filter(user=user.user).count() == 0: - fav = ActivityFavourite(user=user) # , mix=self) + fav = ActivityFavourite(user=user, mix=self) fav.save() self.favourites.add(user) self.save() diff --git a/spa/models/userprofile.py b/spa/models/userprofile.py index f0e8590..657f44b 100755 --- a/spa/models/userprofile.py +++ b/spa/models/userprofile.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import User from django.core.exceptions import SuspiciousOperation from django.db import models from django.db.models import Count +import uuid from django_gravatar.helpers import has_gravatar, get_gravatar_url from sorl import thumbnail @@ -41,6 +42,8 @@ class UserProfile(BaseModel): ACTIVITY_SHARE_NETWORK_TWITTER = 2 user = models.OneToOneField(User, unique=True, related_name='userprofile') + uid = models.UUIDField(primary_key=False, editable=False, null=True) + avatar_type = models.CharField(max_length=15, default='social') avatar_image = models.ImageField(max_length=1024, blank=True, upload_to=avatar_name) display_name = models.CharField(blank=True, max_length=35) diff --git a/spa/podcast/urls.py b/spa/podcast/urls.py new file mode 100644 index 0000000..1081ba9 --- /dev/null +++ b/spa/podcast/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls import patterns, url + +urlpatterns = patterns( + '', + url(r'^(?P[\w\d_.-]+)/favourites/?$', 'spa.podcast.views.favourites', name='podast_favourites_slug'), + url(r'^(?P[\w\d_.-]+)/following/?$', 'spa.podcast.views.following', name='podast_following_slug'), + url(r'^(?P[\w\d_.-]+)/?$', 'spa.podcast.views.user', name='podast_user_slug'), + url(r'^/?$', 'spa.podcast.views.featured', name='podast_featured_slug'), +) diff --git a/spa/podcast/views.py b/spa/podcast/views.py new file mode 100644 index 0000000..7ff1aac --- /dev/null +++ b/spa/podcast/views.py @@ -0,0 +1,63 @@ +from django.http import Http404 +from django.http import HttpResponse +from django.shortcuts import render_to_response +from django.template import RequestContext + +from spa.models import UserProfile, Mix + + +def _get_user(uid): + try: + user = UserProfile.objects.order_by('-id').get(uid=uid) + except UserProfile.DoesNotExist: + raise Http404("User does not exist") + return user + + +def featured(request): + podcast_list = Mix.objects.order_by('-id').filter(is_private=False, is_featured=True) + return _render_podcast(request, 'Deep South Sounds', 'DSS Favourites', + 'All your favourites on Deep South Sounds', podcast_list) + + +def user(request, slug): + user = UserProfile.objects.get(slug=slug) + podcast_list = Mix.objects.order_by('-id').filter(is_private=False, user__slug=slug) + return _render_podcast(request, user.first_name, 'DSS {0}'.format(user.display_name), + 'All of {0}\'s mixes on Deep South Sounds'.format(user.display_name), podcast_list, + image=user.get_sized_avatar_image(1400, 1400)) + + +def favourites(request, uid): + user = _get_user(uid) + podcast_list = user.favourites.all() + return _render_podcast(request, user.first_name, 'DSS Favourites', + 'All your favourites on Deep South Sounds', podcast_list) + + +def following(request, uid): + user = _get_user(uid) + podcast_list = Mix.objects.order_by('-id').filter(is_private=False, user__in=user.following.all()) + return _render_podcast(request, user.first_name, 'DSS Following', + 'Mixes from people you follow on Deep South Sounds', podcast_list) + + +def _render_podcast(request, user, title, description, podcast_list, + image='https://dsscdn2.blob.core.windows.net/static/podcast_logo.png'): + context = { + 'title': title, + 'description': description, + 'link': 'https://deepsouthsounds.com/', + 'image': image, + 'user': user, + 'summary': 'Deep South Sounds is a collective of like minded house heads from Ireland"s Deep South', + 'last_build_date': podcast_list[0].upload_date, + 'objects': podcast_list, + } + response = render_to_response( + 'podcast/feed.xml', + context=context, + context_instance=RequestContext(request), + content_type='application/rss+xml' + ) + return response diff --git a/spa/templatetags/__init__.py b/spa/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/spa/templatetags/dss_extras.py b/spa/templatetags/dss_extras.py new file mode 100644 index 0000000..aa5efa0 --- /dev/null +++ b/spa/templatetags/dss_extras.py @@ -0,0 +1,33 @@ +from django import template +import datetime +import time +from email import utils + +register = template.Library() + + +@register.filter +def get_mix_url(obj): + return obj.get_full_url() + + +@register.filter +def get_mix_audio_url(obj): + return obj.get_download_url() + + +@register.filter +def seconds_to_hms(seconds): + try: + m, s = divmod(seconds, 60) + h, m = divmod(m, 60) + return "%d:%02d:%02d" % (h, m, s) + except Exception as ex: + return "00:00:09" + + +@register.filter +def date_to_rfc2822(date): + nowtuple = date.timetuple() + nowtimestamp = time.mktime(nowtuple) + return utils.formatdate(nowtimestamp) \ No newline at end of file diff --git a/static/img/podcast_logo.png b/static/img/podcast_logo.png new file mode 100644 index 0000000..4a87901 Binary files /dev/null and b/static/img/podcast_logo.png differ diff --git a/templates/podcast/feed.xml b/templates/podcast/feed.xml new file mode 100644 index 0000000..5ca841e --- /dev/null +++ b/templates/podcast/feed.xml @@ -0,0 +1,52 @@ + + + +{% load dss_extras %} + + {{ title }} + {{ description }} + {{ link }} + en-ie + Copyright 2016 + {{ last_build_date|date_to_rfc2822 }} + {{ last_build_date|date_to_rfc2822 }} + http://blogs.law.harvard.edu/tech/rss + webmaster@deepsouthsounds.com + + {{ user }} @ deepsouthsounds + {{ title }} + {{ summary }} + + + Fergal Moran + fergal@deepsouthsounds.com + + + No + + + + + + + {% for item in objects %} + + {{ item.title }} + {{ item|get_mix_url }} + {{ item|get_mix_url }} + {{ item.description }} + + Podcasts + {{ item.upload_date|date_to_rfc2822 }} + {{ item.user.display_name }} + No + {{ item.description }} + {{ item.description }} + {{ item.duration|seconds_to_hms }} + deep south sounds, deep house, Cork, Fergal Moran, Ed Dunlea, {{ item.title }}, {{ item.user.display_name }} + + + {% endfor %} + + + diff --git a/templates/podcast/full.feed.html b/templates/podcast/full.feed.html new file mode 100644 index 0000000..4897a5f --- /dev/null +++ b/templates/podcast/full.feed.html @@ -0,0 +1,57 @@ + + + + {{ object.title }} + {{ object.link }} + {{ object.description|striptags }} + {% if object.language %}{{ object.language }}{% endif %} + ℗ & © {% now "Y" %} {{ object.organization }}. {{ object.copyright }}. + {% for author in object.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{{ author.email }}{% endfor %} + {% if object.author.email or object.webmaster.email %}{% if object.webmaster.email %}{{ object.webmaster.email }}{% else %}{% endif %}{% endif %} + {{ object.list.0.date|date:"r" }} + {% if object.category_show %}{{ object.category_show }}{% endif %} + Django Web Framework + http://blogs.law.harvard.edu/tech/rss + {% if object.ttl %}{{ object.ttl }}{% endif %} + {% if object.image %} + {{ object.image.url }} + {{ object.title }} + {{ object.link }} + {% endif %} + {{ object.organization }} + + {% for author in object.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% endfor %} + {% for author in object.author.all %}{{ author.email }}{% if forloop.last %}{% else %}, {% endif %}{% endfor %} + + {% if object.subtitle %}{{ object.subtitle }}{% endif %} + {% if object.summary %}{{ object.summary|striptags }}{% else %}{{ object.description|striptags }}{% endif %} + {% if object.image %}{% endif %} + {% if object.category.all %}{% for category in object.category.all %}{% if category.name %} + + + {% else %} + {% endif %}{% endfor %}{% endif %} + {% if object.explicit %}{{ object.explicit|lower }}{% endif %} + {% if object.block %}yes{% endif %} + {% if object.redirect %}{{ object.redirect }}{% endif %} + + {% for episode in object.episode_set.published %} + {{ episode.title }} + {{ episode.enclosure_set.all.0.file.url }} + {{ episode.description|striptags }} + {% for author in object.author.all %}{{ author.email }}{% if forloop.last %}{% else %}, {% endif %}{% endfor %} + {% if episode.category %}{{ episode.category }}{% endif %} + + {{ episode.enclosure_set.all.0.file.url }} + {{ episode.date|date:"r" }} GMT + {% for author in episode.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% endfor %} + {% if episode.subtitle %}{{ episode.subtitle }}{% endif %} + {% if episode.summary %}{{ episode.summary|striptags }}{% else %}{{ episode.description|striptags }}{% endif %} + {% if episode.minutes and episode.seconds %}{{ episode.minutes }}:{{ episode.seconds }}{% endif %} + {% if episode.keywords %}{{ episode.keywords }}{% endif %} + {% if episode.explicit %}{{ episode.explicit|lower }}{% endif %} + {% if episode.block %}yes{% endif %} + + {% endfor %} + + \ No newline at end of file