mirror of
https://github.com/fergalmoran/dss.api.git
synced 2025-12-22 09:18:13 +00:00
Merge conflicts
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,3 +34,4 @@ reload
|
||||
reset
|
||||
__krud/
|
||||
celerybeat-schedule
|
||||
private/
|
||||
199
api/auth.py
199
api/auth.py
@@ -1,116 +1,165 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
from calendar import timegm
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
import requests
|
||||
from allauth.socialaccount import models as aamodels
|
||||
from requests_oauthlib import OAuth1
|
||||
from rest_framework import parsers, renderers
|
||||
from rest_framework import status
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import parsers, renderers
|
||||
from rest_framework_jwt.settings import api_settings
|
||||
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
|
||||
from social.apps.django_app.utils import psa
|
||||
|
||||
from dss import settings
|
||||
from spa.models import UserProfile
|
||||
from spa.models.socialaccountlink import SocialAccountLink
|
||||
|
||||
|
||||
@psa()
|
||||
def auth_by_token(request, backend, auth_token):
|
||||
"""Decorator that creates/authenticates a user with an access_token"""
|
||||
|
||||
user = request.backend.do_auth(
|
||||
access_token=auth_token
|
||||
)
|
||||
if user:
|
||||
return user
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_access_token(request, backend):
|
||||
def _temp_reverse_user(uid, provider, access_token, access_token_secret, payload):
|
||||
"""
|
||||
Tries to get the access token from an OAuth Provider
|
||||
:param request:
|
||||
:param backend:
|
||||
:return:
|
||||
Do some magic here to find user account and deprecate psa
|
||||
1. Look for account in
|
||||
"""
|
||||
access_token_url = ''
|
||||
secret = ''
|
||||
|
||||
if backend == 'facebook':
|
||||
access_token_url = 'https://graph.facebook.com/oauth/access_token'
|
||||
secret = settings.SOCIAL_AUTH_FACEBOOK_SECRET
|
||||
if backend == 'twitter':
|
||||
access_token_url = 'https://api.twitter.com/oauth/request_token'
|
||||
secret = settings.SOCIAL_AUTH_TWITTER_SECRET
|
||||
|
||||
params = {
|
||||
'client_id': request.data.get('clientId'),
|
||||
'redirect_uri': request.data.get('redirectUri'),
|
||||
'client_secret': secret,
|
||||
'code': request.data.get('code')
|
||||
}
|
||||
|
||||
# Step 1. Exchange authorization code for access token.
|
||||
r = requests.get(access_token_url, params=params)
|
||||
user = None
|
||||
try:
|
||||
access_token = dict(parse_qsl(r.text))['access_token']
|
||||
except KeyError:
|
||||
access_token = 'FAILED'
|
||||
return access_token
|
||||
sa = SocialAccountLink.objects.get(social_id=uid)
|
||||
sa.type = provider
|
||||
sa.social_id = uid
|
||||
sa.access_token = access_token
|
||||
sa.access_token_secret = access_token_secret
|
||||
sa.provider_data = payload
|
||||
sa.save()
|
||||
user = UserProfile.objects.get(id=sa.user.id)
|
||||
except SocialAccountLink.DoesNotExist:
|
||||
# try allauth
|
||||
try:
|
||||
aa = aamodels.SocialAccount.objects.get(uid=uid)
|
||||
try:
|
||||
user = UserProfile.objects.get(user__id=aa.user_id)
|
||||
except UserProfile.DoesNotExist:
|
||||
print('Need to create UserProfile')
|
||||
# we got an allauth, create the SocialAccountLink
|
||||
sa = SocialAccountLink()
|
||||
sa.user = user
|
||||
sa.social_id = aa.uid
|
||||
sa.type = aa.provider
|
||||
sa.access_token = access_token
|
||||
sa.access_token_secret = access_token_secret
|
||||
sa.provider_data = payload
|
||||
sa.save()
|
||||
except aamodels.SocialAccount.DoesNotExist:
|
||||
print('Need to create social model')
|
||||
|
||||
return user if user else None
|
||||
|
||||
|
||||
class SocialLoginHandler(APIView):
|
||||
"""View to authenticate users through social media."""
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def get(self, request, format=None):
|
||||
pass
|
||||
def post(self, request):
|
||||
uid = None
|
||||
backend = request.query_params.get('backend')
|
||||
user = None
|
||||
if backend in ['twitter']:
|
||||
request_token_url = 'https://api.twitter.com/oauth/request_token'
|
||||
access_token_url = 'https://api.twitter.com/oauth/access_token'
|
||||
access_token = ""
|
||||
access_token_secret = ""
|
||||
if request.data.get('oauth_token') and request.data.get('oauth_verifier'):
|
||||
auth = OAuth1(settings.SOCIAL_AUTH_TWITTER_KEY,
|
||||
client_secret=settings.SOCIAL_AUTH_TWITTER_SECRET,
|
||||
resource_owner_key=request.data.get('oauth_token'),
|
||||
verifier=request.data.get('oauth_verifier'))
|
||||
r = requests.post(access_token_url, auth=auth)
|
||||
profile = dict(parse_qsl(r.text))
|
||||
payload = json.dumps(profile)
|
||||
uid = profile.get('user_id')
|
||||
access_token = profile.get('oauth_token')
|
||||
access_token_secret = profile.get('oauth_token_secret')
|
||||
user = _temp_reverse_user(uid, 'twitter', access_token, access_token_secret, payload)
|
||||
else:
|
||||
oauth = OAuth1(settings.SOCIAL_AUTH_TWITTER_KEY,
|
||||
client_secret=settings.SOCIAL_AUTH_TWITTER_SECRET,
|
||||
callback_uri=settings.TWITTER_CALLBACK_URL)
|
||||
r = requests.post(request_token_url, auth=oauth)
|
||||
access_token = dict(parse_qsl(r.text))
|
||||
return Response(access_token)
|
||||
|
||||
def post(self, request, format=None):
|
||||
backend = request.query_params.get(u'backend', None)
|
||||
auth_token = get_access_token(request, backend)
|
||||
if auth_token and backend:
|
||||
try:
|
||||
# Try to authenticate the user using python-social-auth
|
||||
user = auth_by_token(request, backend, auth_token)
|
||||
except Exception:
|
||||
return Response({'status': 'Bad request',
|
||||
'message': 'Could not authenticate with the provided token.'},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
if user:
|
||||
if not user.is_active:
|
||||
return Response({'status': 'Unauthorized',
|
||||
'message': 'The user account is disabled.'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
elif backend in ['facebook']:
|
||||
access_token_url = 'https://graph.facebook.com/v2.3/oauth/access_token'
|
||||
graph_api_url = 'https://graph.facebook.com/v2.3/me'
|
||||
access_token = ""
|
||||
access_token_secret = ""
|
||||
params = {
|
||||
'client_id': request.data.get('clientId'),
|
||||
'redirect_uri': request.data.get('redirectUri'),
|
||||
'client_secret': settings.SOCIAL_AUTH_FACEBOOK_SECRET,
|
||||
'code': request.data.get('code')
|
||||
}
|
||||
|
||||
# This is the part that differs from the normal python-social-auth implementation.
|
||||
# Return the JWT instead.
|
||||
# Step 1. Exchange authorization code for access token.
|
||||
r = requests.get(access_token_url, params=params)
|
||||
token = json.loads(r.text)
|
||||
|
||||
# Get the JWT payload for the user.
|
||||
payload = jwt_payload_handler(user)
|
||||
# Step 2. Retrieve information about the current user.
|
||||
r = requests.get(graph_api_url, params=token)
|
||||
profile = json.loads(r.text)
|
||||
access_token = token.get('access_token')
|
||||
uid = profile.get('id')
|
||||
user = _temp_reverse_user(uid, 'facebook', access_token, access_token_secret, r.text)
|
||||
elif backend in ['google']:
|
||||
access_token_url = 'https://accounts.google.com/o/oauth2/token'
|
||||
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
|
||||
access_token = ""
|
||||
access_token_secret = ""
|
||||
payload = dict(client_id=request.data.get('clientId'),
|
||||
redirect_uri=request.data.get('redirectUri'),
|
||||
client_secret=settings.SOCIAL_AUTH_GOOGLE_OAUTH_SECRET,
|
||||
code=request.data.get('code'),
|
||||
grant_type='authorization_code')
|
||||
|
||||
# Include original issued at time for a brand new token,
|
||||
# to allow token refresh
|
||||
# Step 1. Exchange authorization code for access token.
|
||||
r = requests.post(access_token_url, data=payload)
|
||||
token = json.loads(r.text)
|
||||
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
|
||||
|
||||
# Step 2. Retrieve information about the current user.
|
||||
r = requests.get(people_api_url, headers=headers)
|
||||
profile = json.loads(r.text)
|
||||
uid = profile.get('sub')
|
||||
user = _temp_reverse_user(uid, 'google', access_token, access_token_secret, r.text)
|
||||
|
||||
if uid is not None and user is not None:
|
||||
if not user.user.is_active:
|
||||
return Response({
|
||||
'status': 'Unauthorized',
|
||||
'message': 'User account disabled'
|
||||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
payload = jwt_payload_handler(user.user)
|
||||
if api_settings.JWT_ALLOW_REFRESH:
|
||||
payload['orig_iat'] = timegm(
|
||||
datetime.datetime.utcnow().utctimetuple()
|
||||
)
|
||||
|
||||
# Create the response object with the JWT payload.
|
||||
response_data = {
|
||||
'token': jwt_encode_handler(payload)
|
||||
'token': jwt_encode_handler(payload),
|
||||
'session': user.get_session_id()
|
||||
}
|
||||
|
||||
return Response(response_data)
|
||||
else:
|
||||
return Response({'status': 'Bad request',
|
||||
'message': 'Authentication could not be performed with received data.'},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response({
|
||||
'status': 'Bad request',
|
||||
'message': 'Authentication could not be performed with received data.'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class ObtainUser(APIView):
|
||||
|
||||
@@ -59,8 +59,8 @@ urlpatterns = patterns(
|
||||
url(r'_search/$', views.SearchResultsView.as_view()),
|
||||
url(r'^', include(router.urls)),
|
||||
|
||||
url(r'^_login', SocialLoginHandler.as_view()),
|
||||
url(r'^_a', SocialLoginHandler.as_view()),
|
||||
url(r'^_login/?$', SocialLoginHandler.as_view()),
|
||||
url(r'^_a?$', SocialLoginHandler.as_view()),
|
||||
url(r'^token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token'),
|
||||
|
||||
url(r'^__u/checkslug', helpers.UserSlugCheckHelper.as_view()),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import os
|
||||
import ast
|
||||
|
||||
print("Importing local settings")
|
||||
|
||||
DEBUG = ast.literal_eval(os.environ.get('IS_DEBUG', 'True'))
|
||||
|
||||
DSS_TEMP_PATH = os.environ.get('DSS_TEMP_PATH', '/tmp/')
|
||||
@@ -40,11 +38,16 @@ RADIO_PORT = os.environ.get('RADIO_PORT', 8888)
|
||||
|
||||
MANDRILL_API_KEY = os.environ.get('MANDRILL_API_KEY', '')
|
||||
|
||||
FACEBOOK_API_VERSION = os.environ.get('FACEBOOK_API_VERSION', '2.5')
|
||||
GOOGLE_CREDENTIALS = os.environ.get('GOOGLE_CREDENTIALS',
|
||||
'/home/fergalm/Dropbox/development/deepsouthsounds.com/dss.api/googleapikey.json')
|
||||
SOCIAL_AUTH_FACEBOOK_KEY = os.environ.get('SOCIAL_AUTH_FACEBOOK_KEY', '')
|
||||
SOCIAL_AUTH_FACEBOOK_SECRET = os.environ.get('SOCIAL_AUTH_FACEBOOK_SECRET', '')
|
||||
|
||||
SOCIAL_AUTH_TWITTER_KEY = os.environ.get('SOCIAL_AUTH_TWITTER_KEY', '')
|
||||
SOCIAL_AUTH_TWITTER_SECRET = os.environ.get('SOCIAL_AUTH_TWITTER_SECRET', '')
|
||||
TWITTER_CALLBACK_URL = os.environ.get('TWITTER_CALLBACK_URL',
|
||||
'http://ext-test.deepsouthsounds.com/_login/?backend=twitter')
|
||||
|
||||
SOCIAL_AUTH_GOOGLE_OAUTH_KEY = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH_KEY', '')
|
||||
SOCIAL_AUTH_GOOGLE_OAUTH_SECRET = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH_SECRET', '')
|
||||
|
||||
@@ -6,18 +6,21 @@ from django.core.urlresolvers import reverse_lazy
|
||||
from django.conf import global_settings
|
||||
from dss import storagesettings
|
||||
from utils import here
|
||||
from dss.localsettings import *
|
||||
|
||||
from dss.storagesettings import *
|
||||
from dss.paymentsettings import *
|
||||
from dss.logsettings import *
|
||||
from dss.pipelinesettings import *
|
||||
from dss.psa import *
|
||||
from dss.celerysettings import *
|
||||
from dss.localsettings import *
|
||||
|
||||
DEVELOPMENT = DEBUG
|
||||
|
||||
# AUTH_USER_MODEL = 'spa.UserProfile'
|
||||
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
VERSION = '2.13.04'
|
||||
VERSION = '3.0.1'
|
||||
|
||||
ADMINS = (
|
||||
('Fergal Moran', 'fergal.moran@gmail.com'),
|
||||
@@ -83,6 +86,7 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
# 'spa.middleware.auth.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from dss import localsettings
|
||||
import os
|
||||
|
||||
print("Importing storage settings")
|
||||
|
||||
AZURE_ACCOUNT_NAME = os.environ.get('CDN_NAME', 'dsscdn2')
|
||||
AZURE_CONTAINER = 'media'
|
||||
AZURE_ACCOUNT_KEY = localsettings.AZURE_ACCOUNT_KEY
|
||||
|
||||
@@ -15,6 +15,10 @@ django-grappelli==2.5.7
|
||||
django-model_utils
|
||||
redis
|
||||
git+git://github.com/llazzaro/django-scheduler.git#django-scheduler
|
||||
|
||||
git+git://github.com/pythonforfacebook/facebook-sdk.git#facebook-sdk
|
||||
google-api-python-client
|
||||
|
||||
django-celery
|
||||
django-scheduler
|
||||
django-recurrence
|
||||
@@ -24,7 +28,7 @@ sorl-thumbnail
|
||||
|
||||
git+git://github.com/disqus/django-bitfield.git#django-bitfield
|
||||
git+git://github.com/tschellenbach/Django-facebook.git#django-facebook
|
||||
git+git://github.com/omab/python-social-auth.git#egg=python-social-auth
|
||||
git+git://github.com/omab/python-social-auth.git#python-social-auth
|
||||
django-allauth
|
||||
apache-libcloud
|
||||
mandrill
|
||||
@@ -47,3 +51,5 @@ ipdb
|
||||
beautifulsoup4
|
||||
django-pipeline
|
||||
django-pipeline-forgiving
|
||||
|
||||
requests-oauthlib
|
||||
13
spa/middleware/auth.py
Normal file
13
spa/middleware/auth.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.contrib import auth
|
||||
|
||||
|
||||
class AuthenticationMiddleware(object):
|
||||
def process_request(self, request):
|
||||
user_cookie_name = "session_key"
|
||||
if user_cookie_name not in request.COOKIES:
|
||||
# log user out if you want
|
||||
return
|
||||
id = request.COOKIES.get(user_cookie_name)
|
||||
# this will find the right backend
|
||||
user = auth.authenticate(id)
|
||||
request.user = user
|
||||
@@ -23,6 +23,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='mix',
|
||||
name='is_private',
|
||||
field=models.BooleanField(default=True),
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
|
||||
28
spa/migrations/0023_socialaccountlink.py
Normal file
28
spa/migrations/0023_socialaccountlink.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('spa', '0022_auto_20151112_2017'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SocialAccountLink',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
|
||||
('object_created', models.DateTimeField(auto_now_add=True)),
|
||||
('object_updated', models.DateTimeField(db_index=True, auto_now=True)),
|
||||
('type', models.CharField(max_length=30, choices=[('twitter', 'Twitter'), ('facebook', 'Facebook'),
|
||||
('google', 'Google')])),
|
||||
('social_id', models.CharField(max_length=150)),
|
||||
('user', models.ForeignKey(to='spa.UserProfile', related_name='social_accounts')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
)
|
||||
]
|
||||
24
spa/migrations/0024_auto_20160121_2029.py
Normal file
24
spa/migrations/0024_auto_20160121_2029.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spa', '0023_socialaccountlink'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='socialaccountlink',
|
||||
name='access_token',
|
||||
field=models.CharField(max_length=500, null=True, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='socialaccountlink',
|
||||
name='access_token_secret',
|
||||
field=models.CharField(max_length=500, null=True, blank=True),
|
||||
),
|
||||
]
|
||||
19
spa/migrations/0025_socialaccountlink_provider_data.py
Normal file
19
spa/migrations/0025_socialaccountlink_provider_data.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spa', '0024_auto_20160121_2029'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='socialaccountlink',
|
||||
name='provider_data',
|
||||
field=models.CharField(blank=True, null=True, max_length=2000),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from .basemodel import BaseModel
|
||||
from .userprofile import UserProfile
|
||||
from .socialaccountlink import SocialAccountLink
|
||||
from .chatmessage import ChatMessage
|
||||
from .comment import Comment
|
||||
from .venue import Venue
|
||||
|
||||
60
spa/models/socialaccountlink.py
Normal file
60
spa/models/socialaccountlink.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
import logging
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
import facebook
|
||||
from django.core.files import File
|
||||
from django.core.files.temp import NamedTemporaryFile
|
||||
from django.db import models
|
||||
from requests_oauthlib import OAuth1Session
|
||||
|
||||
from dss import settings
|
||||
from spa.models import BaseModel
|
||||
from spa.models.userprofile import UserProfile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SocialAccountLink(BaseModel):
|
||||
ACCOUNT_TYPE = (
|
||||
('twitter', 'Twitter'),
|
||||
('facebook', 'Facebook'),
|
||||
('google', 'Google')
|
||||
)
|
||||
type = models.CharField(max_length=30, choices=ACCOUNT_TYPE)
|
||||
social_id = models.CharField(max_length=150)
|
||||
user = models.ForeignKey(UserProfile, related_name='social_accounts')
|
||||
access_token = models.CharField(max_length=500, null=True, blank=True)
|
||||
access_token_secret = models.CharField(max_length=500, null=True, blank=True)
|
||||
provider_data = models.CharField(max_length=2000, null=True, blank=True)
|
||||
|
||||
def _save_image(self, url):
|
||||
|
||||
img = NamedTemporaryFile(delete=True)
|
||||
img.write(urllib.request.urlopen(url).read())
|
||||
|
||||
img.flush()
|
||||
self.user.avatar_image.save(str(self.user.id), File(img))
|
||||
|
||||
def update_image_url(self):
|
||||
try:
|
||||
if self.type in ['twitter']:
|
||||
twitter = OAuth1Session(
|
||||
settings.SOCIAL_AUTH_TWITTER_KEY,
|
||||
client_secret=settings.SOCIAL_AUTH_TWITTER_SECRET,
|
||||
resource_owner_key=self.access_token,
|
||||
resource_owner_secret=self.access_token_secret)
|
||||
url = 'https://api.twitter.com/1.1/users/show.json?user_id={0}'.format(self.social_id)
|
||||
response = twitter.get(url)
|
||||
r = json.loads(response.text)
|
||||
self._save_image(r.get('profile_image_url'))
|
||||
elif self.type in ['facebook']:
|
||||
graph = facebook.GraphAPI(access_token=self.access_token, version=settings.FACEBOOK_API_VERSION)
|
||||
image_url = graph.get_object("me/picture?type=large")
|
||||
self._save_image(image_url.get('url'))
|
||||
elif self.type in ['google']:
|
||||
data = json.loads(self.provider_data)
|
||||
self._save_image(data.get('picture'))
|
||||
except Exception as ex:
|
||||
logger.exception(ex)
|
||||
@@ -7,11 +7,11 @@ from django.contrib.auth.models import User
|
||||
from core.realtime import activity
|
||||
|
||||
from core.utils.audio.mp3 import mp3_length
|
||||
from spa.models import SocialAccountLink
|
||||
from spa.models.activity import ActivityFollow
|
||||
from spa.models.userprofile import UserProfile
|
||||
from spa.models.mix import Mix
|
||||
|
||||
|
||||
waveform_generated_signal = Signal()
|
||||
|
||||
|
||||
@@ -136,3 +136,8 @@ def user_followers_changed(sender, **kwargs):
|
||||
ActivityFollow(user=source_user, to_user=target_user).save()
|
||||
except Exception as ex:
|
||||
print("Error sending new follower: %s" % ex)
|
||||
|
||||
@receiver(post_save, sender=SocialAccountLink, dispatch_uid='socialaccountlink_pre_save')
|
||||
def socialaccountlink_pre_save(sender, **kwargs):
|
||||
# update image url
|
||||
kwargs['instance'].update_image_url()
|
||||
|
||||
Reference in New Issue
Block a user