diff --git a/api/serializers.py b/api/serializers.py index e377265..dde88fe 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) diff --git a/dss/settings.py b/dss/settings.py index 097f0ac..e06d2da 100755 --- a/dss/settings.py +++ b/dss/settings.py @@ -123,13 +123,12 @@ INSTALLED_APPS = ( 'allauth.socialaccount.providers.twitter', 'pipeline', - 'dbbackup', + 'gunicorn', 'corsheaders', 'sorl.thumbnail', 'djcelery', 'spa', - 'gunicorn', 'spa.signals', 'core', 'storages', @@ -254,7 +253,9 @@ 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 \ No newline at end of file +DBBACKUP_DROPBOX_APP_SECRET = localsettings.DSS_DB_BACKUP_SECRET +""" \ No newline at end of file diff --git a/dss/urls.py b/dss/urls.py index 4297d82..cd09b5d 100755 --- a/dss/urls.py +++ b/dss/urls.py @@ -14,6 +14,7 @@ urlpatterns = patterns( (r'^_embed/', include('spa.embedding.urls')), (r'^__redir/blog/', include('spa.blog.urls')), (r'^__redir/social/', include('spa.social.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 9dcc2ff..0d9e4b4 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..b2e7bdc --- /dev/null +++ b/spa/podcast/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import patterns, url + +urlpatterns = patterns( + '', + url(r'^(?P[\w\d_.-]+)/favourites/?$', 'spa.podcast.views.favourites', name='podast_favourites_slug'), +) diff --git a/spa/podcast/views.py b/spa/podcast/views.py new file mode 100644 index 0000000..7eff5cd --- /dev/null +++ b/spa/podcast/views.py @@ -0,0 +1,35 @@ +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 + + +def favourites(request, uid): + try: + user = UserProfile.objects.order_by('-id').get(uid=uid) + except UserProfile.DoesNotExist: + raise Http404("User does not exist") + + fav_list = user.favourites.all() + return _render_podcast(request, user, fav_list) + + +def _render_podcast(request, user, list): + context = { + 'title': 'DSS Favourites', + 'description': 'All your favourites on Deep South Sounds', + 'link': 'https://deepsouthsounds.com/', + 'user': user.first_name, + 'summary': 'Deep South Sounds is a collective of like minded house heads from Ireland"s Deep South', + 'last_build_date': list[0].upload_date, + 'objects': 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..61095a6 --- /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