Skip to content

Commit

Permalink
Merge pull request #12 from andela/ft-user-profile-162949216
Browse files Browse the repository at this point in the history
#162949216 User can create and list profiles
  • Loading branch information
indungu committed Jan 21, 2019
2 parents cb62681 + 5a9e860 commit 02586c9
Show file tree
Hide file tree
Showing 20 changed files with 319 additions and 13 deletions.
2 changes: 1 addition & 1 deletion authors/apps/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 2.1.4 on 2019-01-09 17:18
# Generated by Django 2.1.4 on 2019-01-16 09:03

from django.db import migrations, models

Expand Down
2 changes: 1 addition & 1 deletion authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
app_name = 'authentication'

urlpatterns = [
path('user/', UserRetrieveUpdateAPIView.as_view()),
path('user/', UserRetrieveUpdateAPIView.as_view(), name='user_update'),
path('users/', RegistrationAPIView.as_view(), name="signup_url"),
path('users/login/', LoginAPIView.as_view(), name="login_url")
]
5 changes: 5 additions & 0 deletions authors/apps/profiles/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from .models import UserProfile

admin.site.register(UserProfile)
5 changes: 5 additions & 0 deletions authors/apps/profiles/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ProfilesConfig(AppConfig):
name = 'profiles'
29 changes: 29 additions & 0 deletions authors/apps/profiles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 2.1.4 on 2019-01-19 04:59

import cloudinary.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', cloudinary.models.CloudinaryField(default='', max_length=255, verbose_name='image')),
('bio', models.TextField(blank=True, max_length=255, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateField(auto_now=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profiles', to=settings.AUTH_USER_MODEL)),
],
),
]
34 changes: 34 additions & 0 deletions authors/apps/profiles/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.db import models
from django.db.models.signals import post_save

# Third-party imports
from cloudinary.models import CloudinaryField

# Local imports
from authors.apps.authentication.models import User
from authors.settings import AUTH_USER_MODEL


class UserProfile(models.Model):
user = models.OneToOneField(AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='profiles')
image = CloudinaryField('image', default='')
bio = models.TextField(null=True, blank=True, max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)

@property
def username(self):
return self.user.username

def __str__(self):
return str(self.user)


def create_profile_post_receiver(sender, instance, *args, **kwargs):
if kwargs['created']:
instance.user_profile = UserProfile.objects.create(user=instance)


post_save.connect(create_profile_post_receiver, sender=User)
21 changes: 21 additions & 0 deletions authors/apps/profiles/renderers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import json

from rest_framework.renderers import JSONRenderer


class ProfileJSONRenderer(JSONRenderer):
charset = 'utf-8'

def render(self, data, media_type=None, renderer_context=None):
return json.dumps({
'profile': data
})


class ProfilesJSONRenderer(JSONRenderer):
charset = 'utf-8'

def render(self, data, media_type=None, renderer_context=None):
return json.dumps({
'profiles': data
})
14 changes: 14 additions & 0 deletions authors/apps/profiles/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from rest_framework import serializers

# Local application imports
from .models import UserProfile


class ProfileSerialiazer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username', read_only=True)
image = serializers.ImageField()

class Meta:
model = UserProfile
fields = ['username', 'image', 'bio', 'created_at', 'updated_at']
read_only_fields = ('created_at', 'updated_at', 'username')
12 changes: 12 additions & 0 deletions authors/apps/profiles/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.urls import path

from .views import ProfileListView, ProfileDetail
"""
Django 2.0 requires the app_name variable set when using include namespace
"""
app_name = 'profiles'

urlpatterns = [
path('', ProfileListView.as_view(), name='all_profiles'),
path('<str:username>', ProfileDetail.as_view(), name='profile_details')
]
54 changes: 54 additions & 0 deletions authors/apps/profiles/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response

# Local application imports
from .models import UserProfile
from .serializers import ProfileSerialiazer
from .renderers import ProfileJSONRenderer, ProfilesJSONRenderer


class ProfileListView(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ProfileSerialiazer
renderer_classes = (ProfilesJSONRenderer,)

def get_queryset(self):
queryset = UserProfile.objects.all().exclude(user=self.request.user)
return queryset


class ProfileDetail(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = ProfileSerialiazer
renderer_classes = (ProfileJSONRenderer,)

def get(self, request, username):
try:
profile = UserProfile.objects.get(user__username=username)
except:
message = { "error": "Profile does not exist."}
return Response(message, status=status.HTTP_404_NOT_FOUND)

serializer = self.serializer_class(profile)
return Response(serializer.data, status=status.HTTP_200_OK)

def put(self, request, username):
profile = UserProfile.objects.get(user__username=username)
if profile.user.username != request.user.username:
# Set image to none to avoid calling cloudinary
# This will prevent heroku from interrupting the request
request.data['image'] = None
msg = {"error": "You do not have permission to edit this profile."}
return Response(msg, status=status.HTTP_403_FORBIDDEN)

data = request.data
serializer = self.serializer_class(
instance=request.user.profiles, data=data, partial=True
)
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
20 changes: 11 additions & 9 deletions authors/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@
'django.contrib.messages',
'django.contrib.staticfiles',

'cloudinary',
'corsheaders',
'django_extensions',
'rest_framework_swagger',
'rest_framework',


'authors.apps.authentication',
'authors.apps.core',
'authors.apps.profiles',

'rest_framework_swagger',
]

MIDDLEWARE = [
Expand Down Expand Up @@ -87,7 +87,7 @@
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME':os.getenv('DATABASE_URL')
'NAME': os.getenv('DATABASE_URL')
}
}

