├── .gitignore ├── Dockerfile ├── db.sqlite3 ├── docker-compose.yml ├── feed ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── logging.conf ├── manage.py ├── music ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20170209_1653.py │ ├── 0003_auto_20170209_1656.py │ ├── 0004_auto_20170212_0944.py │ ├── 0005_auto_20170226_1510.py │ ├── 0006_auto_20170226_1511.py │ ├── 0007_song_spotify_uri.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── rest_friendship ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── spotify ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py └── views.py ├── start.sh ├── sync_backend ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py └── user_profile ├── SpotifyBackend.py ├── __init__.py ├── admin.py ├── apps.py ├── migrations ├── 0001_initial.py ├── 0002_userprofile_access_token.py ├── 0003_auto_20161002_2215.py ├── 0004_userprofile_refresh_token.py ├── 0005_auto_20161002_2316.py ├── 0006_remove_userprofile_email.py ├── 0007_auto_20161015_1717.py ├── 0008_auto_20161017_0401.py ├── 0009_auto_20161017_0404.py ├── 0010_auto_20161017_0406.py ├── 0011_auto_20161017_0407.py ├── 0012_auto_20161017_0407.py ├── 0013_auto_20161120_0448.py ├── 0014_auto_20161120_0523.py └── __init__.py ├── models.py ├── serializers.py ├── tests.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | deactivate 2 | *.log 3 | .vscode 4 | /user_uploads 5 | *.pyc 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | 3 | # FROM directive instructing base image to build upon 4 | FROM python:2-onbuild 5 | 6 | # COPY startup script into known file location in container 7 | COPY start.sh /start.sh 8 | 9 | # RUN mkdir /usr/src/app/user_uploads 10 | 11 | # EXPOSE port 80 to allow communication to/from server 12 | EXPOSE 80 13 | 14 | # CMD specifcies the command to execute to start the server running. 15 | CMD ["/start.sh"] 16 | # done! 17 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/db.sqlite3 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: postgres 5 | web: 6 | build: . 7 | command: ./start.sh 8 | volumes: 9 | - .:/usr/src/app 10 | ports: 11 | - "80:80" 12 | depends_on: 13 | - db 14 | -------------------------------------------------------------------------------- /feed/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/feed/__init__.py -------------------------------------------------------------------------------- /feed/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /feed/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class FeedConfig(AppConfig): 7 | name = 'feed' 8 | -------------------------------------------------------------------------------- /feed/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/feed/migrations/__init__.py -------------------------------------------------------------------------------- /feed/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /feed/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /feed/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^user/(?P\w{0,50})/', views.user, name="user"), 7 | url(r'^timeline/(?P\w{0,50})/', views.timeline, name="timeline") 8 | ] -------------------------------------------------------------------------------- /feed/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | try: 4 | from django.contrib.auth import get_user_model 5 | user_model = get_user_model() 6 | except ImportError: 7 | from django.contrib.auth.models import User 8 | user_model = User 9 | 10 | from rest_framework import status 11 | from rest_framework.decorators import api_view 12 | from rest_framework.response import Response 13 | 14 | from user_profile.serializers import UserSerializer, UserProfileSerializer 15 | from user_profile.models import UserProfile 16 | from stream_django.feed_manager import feed_manager 17 | from stream_django.enrich import Enrich 18 | 19 | 20 | enricher = Enrich() 21 | 22 | 23 | # Create your views here. 24 | @api_view(['GET']) 25 | def user(request, username): 26 | """Returns the user feed from getstream""" 27 | print "USERNAME FOR USER FEED: %s" % username 28 | user_obj = user_model.objects.get(username=username) 29 | user_feed = feed_manager.get_user_feed(user_obj.id) 30 | activities = user_feed.get(limit=25)['results'] 31 | enriched_activities = enricher.enrich_activities(activities) 32 | serialized_activities = serialize_activities(enriched_activities, request) 33 | return Response(serialized_activities, status=status.HTTP_200_OK) 34 | 35 | @api_view(['GET']) 36 | def timeline(request, username): 37 | """Returns the timeline from getstream""" 38 | print "USERNAME FOR TIMELINE FEED: %s" % username 39 | user_obj = user_model.objects.get(username=username) 40 | timeline_feed = feed_manager.get_news_feeds(user_obj.id)['timeline'] 41 | activities = timeline_feed.get(limit=25)['results'] 42 | enriched_activities = enricher.enrich_activities(activities) 43 | serialized_activities = serialize_activities(enriched_activities, request) 44 | return Response(serialized_activities, status=status.HTTP_200_OK) 45 | 46 | def get_serialized_object_or_str(obj): 47 | """Grabs from the model the field to determine what class to serialize""" 48 | if hasattr(obj, 'activity_object_serializer_class'): 49 | obj = obj.activity_object_serializer_class(obj).data 50 | else: 51 | obj = str(obj) # Could also raise exception here 52 | return obj 53 | 54 | def serialize_activities(activities, request): 55 | """Serializes the objects based on a property "activity_object_serializer_class""" 56 | for activity in activities: 57 | activity['object'] = get_serialized_object_or_str(activity['object']) 58 | user = activity['actor'] 59 | activity['actor'] = UserSerializer(activity['actor'], context={'request': request}).data 60 | user_profile = UserProfile.objects.get(user=user) 61 | activity['profile'] = UserProfileSerializer(user_profile).data 62 | return activities 63 | -------------------------------------------------------------------------------- /logging.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root, gunicorn.error 3 | 4 | [handlers] 5 | keys=console 6 | 7 | [formatters] 8 | keys=json 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=console 13 | 14 | [logger_gunicorn.error] 15 | level=ERROR 16 | handlers=console 17 | propagate=0 18 | qualname=gunicorn.error 19 | 20 | [handler_console] 21 | class=StreamHandler 22 | formatter=json 23 | args=(sys.stdout, ) 24 | 25 | [formatter_json] 26 | class=jsonlogging.JSONFormatter 27 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sync_backend.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /music/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/music/__init__.py -------------------------------------------------------------------------------- /music/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /music/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class MusicConfig(AppConfig): 7 | name = 'music' 8 | -------------------------------------------------------------------------------- /music/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-02-09 16:50 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Artist', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('name', models.CharField(max_length=200)), 24 | ('spotify_id', models.TextField()), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='Playlist', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('created_date', models.DateTimeField(auto_now_add=True)), 32 | ('modified_date', models.DateTimeField(auto_now=True)), 33 | ('title', models.TextField()), 34 | ('message', models.TextField()), 35 | ('locked', models.BooleanField()), 36 | ('public', models.BooleanField()), 37 | ('playlist_image', models.ImageField(null=True, upload_to=b'user_uploads')), 38 | ('playlist_video', models.FileField(null=True, upload_to=b'video')), 39 | ('latitude', models.TextField()), 40 | ('longitude', models.TextField()), 41 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='creator', to=settings.AUTH_USER_MODEL)), 42 | ('shared_with_users', models.ManyToManyField(related_name='shared_with', to=settings.AUTH_USER_MODEL)), 43 | ], 44 | ), 45 | migrations.CreateModel( 46 | name='Song', 47 | fields=[ 48 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 49 | ('title', models.CharField(max_length=200)), 50 | ('spotify_id', models.TextField()), 51 | ('track_preview_url', models.URLField()), 52 | ('album_artwork', models.URLField()), 53 | ('album_title', models.TextField()), 54 | ('artists', models.ManyToManyField(to='music.Artist')), 55 | ], 56 | ), 57 | migrations.AddField( 58 | model_name='playlist', 59 | name='song_list', 60 | field=models.ManyToManyField(to='music.Song'), 61 | ), 62 | ] 63 | -------------------------------------------------------------------------------- /music/migrations/0002_auto_20170209_1653.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-02-09 16:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('music', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='playlist', 17 | name='latitude', 18 | field=models.DecimalField(decimal_places=6, max_digits=9), 19 | ), 20 | migrations.AlterField( 21 | model_name='playlist', 22 | name='longitude', 23 | field=models.DecimalField(decimal_places=6, max_digits=9), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /music/migrations/0003_auto_20170209_1656.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-02-09 16:56 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('music', '0002_auto_20170209_1653'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='playlist', 17 | name='latitude', 18 | field=models.DecimalField(decimal_places=6, max_digits=9, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name='playlist', 22 | name='longitude', 23 | field=models.DecimalField(decimal_places=6, max_digits=9, null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /music/migrations/0004_auto_20170212_0944.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-02-12 09:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('music', '0003_auto_20170209_1656'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='playlist', 17 | old_name='song_list', 18 | new_name='songs', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /music/migrations/0005_auto_20170226_1510.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-02-26 15:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('music', '0004_auto_20170212_0944'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='playlist', 17 | old_name='created_date', 18 | new_name='created_at', 19 | ), 20 | migrations.RenameField( 21 | model_name='playlist', 22 | old_name='modified_date', 23 | new_name='modified_at', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /music/migrations/0006_auto_20170226_1511.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-02-26 15:11 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('music', '0005_auto_20170226_1510'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='playlist', 17 | old_name='created_by', 18 | new_name='user', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /music/migrations/0007_song_spotify_uri.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2017-03-25 14:01 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('music', '0006_auto_20170226_1511'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='song', 17 | name='spotify_uri', 18 | field=models.TextField(default=b''), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /music/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/music/migrations/__init__.py -------------------------------------------------------------------------------- /music/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | from stream_django.activity import Activity 5 | from stream_django.feed_manager import feed_manager 6 | from stream_django.activity import create_model_reference 7 | 8 | class Artist(models.Model): 9 | """Stores information for a Spotify Artist""" 10 | name = models.CharField(max_length=200) 11 | spotify_id = models.TextField() 12 | 13 | def __str__(self): 14 | return self.name 15 | 16 | class Song(models.Model): 17 | """Stores the information for a Spotify song that 18 | can be used in the creation of playlists, mood posts, 19 | or song prescriptions""" 20 | title = models.CharField(max_length=200) 21 | spotify_id = models.TextField() 22 | spotify_uri = models.TextField(default='') 23 | track_preview_url = models.URLField() 24 | album_artwork = models.URLField() 25 | album_title = models.TextField() 26 | artists = models.ManyToManyField(Artist) 27 | 28 | def __str__(self): 29 | return self.title 30 | 31 | class Playlist(models.Model, Activity): 32 | """A collection of songs that can be shared with 33 | multiple users""" 34 | created_at = models.DateTimeField(auto_now_add=True) 35 | modified_at = models.DateTimeField(auto_now=True) 36 | title = models.TextField() 37 | message = models.TextField() 38 | locked = models.BooleanField() 39 | public = models.BooleanField() 40 | user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='creator') 41 | songs = models.ManyToManyField(Song) 42 | shared_with_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='shared_with') 43 | playlist_image = models.ImageField(upload_to='user_uploads', null=True) 44 | # Eventually change this to upload to a dir named after 45 | # the user or something so it doesn't get too full. 46 | playlist_video = models.FileField(upload_to='video', null=True) 47 | latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True) 48 | longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True) 49 | 50 | def __str__(self): 51 | return self.title 52 | 53 | @property 54 | def activity_object_serializer_class(self): 55 | from .serializers import PlaylistSerializer 56 | return PlaylistSerializer 57 | 58 | @property 59 | def activity_notify(self): 60 | """Notify everyone who the author has shared with""" 61 | feeds = [feed_manager.get_notification_feed(u.id) for u in self.shared_with_users.all()] 62 | return feeds 63 | 64 | @property 65 | def extra_activity_data(self): 66 | ref = create_model_reference(self.user.userprofile) 67 | return {'user_profile': ref } 68 | -------------------------------------------------------------------------------- /music/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User, Group 2 | from rest_framework import serializers 3 | from .models import Playlist, Song, Artist 4 | from user_profile.serializers import UserSerializer, UserProfileSerializer 5 | 6 | 7 | class ArtistSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = Artist 10 | fields = ('name', 'spotify_id') 11 | 12 | class SongSerializer(serializers.ModelSerializer): 13 | artists = ArtistSerializer(many=True, read_only=True) 14 | class Meta: 15 | model = Song 16 | fields = ('title', 'spotify_id', 'spotify_uri', 'track_preview_url', 'album_artwork', 'album_title', 'artists') 17 | 18 | class PlaylistSerializer(serializers.ModelSerializer): 19 | songs = SongSerializer(many=True, read_only=True) 20 | shared_with_users = UserSerializer(many=True, read_only=True) 21 | 22 | class Meta: 23 | model = Playlist 24 | fields = ('created_at', 'modified_at', 'title', 'message', 'locked', 'public', 'user', 'songs', 'shared_with_users', 'playlist_image', 'playlist_video', 'latitude', 'longitude') 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /music/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /music/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^playlist/', views.playlist, name="playlist") 7 | ] -------------------------------------------------------------------------------- /music/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.shortcuts import render 4 | from django.http import HttpResponse 5 | from django.shortcuts import get_object_or_404 6 | 7 | from rest_framework import status 8 | from rest_framework.decorators import api_view 9 | from rest_framework.response import Response 10 | 11 | from models import Playlist, Song, Artist 12 | from serializers import PlaylistSerializer 13 | 14 | try: 15 | from django.contrib.auth import get_user_model 16 | user_model = get_user_model() 17 | except ImportError: 18 | from django.contrib.auth.models import User 19 | user_model = User 20 | 21 | 22 | # Create your views here. 23 | 24 | @api_view(['POST', 'GET']) 25 | def playlist(request): 26 | if request.method == 'GET': 27 | queryset = Playlist.objects.filter(user=request.user).order_by('-created_at') 28 | playlist_serializer = PlaylistSerializer(queryset, context={'request': request}, many=True) 29 | return Response(playlist_serializer.data, status=status.HTTP_200_OK) 30 | data = request.data 31 | user = request.user 32 | playlist_to_save = Playlist() 33 | playlist_to_save.title = data.get('title') 34 | playlist_to_save.message = data.get('message') 35 | playlist_to_save.locked = True if data.get('locked') == 'true' else False 36 | playlist_to_save.public = True if data.get('public') == 'true' else False 37 | playlist_to_save.latitude = data.get('latitude') 38 | playlist_to_save.longitude = data.get('longitude') 39 | playlist_to_save.user = user 40 | playlist_to_save.playlist_image = request.FILES.get('image') 41 | playlist_to_save.playlist_video = request.FILES.get('video') 42 | playlist_to_save.save() 43 | 44 | songs = data.get('songs', []) 45 | if type(songs) == type(""): 46 | songs = json.loads(songs) 47 | 48 | # Save songs 49 | for song_json in songs: 50 | images = song_json['album']['images'] 51 | song, _ = Song.objects.get_or_create( 52 | title=song_json['name'], 53 | spotify_id=song_json['id'], 54 | spotify_uri=song_json['uri'], 55 | track_preview_url=song_json['preview_url'], 56 | album_artwork=images[0].get('url') if images else '', 57 | album_title=song_json['album']['name'] 58 | ) 59 | song.save() 60 | playlist_to_save.songs.add(song) 61 | 62 | for artist_json in song_json['artists']: 63 | artist, _ = Artist.objects.get_or_create( 64 | name=artist_json['name'], 65 | spotify_id=artist_json['id'] 66 | ) 67 | song.artists.add(artist) 68 | 69 | profiles = data.get('profiles', []) 70 | if type(profiles) == type(""): 71 | profiles = json.loads(profiles) 72 | 73 | for profile in profiles: 74 | share_with_user = get_object_or_404(user_model, username=profile['user']['username']) 75 | # user_id = profile_json['user']['id'] 76 | playlist_to_save.shared_with_users.add(share_with_user) 77 | 78 | # notify_feed_of_playlist(user.userprofile, playlist_to_save) 79 | 80 | return Response(PlaylistSerializer(playlist_to_save, 81 | context={'request': request}).data, 82 | status=status.HTTP_201_CREATED) 83 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.2 2 | django-cors-headers==1.2.1 3 | django-environ==0.4.1 4 | django-friendship==1.5.0 5 | djangorestframework==3.4.7 6 | gunicorn==19.7.0 7 | httpie==0.9.6 8 | httpsig==1.1.2 9 | json-logging-py==0.2 10 | Pillow==3.4.2 11 | psycopg2==2.7.1 12 | pycrypto==2.6.1 13 | Pygments==2.1.3 14 | PyJWT==1.3.0 15 | pytz==2016.10 16 | readline==6.2.4.1 17 | requests==2.11.1 18 | six==1.10.0 19 | spotipy==2.3.8 20 | stream-django==1.3.1 21 | stream-python==2.3.9 22 | -------------------------------------------------------------------------------- /rest_friendship/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/rest_friendship/__init__.py -------------------------------------------------------------------------------- /rest_friendship/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /rest_friendship/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class SyncFriendshipConfig(AppConfig): 7 | name = 'sync_friendship' 8 | -------------------------------------------------------------------------------- /rest_friendship/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/rest_friendship/migrations/__init__.py -------------------------------------------------------------------------------- /rest_friendship/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /rest_friendship/serializers.py: -------------------------------------------------------------------------------- 1 | 2 | from django.contrib.auth.models import User, Group 3 | from rest_framework import serializers 4 | from friendship.models import Follow 5 | 6 | class FollowSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Follow 9 | fields = ('follower', 'followee', 'created') 10 | -------------------------------------------------------------------------------- /rest_friendship/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /rest_friendship/urls.py: -------------------------------------------------------------------------------- 1 | """My own wrapper around django friendship to return DRF responses""" 2 | try: 3 | from django.conf.urls import url 4 | except ImportError: 5 | from django.conf.urls.defaults import url 6 | from rest_friendship.views import followers, follows, following, \ 7 | follower_add, follower_remove, follow_counts 8 | 9 | urlpatterns = [ 10 | url( 11 | regex=r'^followers/(?P[\w-]+)/$', 12 | view=followers, 13 | name='friendship_followers', 14 | ), 15 | url( 16 | regex=r'^following/(?P[\w-]+)/$', 17 | view=following, 18 | name='friendship_following', 19 | ), 20 | url( 21 | regex=r'^follower/add/(?P[\w-]+)/$', 22 | view=follower_add, 23 | name='follower_add', 24 | ), 25 | url( 26 | regex=r'^follower/remove/(?P[\w-]+)/$', 27 | view=follower_remove, 28 | name='follower_remove', 29 | ), 30 | url( 31 | regex=r'^follower/follows/(?P[\w-]+)/$', 32 | view=follows, 33 | name='follows', 34 | ), 35 | url( 36 | regex=r'^counts/(?P[\w-]+)/$', 37 | view=follow_counts, 38 | name='follow_counts', 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /rest_friendship/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404, redirect 2 | from django.contrib.auth.decorators import login_required 3 | from django.conf import settings 4 | 5 | try: 6 | from django.contrib.auth import get_user_model 7 | user_model = get_user_model() 8 | except ImportError: 9 | from django.contrib.auth.models import User 10 | user_model = User 11 | 12 | from rest_framework import status 13 | from rest_framework.decorators import api_view 14 | from rest_framework.response import Response 15 | 16 | from stream_django.feed_manager import feed_manager 17 | 18 | from friendship.models import Friend, Follow, FriendshipRequest 19 | from friendship.exceptions import AlreadyExistsError 20 | from rest_friendship.serializers import FollowSerializer 21 | 22 | get_friendship_context_object_name = lambda: getattr(settings, 'FRIENDSHIP_CONTEXT_OBJECT_NAME', 'user') 23 | get_friendship_context_object_list_name = lambda: getattr(settings, 'FRIENDSHIP_CONTEXT_OBJECT_LIST_NAME', 'users') 24 | 25 | @api_view(['GET']) 26 | def followers(request, username): 27 | """ List this user's followers """ 28 | user = get_object_or_404(user_model, username=username) 29 | the_followers = Follow.objects.followers(user) 30 | 31 | return Response(FollowSerializer(the_followers, 32 | context={'request': request}, many=True).data, 33 | status=status.HTTP_200_OK) 34 | 35 | 36 | 37 | @api_view(['GET']) 38 | def following(request, username): 39 | """ List who this user follows """ 40 | user = get_object_or_404(user_model, username=username) 41 | users_following = Follow.objects.following(user) 42 | 43 | return Response(FollowSerializer(users_following, 44 | context={'request': request}, many=True).data, 45 | status=status.HTTP_200_OK) 46 | 47 | @api_view(['GET']) 48 | def follows(request, followee_username): 49 | """ returns whether or not the current user is following this person """ 50 | user = get_object_or_404(user_model, username=followee_username) 51 | follows = Follow.objects.follows(request.user, user) 52 | 53 | return Response({'follows': follows}, 54 | status=status.HTTP_200_OK) 55 | 56 | 57 | @api_view(['GET']) 58 | def follow_counts(request, username): 59 | """ Gets counts for followers and followees """ 60 | user = get_object_or_404(user_model, username=username) 61 | users_following = Follow.objects.following(user).count() 62 | users_followers = Follow.objects.followers(user).count() 63 | 64 | return Response({'following': users_following, 65 | 'followers': users_followers}, 66 | status=status.HTTP_200_OK) 67 | 68 | @api_view(['POST']) 69 | def follower_add(request, followee_username): 70 | """ Create a following relationship """ 71 | 72 | print "IN FOLLOWER_ADD", followee_username 73 | exists = False 74 | if request.method == 'POST': 75 | followee = user_model.objects.get(username=followee_username) 76 | follower = request.user 77 | try: 78 | Follow.objects.add_follower(follower, followee) 79 | feed_manager.follow_user(follower.id, followee.id) 80 | exists = True 81 | except AlreadyExistsError: 82 | exists = True 83 | 84 | if exists: 85 | return Response({'result': 'success'}, 86 | status=status.HTTP_201_CREATED) 87 | return Response({'error': 'Invalid request'}, status=status.HTTP_404_NOT_FOUND) 88 | 89 | 90 | @api_view(['POST']) 91 | def follower_remove(request, followee_username): 92 | """ Remove a following relationship """ 93 | if request.method == 'POST': 94 | followee = user_model.objects.get(username=followee_username) 95 | follower = request.user 96 | Follow.objects.remove_follower(follower, followee) 97 | feed_manager.unfollow_user(follower.id, followee.id) 98 | return Response({'result': 'success'}, 99 | status=status.HTTP_200_OK) 100 | return Response({'error': 'Invalid request'}, status=status.HTTP_404_NOT_FOUND) -------------------------------------------------------------------------------- /spotify/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/spotify/__init__.py -------------------------------------------------------------------------------- /spotify/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /spotify/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class SpotifyConfig(AppConfig): 7 | name = 'spotify' 8 | -------------------------------------------------------------------------------- /spotify/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/spotify/migrations/__init__.py -------------------------------------------------------------------------------- /spotify/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /spotify/serializers.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/spotify/serializers.py -------------------------------------------------------------------------------- /spotify/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /spotify/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import JsonResponse 3 | from django.views.decorators.csrf import csrf_exempt 4 | from rest_framework import status 5 | from rest_framework.response import Response 6 | import spotipy 7 | 8 | # Create your views here. 9 | def me(request): 10 | """ Returns json representing the user or None if the token is invalid 11 | 12 | e.g. {u'product': u'open', u'display_name': u'Kelly Quinn Nicholes', u'external_urls': {u'spotify': u'https://open.spotify.com/user/22rtjcrjy7qe7lxwxzwpwpprq'}, u'country': u'US', u'uri': u'spotify:user:22rtjcrjy7qe7lxwxzwpwpprq', u'href': u'https://api.spotify.com/v1/users/22rtjcrjy7qe7lxwxzwpwpprq', u'followers': {u'total': 3, u'href': None}, u'images': [{u'url': u'https://scontent.xx.fbcdn.net/v/t1.0-1/p200x200/14141580_10153854114853008_298958832652656286_n.jpg?oh=f9122eb8da594fa69e642409ed2ac0a0&oe=5865CDA1', u'width': None, u'height': None}], u'type': u'user', u'id': u'22rtjcrjy7qe7lxwxzwpwpprq'} 13 | """ 14 | access_token = request.GET.get('access_token') 15 | user = None 16 | sp = spotipy.Spotify(auth=access_token) 17 | try: 18 | user = sp.current_user() 19 | return JsonResponse(user) 20 | except spotipy.client.SpotifyException: 21 | return JsonResponse({'error': 'Invalid access token'}, status=status.HTTP_403_FORBIDDEN) 22 | 23 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start Gunicorn processes 4 | echo Starting Gunicorn. 5 | exec gunicorn sync_backend.wsgi:application \ 6 | --bind 0.0.0.0:80 \ 7 | --workers 3 \ 8 | --error-logfile gunicorn_err.log \ 9 | --log-file gunicorn.log \ 10 | --log-config logging.conf 11 | 12 | -------------------------------------------------------------------------------- /sync_backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/sync_backend/__init__.py -------------------------------------------------------------------------------- /sync_backend/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for sync_backend project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'zqbcm)1nx&ptsdo7jjt)p#9x$s@(*9*q01g+in44lz9@mi-ht(' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = ['35.167.143.161'] 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'rest_framework.authtoken', 42 | 'corsheaders', 43 | 'spotify.apps.SpotifyConfig', 44 | 'user_profile.apps.UserProfileConfig', 45 | 'music.apps.MusicConfig', 46 | 'friendship', 47 | 'rest_friendship', 48 | 'stream_django' 49 | ] 50 | 51 | MIDDLEWARE = [ 52 | 'django.middleware.security.SecurityMiddleware', 53 | 'django.contrib.sessions.middleware.SessionMiddleware', 54 | 'corsheaders.middleware.CorsMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.messages.middleware.MessageMiddleware', 59 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 60 | ] 61 | 62 | ROOT_URLCONF = 'sync_backend.urls' 63 | 64 | TEMPLATES = [ 65 | { 66 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 67 | 'DIRS': [], 68 | 'APP_DIRS': True, 69 | 'OPTIONS': { 70 | 'context_processors': [ 71 | 'django.template.context_processors.debug', 72 | 'django.template.context_processors.request', 73 | 'django.contrib.auth.context_processors.auth', 74 | 'django.contrib.messages.context_processors.messages', 75 | ], 76 | }, 77 | }, 78 | ] 79 | 80 | WSGI_APPLICATION = 'sync_backend.wsgi.application' 81 | 82 | 83 | # Database 84 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 85 | if DEBUG: 86 | DATABASES = { 87 | 'default': { 88 | 'ENGINE': 'django.db.backends.sqlite3', 89 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 90 | } 91 | } 92 | else: 93 | DATABASES = { 94 | 'default': { 95 | 'ENGINE': 'django.db.backends.postgresql', 96 | 'NAME': 'postgres', 97 | 'USER': 'postgres', 98 | 'HOST': 'db', 99 | 'PORT': 5432, 100 | } 101 | } 102 | 103 | 104 | 105 | 106 | 107 | # Password validation 108 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 109 | 110 | AUTH_PASSWORD_VALIDATORS = [] 111 | 112 | REST_FRAMEWORK = { 113 | 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',), 114 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 115 | 'user_profile.SpotifyBackend.SpotifyBackend', 116 | ), 117 | 'PAGE_SIZE': 10 118 | } 119 | 120 | 121 | # Internationalization 122 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 123 | 124 | LANGUAGE_CODE = 'en-us' 125 | 126 | TIME_ZONE = 'UTC' 127 | 128 | USE_I18N = True 129 | 130 | USE_L10N = True 131 | 132 | USE_TZ = True 133 | 134 | CORS_EXPOSE_HEADERS = ['Access-Control-Allow-Origin', 'Access-Control-Allow-Headers'] 135 | 136 | CORS_ORIGIN_ALLOW_ALL = True 137 | 138 | CORS_ALLOW_CREDENTIALS = True 139 | 140 | # Static files (CSS, JavaScript, Images) 141 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 142 | 143 | AUTHENTICATION_BACKENDS = ('user_profile.SpotifyBackend.SpotifyBackend', ) 144 | 145 | STATIC_URL = '/static/' 146 | 147 | MEDIA_URL = '/media/' 148 | 149 | MEDIA_ROOT = os.path.join(BASE_DIR, 'user_uploads') 150 | 151 | STREAM_API_KEY = 'm9njdw3t2a8t' 152 | STREAM_API_SECRET = 'vp8vatjx6833s86pqck79ga9zjxsk6q2vbnp9qbks8x6fcsqturke3afjaztngkd' 153 | -------------------------------------------------------------------------------- /sync_backend/urls.py: -------------------------------------------------------------------------------- 1 | """sync_backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | from django.conf.urls import url, include 19 | from rest_framework import routers 20 | from user_profile import views as user_profile_views 21 | from spotify import views as spotify_views 22 | from django.conf import settings 23 | from django.conf.urls.static import static 24 | 25 | 26 | 27 | router = routers.DefaultRouter() 28 | router.register(r'users', user_profile_views.UserViewSet) 29 | router.register(r'groups', user_profile_views.GroupViewSet) 30 | router.register(r'user_profile', user_profile_views.UserProfileViewSet) 31 | 32 | # Wire up our API using automatic URL routing. 33 | # Additionally, we include login URLs for the browsable API. 34 | urlpatterns = [ 35 | url(r'^admin/', admin.site.urls), 36 | url(r'^', include(router.urls)), 37 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), 38 | url(r'^api/spotify/me', spotify_views.me, name='me'), 39 | url(r'^api/user/create', user_profile_views.create_user, name='create_user'), 40 | url(r'^api/user/update', user_profile_views.update_user, name='update_user'), 41 | url(r'^api/user/autocomplete', user_profile_views.user_autocomplete, name='user_autocomplete'), 42 | url(r'^api/user/(?P\w{0,50})', user_profile_views.user, name='user'), 43 | url(r'^api/profile-picture', user_profile_views.save_profile_picture, name='save_profile_picture'), 44 | url(r'^api/profile-background-picture', user_profile_views.save_profile_background_picture, name='save_profile_background_picture'), 45 | url(r'^api/music/', include('music.urls')), 46 | url(r'^api/feed/', include('feed.urls')), 47 | url(r'^api/friendship/', include('rest_friendship.urls')) 48 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 49 | -------------------------------------------------------------------------------- /sync_backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sync_backend project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sync_backend.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /user_profile/SpotifyBackend.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth.hashers import check_password 3 | from django.contrib.auth.models import User 4 | from rest_framework.authentication import BaseAuthentication, get_authorization_header 5 | from rest_framework import exceptions 6 | from .models import UserProfile 7 | 8 | import datetime 9 | import json 10 | import spotipy 11 | 12 | 13 | class SpotifyBackend(BaseAuthentication): 14 | """ 15 | Authenticate against the validity of the spotify user. 16 | """ 17 | 18 | def authenticate_header(self, request): 19 | return 'Bearer' 20 | 21 | def authenticate(self, request): 22 | """Try to get the spotify user information from spotify. 23 | If the info comes back, get a user that has the same ID as the spotifyID. 24 | If it doesn't, return None. 25 | """ 26 | print "in authenticate" 27 | spotify_auth_token = self.get_auth_token(request) 28 | sp = spotipy.Spotify(auth=spotify_auth_token) 29 | try: 30 | spotify_user = sp.current_user() 31 | print spotify_user 32 | except spotipy.client.SpotifyException, e: 33 | print "spotipy exception getting current user!", e.msg 34 | return None, None 35 | spotify_id = spotify_user['id'] 36 | try: 37 | user_profile = UserProfile.objects.get(spotify_id=spotify_id) 38 | user = user_profile.user 39 | except UserProfile.DoesNotExist: 40 | display_name = spotify_user.get('display_name') 41 | first_name = '' 42 | last_name = '' 43 | if display_name != None and len(display_name) > 0: 44 | name_array = display_name.split(' ') 45 | print "getting first name from spotify" 46 | first_name = ' '.join(name_array[:-1]) 47 | if len(name_array) > 0: 48 | last_name = name_array[-1] 49 | print "just got last name from spotify" 50 | user = User(username=spotify_id, 51 | password=User.objects.make_random_password(), 52 | email=spotify_user.get('email'), 53 | first_name=first_name, 54 | last_name=last_name 55 | ) 56 | user.save() 57 | if spotify_user.get('birthdate'): 58 | birthday = datetime.datetime.strptime(spotify_user.get('birthdate'), '%Y-%M-%d') 59 | else: 60 | birthday = None 61 | gender = spotify_user.get('gender') if spotify_user.get('gender') else None 62 | location = spotify_user.get('location') if spotify_user.get('location') else None 63 | user_profile = UserProfile( 64 | user=user, 65 | birthday = birthday, 66 | gender = gender, 67 | spotify_id = spotify_user.get('id'), 68 | location = location, 69 | access_token = spotify_auth_token 70 | ) 71 | user_profile.save() 72 | return user, SpotifyBackend 73 | 74 | 75 | 76 | def get_user(self, user_id): 77 | try: 78 | return User.objects.get(username=user_id) 79 | except User.DoesNotExist: 80 | return None 81 | 82 | def get_auth_token(self, request): 83 | header = get_authorization_header(request) 84 | if not header: 85 | raise exceptions.AuthenticationFailed('No auth header. Use Bearer') 86 | auth_header, token = header.split() 87 | if auth_header != 'Bearer': 88 | raise exceptions.AuthenticationFailed('Invalid auth header. Use Bearer') 89 | if not token: 90 | raise exceptions.AuthenticationFailed('No token passed') 91 | return token 92 | -------------------------------------------------------------------------------- /user_profile/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/user_profile/__init__.py -------------------------------------------------------------------------------- /user_profile/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from models import UserProfile 3 | 4 | # Register your models here. 5 | class UserProfileAdmin(admin.ModelAdmin): 6 | pass 7 | 8 | admin.site.register(UserProfile, UserProfileAdmin) -------------------------------------------------------------------------------- /user_profile/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class UserProfileConfig(AppConfig): 7 | name = 'user_profile' 8 | -------------------------------------------------------------------------------- /user_profile/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-02 22:09 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='UserProfile', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('email', models.EmailField(max_length=254)), 24 | ('birthday', models.DateField()), 25 | ('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female')], default='M', max_length=1)), 26 | ('first_name', models.CharField(max_length=30)), 27 | ('last_name', models.CharField(max_length=40)), 28 | ('spotify_id', models.CharField(max_length=40)), 29 | ('location', models.CharField(max_length=200)), 30 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 31 | ], 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /user_profile/migrations/0002_userprofile_access_token.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-02 22:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='access_token', 18 | field=models.CharField(default=None, max_length=200), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /user_profile/migrations/0003_auto_20161002_2215.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-02 22:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0002_userprofile_access_token'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='access_token', 18 | field=models.CharField(default='', max_length=200), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_profile/migrations/0004_userprofile_refresh_token.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-02 22:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0003_auto_20161002_2215'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='refresh_token', 18 | field=models.CharField(default='', max_length=200), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_profile/migrations/0005_auto_20161002_2316.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-02 23:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0004_userprofile_refresh_token'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='access_token', 18 | field=models.CharField(default='', max_length=500), 19 | ), 20 | migrations.AlterField( 21 | model_name='userprofile', 22 | name='refresh_token', 23 | field=models.CharField(default='', max_length=500), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_profile/migrations/0006_remove_userprofile_email.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-12 03:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0005_auto_20161002_2316'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='userprofile', 17 | name='email', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /user_profile/migrations/0007_auto_20161015_1717.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-15 17:17 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0006_remove_userprofile_email'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='userprofile', 17 | name='first_name', 18 | ), 19 | migrations.RemoveField( 20 | model_name='userprofile', 21 | name='last_name', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /user_profile/migrations/0008_auto_20161017_0401.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-17 04:01 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0007_auto_20161015_1717'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='birthday', 18 | field=models.DateField(null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_profile/migrations/0009_auto_20161017_0404.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-17 04:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0008_auto_20161017_0401'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='gender', 18 | field=models.CharField(choices=[('M', 'Male'), ('F', 'Female')], default='M', max_length=1, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_profile/migrations/0010_auto_20161017_0406.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-17 04:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0009_auto_20161017_0404'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='gender', 18 | field=models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], default='M', max_length=1), 19 | ), 20 | migrations.AlterField( 21 | model_name='userprofile', 22 | name='location', 23 | field=models.CharField(blank=True, max_length=200), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_profile/migrations/0011_auto_20161017_0407.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-17 04:07 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0010_auto_20161017_0406'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='gender', 18 | field=models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], default='M', max_length=1, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_profile/migrations/0012_auto_20161017_0407.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-17 04:07 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0011_auto_20161017_0407'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='location', 18 | field=models.CharField(blank=True, max_length=200, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_profile/migrations/0013_auto_20161120_0448.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-11-20 04:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0012_auto_20161017_0407'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='profile_background_image', 18 | field=models.ImageField(null=True, upload_to='user_uploads'), 19 | ), 20 | migrations.AddField( 21 | model_name='userprofile', 22 | name='profile_image', 23 | field=models.ImageField(null=True, upload_to='user_uploads'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_profile/migrations/0014_auto_20161120_0523.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-11-20 05:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_profile', '0013_auto_20161120_0448'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='userprofile', 17 | old_name='profile_background_image', 18 | new_name='profile_background_picture', 19 | ), 20 | migrations.RenameField( 21 | model_name='userprofile', 22 | old_name='profile_image', 23 | new_name='profile_picture', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_profile/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itmaster921/moodu_backend/aaa76f5cdac038f96b342d3fde88e9fad4b1fae6/user_profile/migrations/__init__.py -------------------------------------------------------------------------------- /user_profile/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | from django.conf import settings 5 | 6 | MALE = 'M' 7 | FEMALE = 'F' 8 | GENDER_CHOICES = ( 9 | (MALE, 'Male'), 10 | (FEMALE, 'Female') 11 | ) 12 | 13 | class UserProfile(models.Model): 14 | 15 | def __str__(self): 16 | return '%s %s' % (self.user.first_name, self.user.last_name) 17 | 18 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 19 | birthday = models.DateField(null=True) 20 | gender = models.CharField(choices=GENDER_CHOICES, max_length=1, default=MALE, blank=True, null=True) 21 | spotify_id = models.CharField(max_length=40) 22 | location = models.CharField(max_length=200, blank=True, null=True) 23 | access_token = models.CharField(max_length=500, default='') 24 | refresh_token = models.CharField(max_length=500, default='') 25 | profile_picture = models.ImageField(upload_to='user_uploads', null=True) 26 | profile_background_picture = models.ImageField(upload_to='user_uploads', null=True) 27 | -------------------------------------------------------------------------------- /user_profile/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User, Group 2 | from rest_framework import serializers 3 | from .models import UserProfile 4 | 5 | class UserSerializer(serializers.ModelSerializer): 6 | 7 | class Meta: 8 | model = User 9 | fields = ('username', 'first_name', 'last_name') 10 | 11 | 12 | class GroupSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = Group 15 | fields = ('name') 16 | 17 | class UserProfileSerializer(serializers.ModelSerializer): 18 | user = UserSerializer() 19 | 20 | class Meta: 21 | model = UserProfile 22 | depth = 1 23 | exclude = ('access_token', ) 24 | 25 | -------------------------------------------------------------------------------- /user_profile/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /user_profile/views.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.conf import settings 4 | from django.contrib.auth import authenticate, login 5 | from django.contrib.auth.models import User, Group 6 | from django.contrib.auth.decorators import login_required 7 | from django.http import HttpResponse 8 | 9 | from rest_framework.renderers import JSONRenderer 10 | from rest_framework import viewsets, status 11 | from rest_framework.response import Response 12 | from rest_framework.decorators import api_view 13 | 14 | from user_profile.serializers import UserSerializer, GroupSerializer, UserProfileSerializer 15 | from user_profile.models import UserProfile 16 | from user_profile.SpotifyBackend import SpotifyBackend 17 | 18 | from friendship.models import Follow 19 | 20 | 21 | class UserViewSet(viewsets.ModelViewSet): 22 | """ 23 | API endpoint that allows users to be viewed or edited. 24 | """ 25 | queryset = User.objects.all().order_by('-date_joined') 26 | serializer_class = UserSerializer 27 | 28 | 29 | class GroupViewSet(viewsets.ModelViewSet): 30 | """ 31 | API endpoint that allows groups to be viewed or edited. 32 | """ 33 | queryset = Group.objects.all() 34 | serializer_class = GroupSerializer 35 | 36 | class UserProfileViewSet(viewsets.ModelViewSet): 37 | """ 38 | API endpoint that allows userprofiles to be viewed or edited. 39 | """ 40 | queryset = UserProfile.objects.all() 41 | serializer_class = UserProfileSerializer 42 | 43 | @api_view(['POST']) 44 | def create_user(request): 45 | """Creates a user in the database""" 46 | data = request.data 47 | user_obj = request.user 48 | user_obj.first_name = data.get('firstName') 49 | user_obj.last_name = data.get('lastName') 50 | user_obj.save() 51 | user_profile = user_obj.userprofile 52 | user_profile.email = data.get('email') 53 | birthday = datetime.datetime.strptime(data.get('birthday'), '%Y-%M-%d') 54 | user_profile.birthday = birthday 55 | user_profile.gender = data.get('gender') 56 | user_profile.location = data.get('location') 57 | user_profile = user_profile.save() 58 | # Serialize user and user profile. 59 | res = get_user_helper(user_obj) 60 | res.status = status.HTTP_201_CREATED 61 | return res 62 | 63 | @api_view(['POST']) 64 | def update_user(request): 65 | """Updates a user in the db""" 66 | data = request.data 67 | user_obj = request.user 68 | user_obj.first_name = data.get('firstName') 69 | user_obj.last_name = data.get('lastName') 70 | user_obj.save() 71 | user_profile = user_obj.userprofile 72 | user_profile.email = data.get('email') 73 | birthday = datetime.datetime.strptime(data.get('birthday'), '%Y-%m-%d') 74 | user_profile.birthday = birthday 75 | user_profile.gender = data.get('gender') 76 | user_profile.location = data.get('location') 77 | user_profile = user_profile.save() 78 | # Serialize user and user profile. 79 | return get_user_helper(user_obj) 80 | 81 | def get_user_helper(user): 82 | """Returns information about a user so we can serialize it easily""" 83 | data = {} 84 | data['gender'] = user.userprofile.gender 85 | data['birthday'] = user.userprofile.birthday.strftime('%Y-%m-%d') 86 | data['spotify_id'] = user.userprofile.spotify_id 87 | data['location'] = user.userprofile.location 88 | data['first_name'] = user.first_name 89 | data['last_name'] = user.last_name 90 | data['email'] = user.email 91 | data['username'] = user.username 92 | try: 93 | data['profile_picture'] = user.userprofile.profile_picture.url 94 | except ValueError: 95 | data['profile_picture'] = '' 96 | try: 97 | data['profile_background_picture'] = user.userprofile.profile_background_picture.url 98 | except ValueError: 99 | data['profile_background_picture'] = '' 100 | data['followers'] = len(Follow.objects.followers(user)) 101 | data['following'] = len(Follow.objects.following(user)) 102 | return Response(data, status=status.HTTP_200_OK) 103 | 104 | @api_view(['GET']) 105 | def user(request, spotify_id): 106 | if request.method == 'GET': 107 | my_user = User.objects.get(userprofile__spotify_id=spotify_id) 108 | return get_user_helper(my_user) 109 | return Response({'error': 'Cannot find that user'}, status=status.HTTP_404_NOT_FOUND) 110 | 111 | @api_view(['GET']) 112 | def user_autocomplete(request): 113 | """Very basic function that expects a 'q' url param and returns a list of users whose first name 114 | starts with whatever is in q. This is case insensitive 115 | """ 116 | query = request.GET.get('q') 117 | print "in user_autocomplete. Query is: ", query 118 | if query: 119 | query_set = UserProfile.objects.filter(user__first_name__istartswith=query) 120 | # import pdb; pdb.set_trace() 121 | return Response(UserProfileSerializer(query_set, many=True, context={'request': request}).data, status=status.HTTP_200_OK) 122 | return Response({'error': 'Cannot find any users whose first name start with that query'}, 123 | status=status.HTTP_404_NOT_FOUND) 124 | 125 | @api_view(['POST']) 126 | def save_profile_picture(request): 127 | """Saves a user's profile picture""" 128 | if request.user: 129 | profile = request.user.userprofile 130 | picture = request.FILES['file'] 131 | profile.profile_picture = picture 132 | profile.save() 133 | return Response(profile.profile_picture.url, status=status.HTTP_201_CREATED) 134 | return Response({'error': 'Cannot find that user'}, status=status.HTTP_404_NOT_FOUND) 135 | 136 | @api_view(['POST']) 137 | def save_profile_background_picture(request): 138 | """Save's a user's profile background picture""" 139 | if request.user: 140 | profile = request.user.userprofile 141 | picture = request.FILES['file'] 142 | profile.profile_background_picture = picture 143 | profile.save() 144 | return Response(profile.profile_background_picture.url, status=status.HTTP_201_CREATED) 145 | return Response({'error': 'Cannot find that user'}, status=status.HTTP_404_NOT_FOUND) --------------------------------------------------------------------------------