-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(profiles):users create their profiles
-write tests on profiles CRUD -create profiles model -implement creation of profiles after user creation -implement retrieving of single and all profiles -implement updating of single profiles instances -add profiles urls to url.py file -verify if tests running on CRUD profiles -include swagger api configuartions -remove typo in base settings file [Finishes #164046250]
- Loading branch information
1 parent
2d86944
commit a50604a
Showing
15 changed files
with
395 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from django.db import models | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
|
||
class TimeStampModel(models.Model): | ||
created_at = models.DateTimeField(_('created at'), auto_now_add=True) | ||
updated_at = models.DateTimeField( | ||
_('updated at'), auto_now_add=False, auto_now=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from rest_framework.exceptions import APIException | ||
|
||
|
||
class ProfileDoesNotExist(APIException): | ||
status_code = 400 | ||
default_detail = 'The requested profile does not exist.' | ||
|
||
|
||
class UserIsNotAuthenticated(APIException): | ||
status_code = 403 | ||
default_detail = 'You should be logged in to proceed.' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from django.conf import settings | ||
from django.db import models | ||
from django.db.models.signals import post_save | ||
from authors.apps.core.models import TimeStampModel | ||
from cloudinary.models import CloudinaryField | ||
from cloudinary import CloudinaryImage | ||
|
||
|
||
class Profile(TimeStampModel): | ||
user = models.OneToOneField( | ||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE) | ||
first_name = models.CharField( | ||
'first name', max_length=30, blank=True, null=True) | ||
last_name = models.CharField( | ||
'last name', max_length=30, blank=True, null=True) | ||
birth_date = models.DateField('last name', null=True, blank=True) | ||
bio = models.TextField('bio', default='', null=True, blank=True) | ||
city = models.CharField('city', blank=True, null=True, | ||
max_length=100, default='') | ||
country = models.CharField('country', blank=True, | ||
null=True, max_length=100, default='') | ||
phone = models.IntegerField('phone', blank=True, null=True, default=0) | ||
website = models.URLField('website', blank=True, null=True, default='') | ||
image = CloudinaryField( | ||
'image', default="image/upload/v1552193974/gyzbaptikqalgthxfdnh.png") # noqa | ||
|
||
def __str__(self): | ||
return self.user.username | ||
|
||
@property | ||
def get_username(self): | ||
return self.user.username | ||
|
||
def get_cloudinary_url(self): | ||
image_url = CloudinaryImage(str(self.image)).build_url( | ||
width=100, height=150, crop='fill') | ||
return image_url | ||
|
||
|
||
""" | ||
Signal receiver for 'post_save' signal sent by User model upon saving | ||
""" | ||
|
||
|
||
def create_profile(sender, **kwargs): | ||
if kwargs.get('created'): | ||
user_profile = Profile(user=kwargs.get('instance')) | ||
user_profile.save() | ||
|
||
|
||
# connect the signal to the handler function | ||
post_save.connect(create_profile, sender=settings.AUTH_USER_MODEL) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import json | ||
|
||
from rest_framework.renderers import JSONRenderer | ||
|
||
|
||
class ProfileJSONRenderer(JSONRenderer): | ||
charset = 'utf-8' | ||
|
||
def render(self, data, media_type=None, renderer_context=None): | ||
|
||
# Finally, we can render our data under the "profile" namespace. | ||
return json.dumps({ | ||
'profile': data | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from rest_framework import serializers | ||
|
||
from .models import Profile | ||
|
||
|
||
class GetProfileSerializer(serializers.ModelSerializer): | ||
""" | ||
serializers for user profile upon user registration. | ||
""" | ||
|
||
username = serializers.ReadOnlyField(source='get_username') | ||
image_url = serializers.ReadOnlyField(source='get_cloudinary_url') | ||
|
||
class Meta: | ||
model = Profile | ||
|
||
fields = ( | ||
'username', 'first_name', 'last_name', 'bio', 'image', 'image_url', | ||
'website', 'city', 'phone', 'country') | ||
|
||
read_only_fields = ("created_at", "updated_at") |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from django.test import TestCase | ||
|
||
from ..models import Profile | ||
from authors.apps.authentication.models import User | ||
|
||
|
||
class TestProfile(TestCase): | ||
""" class to test the profile models""" | ||
|
||
def setUp(self): | ||
""" Setup some code that is used by the unittests""" | ||
self.email = 'serem@gmail.com' | ||
self.username = 'testing' | ||
self.password = 'jcbsdhcvshucj!!' | ||
|
||
# create a user that will be logged in | ||
self.user = User.objects.create_user( | ||
self.username, self.email, self.password) | ||
self.profile = self.user.profile | ||
|
||
def test_string_representation(self): | ||
""" test for the value returned by __str__ """ | ||
self.assertEqual(str(self.profile), self.username) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
from django.test import TestCase, Client | ||
|
||
import json | ||
|
||
from authors.apps.authentication.models import User | ||
from authors.apps.profiles.models import Profile | ||
from authors.apps.profiles.renderers import ProfileJSONRenderer | ||
|
||
|
||
class TestProfileViews(TestCase): | ||
def setUp(self): | ||
""" Funtion to setup some code that will be needed for unittests """ | ||
self.email = 'boomboom@gmail.com' | ||
self.username = 'testing12' | ||
self.password = 'testuserpass' | ||
|
||
# create a user that will be logged in | ||
self.user = User.objects.create_user( | ||
self.username, self.email, self.password) | ||
|
||
# verify a user's account and save | ||
self.user.is_verified = True | ||
self.user.save() | ||
|
||
self.data = { | ||
'user': { | ||
'username': self.username, | ||
'email': self.email, | ||
'password': self.password, | ||
} | ||
} | ||
self.updated_data = { | ||
|
||
'username': "newname", | ||
'email': "another@email.com", | ||
'password': "otherpaswword", | ||
'bio': 'i like classical music' | ||
|
||
} | ||
self.errornious_updated_data = { | ||
"website": "notavalidurlforawebsiteman" | ||
} | ||
self.image_link = { | ||
"image":"cats.jpg" | ||
} | ||
|
||
self.test_client = Client() | ||
|
||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
|
||
def tearDown(self): | ||
pass | ||
|
||
def login_a_user(self): | ||
""" | ||
Reusable function to login a user | ||
""" | ||
|
||
response = self.test_client.post( | ||
"/api/users/login/", data=json.dumps(self.data), content_type='application/json') | ||
token = response.json()['user']['token'] | ||
return token | ||
|
||
def register_user(self, user_details_dict): | ||
""" Register anew user to the system | ||
Args: | ||
user_details_dict: a dictionary with username, email, password of the user | ||
Returns: an issued post request to the user registration endpoint | ||
""" | ||
return self.test_client.post( | ||
"/api/users/", data=json.dumps(user_details_dict), content_type='application/json') | ||
|
||
@property | ||
def token(self): | ||
return self.login_a_user() | ||
|
||
def test_retrieve_profile(self): | ||
""" test for the retrive profile endpoint """ | ||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
response = self.test_client.get( | ||
"/api/profiles/{}/".format(self.username), **headers, content_type='application/json') | ||
|
||
self.assertEqual(response.status_code, 200) | ||
|
||
def test_update_existing_profie(self): | ||
""" test for updating exisiting user profile""" | ||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
self.register_user(self.data) | ||
response = self.test_client.put( | ||
"/api/user/", **headers, content_type='application/json', data=json.dumps(self.updated_data)) | ||
|
||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(response.json()['user']['email'], "another@email.com") | ||
self.assertEqual(response.json()['user']['username'], "newname") | ||
self.assertEqual( | ||
response.json()['user']['profile']['bio'], 'i like classical music') | ||
|
||
def test_list_all_profiles(self): | ||
""" test for checking if app returns all available profiles """ | ||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
response = self.client.get( | ||
"/api/profiles/", **headers, content_type='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
|
||
def test_get_non_existing_profile(self): | ||
""" test for checking if app catches non-existing profile error """ | ||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
response = self.client.get( | ||
"/api/profiles/serdgddddadscw/", **headers, content_type='application/json') | ||
self.assertEqual(response.status_code, 400) | ||
self.assertEqual( | ||
response.json()['profile']['detail'], "The requested profile does not exist.") | ||
|
||
def test_if_renderer_catches_errors(self): | ||
""" test is an error is caught by profile json renderer """ | ||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
self.register_user(self.data) | ||
response = self.test_client.put( | ||
"/api/user/", **headers, content_type='application/json', data=json.dumps(self.errornious_updated_data)) | ||
|
||
self.assertEqual(response.json()['errors']['profile']['website'], [ | ||
"Enter a valid URL."]) | ||
|
||
def test_if_image_uploads_successfully(self): | ||
""" test if an profile image uploads successfully """ | ||
token = self.login_a_user() | ||
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token} | ||
self.register_user(self.data) | ||
response = self.test_client.put( | ||
"/api/user/", **headers, content_type='application/json', data=json.dumps(self.image_link)) | ||
|
||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(response.json()['user']['profile']['image_url'], | ||
"https://res.cloudinary.com/dbsri2qtr/image/upload/c_fill,h_150,w_100/cats") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from django.urls import path | ||
|
||
from .views import ProfileRetrieveAPIView, ProfilesListAPIView | ||
|
||
app_name = 'profiles' | ||
|
||
urlpatterns = [ | ||
path('profiles/<username>/', ProfileRetrieveAPIView.as_view()), | ||
path('profiles/', ProfilesListAPIView.as_view()), | ||
] |
Oops, something went wrong.