Expand Down Expand Up @@ -154,13 +154,8 @@
),
}

# Do not implement sessions at the moment since authentication
# does not have token based authentication. Once the authentication
# via JWT is implemented the settings should be changed.
# Uses basic authentication.
# Jwt configuration


#Jwt configuration
JWT_AUTH = {
'JWT_ENCODE_HANDLER':
'rest_framework_jwt.utils.jwt_encode_handler',
Expand Down Expand Up @@ -195,6 +190,13 @@

}

CLOUDINARY = {
'cloud_name': os.getenv('CLOUDINARY_NAME'),
'api_key': os.getenv('CLOUDINARY_KEY'),
'api_secret': os.getenv('CLOUDINARY_SECRET'),
'secure': True
}
APPEND_SLASH = False
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

SWAGGER_SETTINGS = {
Expand Down
Empty file added authors/tests/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ def setUp(self):
self.client = APIClient()
self.login_url = reverse('authentication:login_url')
self.user_url = reverse('authentication:signup_url')
self.update_url = reverse('authentication:user_update')

self.user_data = {
"user": {
"username": "sam",
"email": "sam@gmail.com",
"password": "A23DVFRss@"
}}

self.update_data = {
"user": {
"username": "sam2",
"email": "sam@gmail.com",
"password": "A23DVFRss@2"
}}
self.test_username = {
"user": {
"username": "sam",
Expand Down
File renamed without changes.
99 changes: 99 additions & 0 deletions authors/tests/test_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase, APIClient
import json


class TestUserProfile(APITestCase):
"""Tests for the user profile"""

def setUp(self):
"""This method is used to initialize variables used by the tests."""
self.client = APIClient()
self.user_data = {
"user": {
"username": "sam",
"email": "sam@gmail.com",
"password": "A23DVFRss@"
}}
self.user_data2 = {
"user": {
"username": "catherine",
"email": "catherine@gmail.com",
"password": "A23DVFRss@"
}}
self.update_data = {
"user": {
"bio": "catherine"
}}

def get_token_on_signup(self,):
return self.client.post(
reverse('authentication:signup_url'),
data=json.dumps(self.user_data),
content_type='application/json'
)

def authentication_token(self,):
res = self.get_token_on_signup()
token = res.data['token']
return token

def test_create_profile(self):
"""Test create profile on registration """
token = self.authentication_token()
url = reverse('profiles:profile_details', kwargs={'username': 'sam'})
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
response = self.client.get(url)
self.assertEqual(response.data['username'], 'sam')

def test_get_all_profiles(self):
"""Test for getting a all profiles"""
token = self.authentication_token()
url = reverse('profiles:all_profiles')
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
response = self.client.get(url)
self.assertEquals(response.status_code, status.HTTP_200_OK)

def test_get_profile_details(self):
"""Test for getting a single userprofile"""
token = self.authentication_token()
url = reverse('profiles:profile_details', kwargs={'username': 'sam'})
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
response = self.client.get(url)
self.assertEquals(response.status_code, status.HTTP_200_OK)

def test_profile_update(self):
"""Test profile update"""
token = self.authentication_token()
url = reverse('profiles:profile_details', kwargs={'username': 'sam'})
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
response = self.client.put(url, data=json.dumps(self.update_data),
content_type='application/json')
self.assertEquals(response.status_code, status.HTTP_200_OK)

def test_get_non_existing_profile(self):
"""Test for getting non existing profile"""
token = self.authentication_token()
url = reverse('profiles:profile_details', kwargs={'username': 'kwanj'})
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
response = self.client.get(url)
self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND)

def test_update_other_profile(self):
"""Tests for updating another person's profile"""
token = self.authentication_token()
self.client.post(
reverse('authentication:signup_url'),
data=json.dumps(self.user_data2),
content_type='application/json'
)
url = reverse('profiles:profile_details',kwargs={'username':'catherine'})
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
response = self.client.put(url, data={
"user": {
"bio": "terrible"
}
},
format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
Loading

0 comments on commit 02586c9

Please sign in to comment.