Skip to content

Commit

Permalink
feat(Profile): Create user profile
Browse files Browse the repository at this point in the history
- Ensure that user profile is created for a user at point of registration
- Ensure users of the application can view user profiles
- Ensure that only the owner of the profile can edit it

[Finishes #164069222]
  • Loading branch information
wasibani roy committed Mar 11, 2019
1 parent 8bbf8f9 commit 9455809
Show file tree
Hide file tree
Showing 24 changed files with 549 additions and 14 deletions.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
web: gunicorn authors.wsgi
release: python manage.py migrate --noinput
6 changes: 5 additions & 1 deletion authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import re

from django.contrib.auth import authenticate

from rest_framework import serializers

from .models import User
from validate_email import validate_email

from .validators import validate_email


class RegistrationSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -119,6 +122,7 @@ def validate(self, data):
raise serializers.ValidationError(
'Your email is not verified,please click the link in your mailbox')


# The `validate` method should return a dictionary of validated data.
# This is the data that is passed to the `create` and `update` methods
# that we will see later on.
Expand Down
81 changes: 80 additions & 1 deletion authors/apps/authentication/tests/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,87 @@

invalid_password = {
"user": {
"username": "testuser",
"username": "testuser12",
"email": "testuser@gmail.com",
"password": "testingui"
}
}

same_email = {
"user": {
"username": "roy12",
"email": "bagendadeogracious@gmail.com",
"password": "Password123"
}
}

same_username = {
"user": {
"username": "Bagzie12",
"email": "roywaisibani@gmail.com",
"password": "Password123"
}
}

responses = {
'test_login_with_invalid_user_fails':{
"errors": {
"error": [
"A user with this email and password was not found."
]
}
},

'invalid_username':{
"errors":{
"username": [
"Username cannot contain special characters."
]
}
},

'invalid_email':{
"errors":{
"email": [
"Enter a valid email address."
]
}
},


'test_login_with_missing_email_fails': {
"errors": {
"error": [
"A user with this email and password was not found."
]
}
},
'email_already_exists':{
"errors": {
"email": [
"user with this email already exists."
]
}
},
'password_is_too_short': {
"errors": {
"password": [
"Password should be atleast 8 characters"
]
}
},
'password_is_weak': {
"errors": {
"password": [
"Password should at least contain a number, capital and small letter."
]
}
},
'username_already_exists': {
"errors": {
"username": [
"user with this username already exists."
]
}
}
}
13 changes: 9 additions & 4 deletions authors/apps/authentication/tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ class UserLoginTest(BaseTest):

def test_verified_user_can_login(self):
"""Tests if a user with valid credentials can login."""
register_response = self.client.post(
self.registration_url, valid_user, format='json')
self.client.get(self.verify_url+"?token=" +
register_response.data['token'], format='json')
reg_data = self.client.post(self.registration_url, valid_user, format='json')
self.client.get(self.verify_url + "?token=" + reg_data.data["token"], format='json')
response = self.client.post(self.login_url, valid_login, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

Expand Down Expand Up @@ -59,3 +57,10 @@ def test_missing_email_value(self):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data['errors']
['email'][0], "This field is required.")
def test_login_inactive_email(self):
""" Tests to see if user can login with an inactive email """
self.client.post(self.registration_url, valid_user, format='json')
response = self.client.post(self.login_url, valid_login, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data['errors']['error'][0], "Your email is not verified,please click the link in your mailbox")

45 changes: 45 additions & 0 deletions authors/apps/authentication/tests/test_registration_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import json
import os

from django.test import TestCase
from django.urls import reverse

from .test_base import BaseTest
from .test_data import (valid_user, empty_username,
invalid_user_email, short_password, missing_username_key,
invalid_username, invalid_password, empty_email, empty_password,
expired_token, invalid_token,
missing_username_key, responses, same_email,
same_username)

class RegistrationValidationTest(BaseTest):

def test_invalid_short_password(self):

expected_response = responses['password_is_too_short']
resp = self.client.post(
self.registration_url, short_password, format='json')
self.assertDictEqual(resp.data, expected_response)

def test_signup_existing_email(self):

expected_response = responses['email_already_exists']
self.client.post(
self.registration_url, valid_user, format='json')
resp = self.client.post(
self.registration_url, same_email, format='json')
self.assertDictEqual(resp.data, expected_response)

def test_signup_existing_username(self):
expected_response = responses['username_already_exists']
self.client.post(
self.registration_url, valid_user, format='json')
resp = self.client.post(
self.registration_url, same_username, format='json')
self.assertDictEqual(resp.data, expected_response)

def test_signup_invalid_email(self):
expected_response = responses['invalid_email']
resp = self.client.post(
self.registration_url, invalid_user_email, format='json')
self.assertDictEqual(resp.data, expected_response)
56 changes: 56 additions & 0 deletions authors/apps/authentication/tests/test_user_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json
import os

from django.test import TestCase
from django.urls import reverse

from ..models import User


class TestUserModel(TestCase):
def setUp(self):
self.username = 'sample12'
self.email = 'sample@gmail.com'
self.password = 'password'

def test_user_creation(self):
response = User.objects.create_user(username=self.username,
email=self.email,
password=self.password)
self.assertEqual(response.get_full_name, 'sample12')

def test_super_user_creation(self):
response = User.objects.create_superuser(username=self.username,
email=self.email,
password=self.password)
self.assertEqual(response.get_full_name, 'sample12')

def test_super_user_missing_password(self):
with self.assertRaises(TypeError):
response = User.objects.create_superuser(username=self.username,
email=self.email,
password=None)

def test_missing_email(self):
with self.assertRaises(TypeError):
response = User.objects.create_user(username=self.username,
email=None,
password=self.password)

def test_missing_username(self):
with self.assertRaises(TypeError):
response = User.objects.create_user(username=None,
email=self.email,
password=self.password)

def test_return_short_name(self):
self.response = User.objects.create_user(username=self.username,
email=self.email,
password=self.password)
self.assertEqual(self.response.get_short_name(), 'sample12')

def test_str_return(self):
self.response = User.objects.create_user(username=self.username,
email=self.email,
password=self.password)
self.assertEqual(self.response.__str__(), 'sample@gmail.com')
3 changes: 1 addition & 2 deletions authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from django.urls import path

from .views import (
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView,
EmailVerifyAPIView)
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView, EmailVerifyAPIView)
urlpatterns = [
path('user/', UserRetrieveUpdateAPIView.as_view(), name='viewusers'),
path('users/', RegistrationAPIView.as_view(), name='registration'),
Expand Down
15 changes: 15 additions & 0 deletions authors/apps/authentication/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import re

from django.shortcuts import get_object_or_404

from rest_framework import serializers

from authors.apps.authentication.models import User

def validate_email(email):
check_email = User.objects.filter(email=email)
if not re.search(r'^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$', email):
raise serializers.ValidationError("Incorrect email format please try again")
if check_email.exists():
raise serializers.ValidationError("This email has already been used to create a user")
return email
12 changes: 6 additions & 6 deletions authors/apps/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import jwt
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from rest_framework import status, generics
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response

from .models import User
from .renderers import UserJSONRenderer
from .serializers import (
LoginSerializer, RegistrationSerializer, UserSerializer
)
from django.contrib.sites.shortcuts import get_current_site
from .models import User
from django.conf import settings
from django.core.mail import EmailMessage
import jwt



class RegistrationAPIView(generics.GenericAPIView):
Expand Down Expand Up @@ -88,7 +89,6 @@ def update(self, request, *args, **kwargs):


class EmailVerifyAPIView(generics.GenericAPIView):

def get(self, request):
token = request.GET.get('token')
try:
Expand Down
3 changes: 3 additions & 0 deletions authors/apps/profiles/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
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'
31 changes: 31 additions & 0 deletions authors/apps/profiles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 2.1.5 on 2019-03-06 06:58

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='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('display_name', models.CharField(blank=True, max_length=120, null=True)),
('bio', models.TextField(blank=True, null=True)),
('image', models.URLField(null=True)),
('following', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('followers', models.ManyToManyField(blank=True, related_name='is_following', to=settings.AUTH_USER_MODEL)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file.
40 changes: 40 additions & 0 deletions authors/apps/profiles/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django.db import models
from django.db.models.signals import post_save
from authors.apps.authentication.models import User


class Profile(models.Model):
"""
Creates the profile model that will hold user profiles
"""

user = models.OneToOneField(User, on_delete=models.CASCADE)
display_name = models.CharField(max_length=120, blank=True, null=True)
followers = models.ManyToManyField(
User, related_name='is_following', blank=True)
bio = models.TextField(blank=True, null=True)
image = models.URLField(null=True)
following = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"{self.user.username}"

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


def user_post_save_receiver(instance, created, *args, **kwargs):
"""
Handle creating the profile when a user finishes
the signup process
"""
if created:
Profile.objects.get_or_create(
user=instance,
)


post_save.connect(user_post_save_receiver, sender=User)
11 changes: 11 additions & 0 deletions authors/apps/profiles/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from rest_framework.permissions import BasePermission, SAFE_METHODS


class IsOwnerOrReadOnly(BasePermission):
message = 'You are not the owner of this profile.'

def has_object_permission(self, request, view, obj):

if request.method in SAFE_METHODS:
return True
return obj.user == request.user
Loading

0 comments on commit 9455809

Please sign in to comment.