From 54873e15ae2c78e411f00875a81a6e066da41d24 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Tue, 15 Jan 2019 23:04:09 +0300 Subject: [PATCH 01/31] Feature (Profiles) Add profile endpoints Create profile app Add first_name, last_name, bio and profile image models create profile serializer create profile views [Delivers #162948841] --- .../migrations/0002_auto_20190115_1939.py | 30 ++++++++++++++++ .../migrations/0003_auto_20190115_1949.py | 25 +++++++++++++ authors/apps/authentication/models.py | 18 +++++++++- authors/apps/profiles/admin.py | 3 ++ authors/apps/profiles/apps.py | 5 +++ .../apps/profiles/migrations/0001_initial.py | 26 ++++++++++++++ .../migrations/0002_auto_20190115_1939.py | 20 +++++++++++ .../migrations/0021_auto_20190122_1723.py | 18 ++++++++++ authors/apps/profiles/migrations/__init__.py | 0 authors/apps/profiles/models.py | 0 authors/apps/profiles/serializers.py | 8 +++++ authors/apps/profiles/tests.py | 3 ++ authors/apps/profiles/urls.py | 9 +++++ authors/apps/profiles/views.py | 7 ++++ authors/urls.py | 2 +- requirements.txt | 36 ++++++++++++++++--- 16 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 authors/apps/authentication/migrations/0002_auto_20190115_1939.py create mode 100644 authors/apps/authentication/migrations/0003_auto_20190115_1949.py create mode 100644 authors/apps/profiles/admin.py create mode 100644 authors/apps/profiles/apps.py create mode 100644 authors/apps/profiles/migrations/0001_initial.py create mode 100644 authors/apps/profiles/migrations/0002_auto_20190115_1939.py create mode 100644 authors/apps/profiles/migrations/0021_auto_20190122_1723.py create mode 100644 authors/apps/profiles/migrations/__init__.py create mode 100644 authors/apps/profiles/models.py create mode 100644 authors/apps/profiles/serializers.py create mode 100644 authors/apps/profiles/tests.py create mode 100644 authors/apps/profiles/urls.py create mode 100644 authors/apps/profiles/views.py diff --git a/authors/apps/authentication/migrations/0002_auto_20190115_1939.py b/authors/apps/authentication/migrations/0002_auto_20190115_1939.py new file mode 100644 index 00000000..96f0426b --- /dev/null +++ b/authors/apps/authentication/migrations/0002_auto_20190115_1939.py @@ -0,0 +1,30 @@ +# Generated by Django 2.1.4 on 2019-01-15 19:39 + +import cloudinary.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='bio', + field=models.CharField(default=None, max_length=200), + preserve_default=False, + ), + migrations.AddField( + model_name='user', + name='gender', + field=models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE')], default='M', max_length=10), + ), + migrations.AddField( + model_name='user', + name='profile_image', + field=cloudinary.models.CloudinaryField(max_length=255, null=True, verbose_name='images'), + ), + ] diff --git a/authors/apps/authentication/migrations/0003_auto_20190115_1949.py b/authors/apps/authentication/migrations/0003_auto_20190115_1949.py new file mode 100644 index 00000000..855e90b3 --- /dev/null +++ b/authors/apps/authentication/migrations/0003_auto_20190115_1949.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.4 on 2019-01-15 19:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0002_auto_20190115_1939'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='first_name', + field=models.CharField(default=None, max_length=100), + preserve_default=False, + ), + migrations.AddField( + model_name='user', + name='last_name', + field=models.CharField(default=None, max_length=100), + preserve_default=False, + ), + ] diff --git a/authors/apps/authentication/models.py b/authors/apps/authentication/models.py index 6c56a450..f0fc0818 100644 --- a/authors/apps/authentication/models.py +++ b/authors/apps/authentication/models.py @@ -5,7 +5,7 @@ AbstractBaseUser, BaseUserManager, PermissionsMixin ) from django.db import models - +from cloudinary.models import CloudinaryField class UserManager(BaseUserManager): """ @@ -81,6 +81,22 @@ class User(AbstractBaseUser, PermissionsMixin): # A timestamp reprensenting when this object was last updated. updated_at = models.DateTimeField(auto_now=True) + # A char field to hold bio data + bio = models.CharField(max_length=200) + + # A choice field to hold multiple choice + gender_choices = (('M','MALE'),('F','FEMALE')) + gender = models.CharField(max_length=10, choices=gender_choices, default='M') + + # cloudinary field + profile_image = CloudinaryField('images', null=True) + + # A char field for the First name + first_name = models.CharField(max_length=100) + + # A char field for the user Last name + last_name = models.CharField(max_length=100) + # More fields required by Django when specifying a custom user model. # The `USERNAME_FIELD` property tells us which field we will use to log in. diff --git a/authors/apps/profiles/admin.py b/authors/apps/profiles/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/authors/apps/profiles/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/authors/apps/profiles/apps.py b/authors/apps/profiles/apps.py new file mode 100644 index 00000000..5501fdad --- /dev/null +++ b/authors/apps/profiles/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ProfilesConfig(AppConfig): + name = 'profiles' diff --git a/authors/apps/profiles/migrations/0001_initial.py b/authors/apps/profiles/migrations/0001_initial.py new file mode 100644 index 00000000..d7de0d78 --- /dev/null +++ b/authors/apps/profiles/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 2.1.4 on 2019-01-15 08:51 + +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 = [ + ('authentication', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('gender', models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE')], default='M', max_length=10)), + ('profile_image', cloudinary.models.CloudinaryField(max_length=255, null=True, verbose_name='images')), + ], + ), + ] diff --git a/authors/apps/profiles/migrations/0002_auto_20190115_1939.py b/authors/apps/profiles/migrations/0002_auto_20190115_1939.py new file mode 100644 index 00000000..159dcbd0 --- /dev/null +++ b/authors/apps/profiles/migrations/0002_auto_20190115_1939.py @@ -0,0 +1,20 @@ +# Generated by Django 2.1.4 on 2019-01-15 19:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='userprofile', + name='user', + ), + migrations.DeleteModel( + name='UserProfile', + ), + ] diff --git a/authors/apps/profiles/migrations/0021_auto_20190122_1723.py b/authors/apps/profiles/migrations/0021_auto_20190122_1723.py new file mode 100644 index 00000000..82950f8b --- /dev/null +++ b/authors/apps/profiles/migrations/0021_auto_20190122_1723.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-22 17:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0020_auto_20190121_1445'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='bio', + field=models.TextField(default='empty', max_length=200), + ), + ] diff --git a/authors/apps/profiles/migrations/__init__.py b/authors/apps/profiles/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py new file mode 100644 index 00000000..e69de29b diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py new file mode 100644 index 00000000..6978ae64 --- /dev/null +++ b/authors/apps/profiles/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from ..authentication.models import User + +class UserProfileSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('gender', 'bio', 'profile_image') + diff --git a/authors/apps/profiles/tests.py b/authors/apps/profiles/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/authors/apps/profiles/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py new file mode 100644 index 00000000..bac4cc82 --- /dev/null +++ b/authors/apps/profiles/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + + +from .views import ProfileView + +app_name = "profiles" +urlpatterns = [ + path('profile//', ProfileView.as_view()), +] diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py new file mode 100644 index 00000000..c844b4be --- /dev/null +++ b/authors/apps/profiles/views.py @@ -0,0 +1,7 @@ +from rest_framework import generics +from .serializers import UserProfileSerializer +from ..authentication.models import User + +class ProfileView(generics.RetrieveUpdateAPIView): + serializer_class = UserProfileSerializer + queryset = User.objects.all() diff --git a/authors/urls.py b/authors/urls.py index 60be05bd..460aca7a 100644 --- a/authors/urls.py +++ b/authors/urls.py @@ -26,7 +26,7 @@ path('api/', include('authors.apps.authentication.urls', namespace='authentication')), - path('api/', include('authors.apps.friends.urls')), + path('user/', include('authors.apps.profiles.urls', namespace='profiles')), path('api/', include('authors.apps.articles.urls', namespace='articles')), # Set the API documentation at the root of the site diff --git a/requirements.txt b/requirements.txt index 1ae0588b..a89185a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,43 @@ +astroid==2.1.0 +certifi==2018.11.29 +chardet==3.0.4 +cloudinary==1.15.0 +coreapi==2.3.3 +coreschema==0.0.4 +coverage==4.5.2 +coveralls==1.5.1 +dj-database-url==0.5.0 Django==2.1.4 django-cors-middleware==1.3.1 django-extensions==2.1.4 +django-heroku==0.3.1 django-rest-swagger==2.1.0 djangorestframework==3.9.0 +docopt==0.6.2 +gunicorn==19.9.0 +idna==2.8 +isort==4.3.4 +itypes==1.1.0 +Jinja2==2.10 +lazy-object-proxy==1.3.1 +MarkupSafe==1.1.0 +mccabe==0.6.1 +mock==2.0.0 +openapi-codec==1.3.2 +pbr==5.1.1 +psycopg2==2.7.6.1 psycopg2-binary==2.7.6.1 pycodestyle==2.4.0 -dj-database-url==0.5.0 -whitenoise==4.1.2 -gunicorn==19.9.0 -django-heroku==0.3.1 PyJWT==1.7.1 -six==1.12.0 +pylint==2.2.2 pytz==2018.9 cloudinary==1.15.0 Pillow==5.4.1 django-taggit==0.23.0 +requests==2.21.0 +simplejson==3.16.0 +six==1.12.0 +uritemplate==3.0.0 +urllib3==1.24.1 +whitenoise==4.1.2 +wrapt==1.11.0 From 5d441f2e8774e483b1c356ffae5f69af7307a99e Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 17 Jan 2019 12:16:15 +0300 Subject: [PATCH 02/31] feat (Profile) user can create a profile - Add post save and pre save signals - Add a slug - Clean up code [Delivers #162948841] --- authors/apps/authentication/admin.py | 5 ++ .../migrations/0002_auto_20190115_1939.py | 30 ----------- .../migrations/0003_auto_20190115_1949.py | 25 ---------- authors/apps/authentication/models.py | 18 +------ authors/apps/core/permissions.py | 17 +++++++ authors/apps/profiles/admin.py | 4 +- .../apps/profiles/migrations/0001_initial.py | 12 +++-- .../migrations/0002_auto_20190115_1939.py | 20 -------- .../migrations/0002_auto_20190116_1253.py | 23 +++++++++ .../migrations/0003_auto_20190116_1348.py | 19 +++++++ .../migrations/0004_userprofile_slug.py | 18 +++++++ authors/apps/profiles/models.py | 50 +++++++++++++++++++ authors/apps/profiles/serializers.py | 13 +++-- authors/apps/profiles/urls.py | 6 +-- authors/apps/profiles/views.py | 19 ++++++- authors/settings.py | 8 +-- authors/urls.py | 2 +- requirements.txt | 37 ++++---------- 18 files changed, 188 insertions(+), 138 deletions(-) create mode 100644 authors/apps/authentication/admin.py delete mode 100644 authors/apps/authentication/migrations/0002_auto_20190115_1939.py delete mode 100644 authors/apps/authentication/migrations/0003_auto_20190115_1949.py delete mode 100644 authors/apps/profiles/migrations/0002_auto_20190115_1939.py create mode 100644 authors/apps/profiles/migrations/0002_auto_20190116_1253.py create mode 100644 authors/apps/profiles/migrations/0003_auto_20190116_1348.py create mode 100644 authors/apps/profiles/migrations/0004_userprofile_slug.py diff --git a/authors/apps/authentication/admin.py b/authors/apps/authentication/admin.py new file mode 100644 index 00000000..30e6d9ab --- /dev/null +++ b/authors/apps/authentication/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +from .models import User + +admin.site.register(User) \ No newline at end of file diff --git a/authors/apps/authentication/migrations/0002_auto_20190115_1939.py b/authors/apps/authentication/migrations/0002_auto_20190115_1939.py deleted file mode 100644 index 96f0426b..00000000 --- a/authors/apps/authentication/migrations/0002_auto_20190115_1939.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.1.4 on 2019-01-15 19:39 - -import cloudinary.models -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='bio', - field=models.CharField(default=None, max_length=200), - preserve_default=False, - ), - migrations.AddField( - model_name='user', - name='gender', - field=models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE')], default='M', max_length=10), - ), - migrations.AddField( - model_name='user', - name='profile_image', - field=cloudinary.models.CloudinaryField(max_length=255, null=True, verbose_name='images'), - ), - ] diff --git a/authors/apps/authentication/migrations/0003_auto_20190115_1949.py b/authors/apps/authentication/migrations/0003_auto_20190115_1949.py deleted file mode 100644 index 855e90b3..00000000 --- a/authors/apps/authentication/migrations/0003_auto_20190115_1949.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1.4 on 2019-01-15 19:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0002_auto_20190115_1939'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='first_name', - field=models.CharField(default=None, max_length=100), - preserve_default=False, - ), - migrations.AddField( - model_name='user', - name='last_name', - field=models.CharField(default=None, max_length=100), - preserve_default=False, - ), - ] diff --git a/authors/apps/authentication/models.py b/authors/apps/authentication/models.py index f0fc0818..6c56a450 100644 --- a/authors/apps/authentication/models.py +++ b/authors/apps/authentication/models.py @@ -5,7 +5,7 @@ AbstractBaseUser, BaseUserManager, PermissionsMixin ) from django.db import models -from cloudinary.models import CloudinaryField + class UserManager(BaseUserManager): """ @@ -81,22 +81,6 @@ class User(AbstractBaseUser, PermissionsMixin): # A timestamp reprensenting when this object was last updated. updated_at = models.DateTimeField(auto_now=True) - # A char field to hold bio data - bio = models.CharField(max_length=200) - - # A choice field to hold multiple choice - gender_choices = (('M','MALE'),('F','FEMALE')) - gender = models.CharField(max_length=10, choices=gender_choices, default='M') - - # cloudinary field - profile_image = CloudinaryField('images', null=True) - - # A char field for the First name - first_name = models.CharField(max_length=100) - - # A char field for the user Last name - last_name = models.CharField(max_length=100) - # More fields required by Django when specifying a custom user model. # The `USERNAME_FIELD` property tells us which field we will use to log in. diff --git a/authors/apps/core/permissions.py b/authors/apps/core/permissions.py index 2aed7e0c..7875b98c 100644 --- a/authors/apps/core/permissions.py +++ b/authors/apps/core/permissions.py @@ -29,3 +29,20 @@ def has_object_permission(self, request, view, obj): # Rating permission is allowed to all other users except the owner # The article owner will only be allowed GET return obj.author != request.user + + +class IsMyProfileOrReadOnly(BasePermission): + """ + Custom permission to allow only owners of an object to edit it + """ + message = "You are not allowed to edit or delete this object" + + def has_object_permission(self, request, view, obj): + # read permissions are allowed to any request + # so we will allow GET, HEAD or OPTIONS requests + if request.method in SAFE_METHODS: + return True + + # write permission is only allowed to the owner of the object + print(obj) + return obj.user.username == request.user.username diff --git a/authors/apps/profiles/admin.py b/authors/apps/profiles/admin.py index 8c38f3f3..90c2b34b 100644 --- a/authors/apps/profiles/admin.py +++ b/authors/apps/profiles/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin -# Register your models here. +from .models import UserProfile + +admin.site.register(UserProfile) \ No newline at end of file diff --git a/authors/apps/profiles/migrations/0001_initial.py b/authors/apps/profiles/migrations/0001_initial.py index d7de0d78..a6e12879 100644 --- a/authors/apps/profiles/migrations/0001_initial.py +++ b/authors/apps/profiles/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.4 on 2019-01-15 08:51 +# Generated by Django 2.1.4 on 2019-01-16 11:17 import cloudinary.models from django.conf import settings @@ -11,16 +11,20 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('authentication', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='UserProfile', fields=[ - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('bio', models.TextField(max_length=200)), ('gender', models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE')], default='M', max_length=10)), - ('profile_image', cloudinary.models.CloudinaryField(max_length=255, null=True, verbose_name='images')), + ('profile_image', cloudinary.models.CloudinaryField(default='', max_length=255, null=True, verbose_name='images')), + ('first_name', models.CharField(max_length=100)), + ('last_name', models.CharField(max_length=100)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/authors/apps/profiles/migrations/0002_auto_20190115_1939.py b/authors/apps/profiles/migrations/0002_auto_20190115_1939.py deleted file mode 100644 index 159dcbd0..00000000 --- a/authors/apps/profiles/migrations/0002_auto_20190115_1939.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.4 on 2019-01-15 19:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='userprofile', - name='user', - ), - migrations.DeleteModel( - name='UserProfile', - ), - ] diff --git a/authors/apps/profiles/migrations/0002_auto_20190116_1253.py b/authors/apps/profiles/migrations/0002_auto_20190116_1253.py new file mode 100644 index 00000000..a5397862 --- /dev/null +++ b/authors/apps/profiles/migrations/0002_auto_20190116_1253.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.4 on 2019-01-16 12:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='first_name', + field=models.CharField(default='', max_length=100), + ), + migrations.AlterField( + model_name='userprofile', + name='last_name', + field=models.CharField(default='', max_length=100), + ), + ] diff --git a/authors/apps/profiles/migrations/0003_auto_20190116_1348.py b/authors/apps/profiles/migrations/0003_auto_20190116_1348.py new file mode 100644 index 00000000..445ef13a --- /dev/null +++ b/authors/apps/profiles/migrations/0003_auto_20190116_1348.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-16 13:48 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0002_auto_20190116_1253'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(default='', max_length=255, null=True, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0004_userprofile_slug.py b/authors/apps/profiles/migrations/0004_userprofile_slug.py new file mode 100644 index 00000000..8eb35dd0 --- /dev/null +++ b/authors/apps/profiles/migrations/0004_userprofile_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-17 08:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0003_auto_20190116_1348'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='slug', + field=models.SlugField(blank=True, null=True), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index e69de29b..e83fd226 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -0,0 +1,50 @@ +from django.db import models +from django.db.models.signals import post_save, pre_save +from django.utils.text import slugify +from ..authentication.models import User,UserManager +from cloudinary.models import CloudinaryField + + +class UserProfile(models.Model): + """ + This class contains models relating to user profile. + It has a one to one relationship with User model class. + An intance of this class is created during user registration by calling + the create profile in the post_save function + """ + + user = models.OneToOneField(User, on_delete=models.CASCADE) + # A char field to hold bio data + bio = models.TextField(max_length=200) + + # A choice field to hold multiple choice + gender_choices = (('M','MALE'),('F','FEMALE')) + gender = models.CharField(max_length=10, choices=gender_choices, default='M') + + # cloudinary field + profile_image = CloudinaryField('image', null=True, default='') + + # A char field for the First name + first_name = models.CharField(max_length=100, default='') + + # A char field for the user Last name + last_name = models.CharField(max_length=100, default='') + objects = UserManager() + + # create a slug for the user name + slug = models.SlugField(null=True, blank=True) + +# method to create a new profile +def create_profile(sender, **kwargs): + if kwargs['created']: + user_profile = UserProfile.objects.create(user=kwargs['instance']) + return user_profile + +post_save.connect(create_profile, sender=User) + +# method to set the slug +def create_slug_receiver(sender,instance, **kwargs): + slug = slugify(instance.user.username) + instance.slug = slug + +pre_save.connect(create_slug_receiver, sender=UserProfile) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 6978ae64..41a764ef 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -1,8 +1,13 @@ from rest_framework import serializers -from ..authentication.models import User +from .models import UserProfile + class UserProfileSerializer(serializers.ModelSerializer): + """ + Class to serialize user profile data + """ + profile_image = serializers.ImageField(default=None) class Meta: - model = User - fields = ('gender', 'bio', 'profile_image') - + model = UserProfile + profile_image = serializers.ImageField() + fields = ('first_name','last_name','gender', 'bio', 'profile_image') diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py index bac4cc82..2df60950 100644 --- a/authors/apps/profiles/urls.py +++ b/authors/apps/profiles/urls.py @@ -1,9 +1,9 @@ from django.urls import path +from .views import ProfileView, GetProfileView -from .views import ProfileView - app_name = "profiles" urlpatterns = [ - path('profile//', ProfileView.as_view()), + path('profile//', ProfileView.as_view()), + path('profile//', GetProfileView.as_view()), ] diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index c844b4be..a9c29e45 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -1,7 +1,22 @@ from rest_framework import generics from .serializers import UserProfileSerializer -from ..authentication.models import User +from .models import UserProfile +from authors.apps.core.permissions import IsMyProfileOrReadOnly + class ProfileView(generics.RetrieveUpdateAPIView): + """ + class view to update user profile + """ + serializer_class = UserProfileSerializer + queryset = UserProfile.objects.all() + permission_classes = (IsMyProfileOrReadOnly,) + lookup_field = 'slug' + +class GetProfileView(generics.ListAPIView): + """ + class view to view user profile + """ serializer_class = UserProfileSerializer - queryset = User.objects.all() + queryset = UserProfile.objects.all() + lookup_field = 'slug' diff --git a/authors/settings.py b/authors/settings.py index f7b253db..bf1d0caa 100644 --- a/authors/settings.py +++ b/authors/settings.py @@ -174,7 +174,6 @@ }, } - SECRET_KEY = os.getenv('SECRET_KEY') EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') @@ -184,7 +183,8 @@ EMAIL_USE_TLS = True cloudinary.config( - cloud_name='authors', - api_key='777468436252743', - api_secret='_mRwkwGpbRKurTYgzSuSPlxG3uk' + cloud_name = 'authors', + api_key = '777468436252743', + api_secret = '_mRwkwGpbRKurTYgzSuSPlxG3uk' ) + diff --git a/authors/urls.py b/authors/urls.py index 460aca7a..0c530424 100644 --- a/authors/urls.py +++ b/authors/urls.py @@ -26,7 +26,7 @@ path('api/', include('authors.apps.authentication.urls', namespace='authentication')), - path('user/', include('authors.apps.profiles.urls', namespace='profiles')), + path('users/', include('authors.apps.profiles.urls', namespace='profiles')), path('api/', include('authors.apps.articles.urls', namespace='articles')), # Set the API documentation at the root of the site diff --git a/requirements.txt b/requirements.txt index a89185a0..b9ea896d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,34 +1,16 @@ -astroid==2.1.0 -certifi==2018.11.29 -chardet==3.0.4 -cloudinary==1.15.0 -coreapi==2.3.3 -coreschema==0.0.4 -coverage==4.5.2 -coveralls==1.5.1 -dj-database-url==0.5.0 Django==2.1.4 django-cors-middleware==1.3.1 django-extensions==2.1.4 -django-heroku==0.3.1 django-rest-swagger==2.1.0 djangorestframework==3.9.0 -docopt==0.6.2 -gunicorn==19.9.0 -idna==2.8 -isort==4.3.4 -itypes==1.1.0 -Jinja2==2.10 -lazy-object-proxy==1.3.1 -MarkupSafe==1.1.0 -mccabe==0.6.1 -mock==2.0.0 -openapi-codec==1.3.2 -pbr==5.1.1 -psycopg2==2.7.6.1 psycopg2-binary==2.7.6.1 pycodestyle==2.4.0 +dj-database-url==0.5.0 +whitenoise==4.1.2 +gunicorn==19.9.0 +django-heroku==0.3.1 PyJWT==1.7.1 +<<<<<<< HEAD pylint==2.2.2 pytz==2018.9 cloudinary==1.15.0 @@ -36,8 +18,9 @@ Pillow==5.4.1 django-taggit==0.23.0 requests==2.21.0 simplejson==3.16.0 +======= +>>>>>>> feat (Profile) user can create a profile six==1.12.0 -uritemplate==3.0.0 -urllib3==1.24.1 -whitenoise==4.1.2 -wrapt==1.11.0 +pytz==2018.9 +cloudinary==1.15.0 +Pillow==5.4.1 From 38456365f25190d2bbf84275201a4f8159d0d92f Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 17 Jan 2019 15:39:54 +0300 Subject: [PATCH 03/31] feat (Profile) user can create a profile - Create a test folder [Delivers #162948841] --- .../migrations/0005_auto_20190117_1605.py | 18 +++++++++++++ .../migrations/0006_auto_20190117_1610.py | 19 +++++++++++++ .../migrations/0007_auto_20190117_1610.py | 19 +++++++++++++ .../migrations/0008_auto_20190117_1614.py | 19 +++++++++++++ authors/apps/profiles/models.py | 4 +-- authors/apps/profiles/serializers.py | 1 - authors/apps/profiles/test/__init__.py | 0 .../apps/profiles/test/test_user_profile.py | 27 +++++++++++++++++++ authors/apps/profiles/tests.py | 3 --- authors/apps/profiles/urls.py | 4 +-- 10 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 authors/apps/profiles/migrations/0005_auto_20190117_1605.py create mode 100644 authors/apps/profiles/migrations/0006_auto_20190117_1610.py create mode 100644 authors/apps/profiles/migrations/0007_auto_20190117_1610.py create mode 100644 authors/apps/profiles/migrations/0008_auto_20190117_1614.py create mode 100644 authors/apps/profiles/test/__init__.py create mode 100644 authors/apps/profiles/test/test_user_profile.py delete mode 100644 authors/apps/profiles/tests.py diff --git a/authors/apps/profiles/migrations/0005_auto_20190117_1605.py b/authors/apps/profiles/migrations/0005_auto_20190117_1605.py new file mode 100644 index 00000000..fe2bbf89 --- /dev/null +++ b/authors/apps/profiles/migrations/0005_auto_20190117_1605.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-17 16:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0004_userprofile_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='bio', + field=models.TextField(default='', max_length=200), + ), + ] diff --git a/authors/apps/profiles/migrations/0006_auto_20190117_1610.py b/authors/apps/profiles/migrations/0006_auto_20190117_1610.py new file mode 100644 index 00000000..55a0136b --- /dev/null +++ b/authors/apps/profiles/migrations/0006_auto_20190117_1610.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-17 16:10 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0005_auto_20190117_1605'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(default='', max_length=255, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0007_auto_20190117_1610.py b/authors/apps/profiles/migrations/0007_auto_20190117_1610.py new file mode 100644 index 00000000..dbe31c68 --- /dev/null +++ b/authors/apps/profiles/migrations/0007_auto_20190117_1610.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-17 16:10 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0006_auto_20190117_1610'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(default='', max_length=255, null=True, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0008_auto_20190117_1614.py b/authors/apps/profiles/migrations/0008_auto_20190117_1614.py new file mode 100644 index 00000000..c56bcdef --- /dev/null +++ b/authors/apps/profiles/migrations/0008_auto_20190117_1614.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-17 16:14 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0007_auto_20190117_1610'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(blank=True, default='', max_length=255, null=True, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index e83fd226..843b97f3 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -15,14 +15,14 @@ class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) # A char field to hold bio data - bio = models.TextField(max_length=200) + bio = models.TextField(max_length=200, default='') # A choice field to hold multiple choice gender_choices = (('M','MALE'),('F','FEMALE')) gender = models.CharField(max_length=10, choices=gender_choices, default='M') # cloudinary field - profile_image = CloudinaryField('image', null=True, default='') + profile_image = CloudinaryField('image', null=True, default='', blank=True) # A char field for the First name first_name = models.CharField(max_length=100, default='') diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 41a764ef..183f2c30 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -9,5 +9,4 @@ class UserProfileSerializer(serializers.ModelSerializer): profile_image = serializers.ImageField(default=None) class Meta: model = UserProfile - profile_image = serializers.ImageField() fields = ('first_name','last_name','gender', 'bio', 'profile_image') diff --git a/authors/apps/profiles/test/__init__.py b/authors/apps/profiles/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py new file mode 100644 index 00000000..e6206182 --- /dev/null +++ b/authors/apps/profiles/test/test_user_profile.py @@ -0,0 +1,27 @@ +import json +from django.urls import reverse +from rest_framework.views import status +from rest_framework.test import APITestCase, APIClient + + +class LoginTestCase(APITestCase): + def setUp(self): + self.client = APIClient() + self.login_url = reverse('authentication:auth-login') + self.signup_url = reverse('authentication:auth-register') + self.signup_data = { + "user": { + "username": "johndoe", + "email": "john@gmail.com", + "password": "@Qwerty12345" + }} + self.profile_data = { + "first_name":"","last_name":"", + "gender":"M","bio":"","profile_image":None} + + def test_get_profile(self): + """Test get profile upon registrations""" + register = self.client.post(self.signup_url,self.signup_data,format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + profile = self.client.get('/users/profile/johndoe/',format='json') + self.assertEqual(self.profile_data,profile.data) diff --git a/authors/apps/profiles/tests.py b/authors/apps/profiles/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/authors/apps/profiles/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py index 2df60950..90094b17 100644 --- a/authors/apps/profiles/urls.py +++ b/authors/apps/profiles/urls.py @@ -4,6 +4,6 @@ app_name = "profiles" urlpatterns = [ - path('profile//', ProfileView.as_view()), - path('profile//', GetProfileView.as_view()), + path('profile//', ProfileView.as_view(), name='post-profile'), + path('profile//', GetProfileView.as_view(), name='get-profile'), ] From e757cb31b3ed52b6ec01ea6d80829a9cafcc1546 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Fri, 18 Jan 2019 10:22:10 +0300 Subject: [PATCH 04/31] feat(profile test)Add test - Add post profile test - Add get profile test [Delivers #162948841] --- authors/apps/profiles/test/test_user_profile.py | 15 ++++++++++++++- authors/settings.py | 8 ++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index e6206182..14c427ac 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -23,5 +23,18 @@ def test_get_profile(self): """Test get profile upon registrations""" register = self.client.post(self.signup_url,self.signup_data,format='json') self.assertEqual(register.status_code, status.HTTP_201_CREATED) - profile = self.client.get('/users/profile/johndoe/',format='json') + profile = self.client.get(reverse('profiles:get-profile', kwargs={'slug': 'johndoe'}),format='json') self.assertEqual(self.profile_data,profile.data) + + def test_post_profile(self): + """Test post profile upon registrations""" + self.profile_data['first_name'],self.profile_data['last_name'] = 'kwame', 'asiago' + self.profile_data['bio'] = 'some bio' + del self.profile_data['profile_image'] + register = self.client.post(self.signup_url,self.signup_data,format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + token = register.data['token'] + profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}),self.profile_data, + format='json',HTTP_AUTHORIZATION='token {}'.format(token)) + self.profile_data['profile_image'] = None + self.assertEqual(self.profile_data,profile.data) \ No newline at end of file diff --git a/authors/settings.py b/authors/settings.py index bf1d0caa..370b961f 100644 --- a/authors/settings.py +++ b/authors/settings.py @@ -183,8 +183,8 @@ EMAIL_USE_TLS = True cloudinary.config( - cloud_name = 'authors', - api_key = '777468436252743', - api_secret = '_mRwkwGpbRKurTYgzSuSPlxG3uk' + cloud_name = os.getenv("CLOUD_NAME"), + api_key = os.getenv("API_KEY"), + api_secret = os.getenv("API_SECRET") ) - + \ No newline at end of file From f11d8ad86762584ec4620d96dd9b751ad5169113 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Fri, 18 Jan 2019 14:58:14 +0300 Subject: [PATCH 05/31] feat (feedback) Implement feedback - Add new line at the end of the file [Delivers #162948841] --- authors/apps/profiles/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authors/apps/profiles/admin.py b/authors/apps/profiles/admin.py index 90c2b34b..f5bccb6d 100644 --- a/authors/apps/profiles/admin.py +++ b/authors/apps/profiles/admin.py @@ -2,4 +2,4 @@ from .models import UserProfile -admin.site.register(UserProfile) \ No newline at end of file +admin.site.register(UserProfile) From 2ffbed2fd29318e2d8b96c94c2c520525b7b6162 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Sat, 19 Jan 2019 17:45:24 +0300 Subject: [PATCH 06/31] feat (Profile) Implement feedback - remove the print [Delivers #162948841] --- authors/apps/core/permissions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/authors/apps/core/permissions.py b/authors/apps/core/permissions.py index 7875b98c..dc1b0720 100644 --- a/authors/apps/core/permissions.py +++ b/authors/apps/core/permissions.py @@ -44,5 +44,4 @@ def has_object_permission(self, request, view, obj): return True # write permission is only allowed to the owner of the object - print(obj) return obj.user.username == request.user.username From 3fb89c8128126432fe5f8321732f8b551231881a Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Sat, 19 Jan 2019 17:50:41 +0300 Subject: [PATCH 07/31] feat (Profile) Implement feedback - Make function easier to read [Delivers #162948841] --- .../apps/profiles/test/test_user_profile.py | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 14c427ac..685783ce 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -16,25 +16,37 @@ def setUp(self): "password": "@Qwerty12345" }} self.profile_data = { - "first_name":"","last_name":"", - "gender":"M","bio":"","profile_image":None} + "first_name":"", + "last_name":"", + "gender":"M", + "bio":"", + "profile_image":None + } def test_get_profile(self): """Test get profile upon registrations""" + # register user register = self.client.post(self.signup_url,self.signup_data,format='json') self.assertEqual(register.status_code, status.HTTP_201_CREATED) + + # get user profile profile = self.client.get(reverse('profiles:get-profile', kwargs={'slug': 'johndoe'}),format='json') self.assertEqual(self.profile_data,profile.data) def test_post_profile(self): - """Test post profile upon registrations""" - self.profile_data['first_name'],self.profile_data['last_name'] = 'kwame', 'asiago' - self.profile_data['bio'] = 'some bio' - del self.profile_data['profile_image'] - register = self.client.post(self.signup_url,self.signup_data,format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) - token = register.data['token'] - profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}),self.profile_data, - format='json',HTTP_AUTHORIZATION='token {}'.format(token)) - self.profile_data['profile_image'] = None - self.assertEqual(self.profile_data,profile.data) \ No newline at end of file + """Test post profile upon registrations""" + # override self.profile_data + self.profile_data['first_name'],self.profile_data['last_name'] = 'kwame', 'asiago' + self.profile_data['bio'] = 'some bio' + del self.profile_data['profile_image'] + + # sign up a user and get token + register = self.client.post(self.signup_url,self.signup_data,format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + token = register.data['token'] + + # update profile + profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}),self.profile_data, + format='json',HTTP_AUTHORIZATION='token {}'.format(token)) + self.profile_data['profile_image'] = None + self.assertEqual(self.profile_data,profile.data) \ No newline at end of file From d51e12e4ac7b057f202c70b771d0661a89f267c9 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Mon, 21 Jan 2019 09:03:33 +0300 Subject: [PATCH 08/31] feat(profiles) Implement feedback - Edit to pep8 standard - remove json import from test_user_profile.py - reorder importations [Delivers #162948841] --- authors/apps/profiles/models.py | 23 ++++++---- authors/apps/profiles/serializers.py | 3 +- .../apps/profiles/test/test_user_profile.py | 46 ++++++++++--------- authors/apps/profiles/views.py | 1 + 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 843b97f3..2bf1c12b 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -1,8 +1,8 @@ from django.db import models from django.db.models.signals import post_save, pre_save from django.utils.text import slugify -from ..authentication.models import User,UserManager from cloudinary.models import CloudinaryField +from ..authentication.models import User, UserManager class UserProfile(models.Model): @@ -17,9 +17,10 @@ class UserProfile(models.Model): # A char field to hold bio data bio = models.TextField(max_length=200, default='') - # A choice field to hold multiple choice - gender_choices = (('M','MALE'),('F','FEMALE')) - gender = models.CharField(max_length=10, choices=gender_choices, default='M') + # A choice field to hold multiple choice + gender_choices = (('M', 'MALE'), ('F', 'FEMALE')) + gender = models.CharField( + max_length=10, choices=gender_choices, default='M') # cloudinary field profile_image = CloudinaryField('image', null=True, default='', blank=True) @@ -35,16 +36,22 @@ class UserProfile(models.Model): slug = models.SlugField(null=True, blank=True) # method to create a new profile + + def create_profile(sender, **kwargs): if kwargs['created']: - user_profile = UserProfile.objects.create(user=kwargs['instance']) - return user_profile + user_profile = UserProfile.objects.create(user=kwargs['instance']) + return user_profile + post_save.connect(create_profile, sender=User) -# method to set the slug -def create_slug_receiver(sender,instance, **kwargs): +# method to set the slug + + +def create_slug_receiver(sender, instance, **kwargs): slug = slugify(instance.user.username) instance.slug = slug + pre_save.connect(create_slug_receiver, sender=UserProfile) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 183f2c30..ca967868 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -7,6 +7,7 @@ class UserProfileSerializer(serializers.ModelSerializer): Class to serialize user profile data """ profile_image = serializers.ImageField(default=None) + class Meta: model = UserProfile - fields = ('first_name','last_name','gender', 'bio', 'profile_image') + fields = ('first_name', 'last_name', 'gender', 'bio', 'profile_image') diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 685783ce..59de58cb 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -1,4 +1,3 @@ -import json from django.urls import reverse from rest_framework.views import status from rest_framework.test import APITestCase, APIClient @@ -14,39 +13,42 @@ def setUp(self): "username": "johndoe", "email": "john@gmail.com", "password": "@Qwerty12345" - }} + }} self.profile_data = { - "first_name":"", - "last_name":"", - "gender":"M", - "bio":"", - "profile_image":None - } + "first_name": "", + "last_name": "", + "gender": "M", + "bio": "", + "profile_image": None + } def test_get_profile(self): """Test get profile upon registrations""" # register user - register = self.client.post(self.signup_url,self.signup_data,format='json') + register = self.client.post( + self.signup_url, self.signup_data, format='json') self.assertEqual(register.status_code, status.HTTP_201_CREATED) - + # get user profile - profile = self.client.get(reverse('profiles:get-profile', kwargs={'slug': 'johndoe'}),format='json') - self.assertEqual(self.profile_data,profile.data) - + profile = self.client.get( + reverse('profiles:get-profile', kwargs={'slug': 'johndoe'}), format='json') + self.assertEqual(self.profile_data, profile.data) + def test_post_profile(self): """Test post profile upon registrations""" - # override self.profile_data - self.profile_data['first_name'],self.profile_data['last_name'] = 'kwame', 'asiago' + # override self.profile_data + self.profile_data['first_name'], self.profile_data['last_name'] = 'kwame', 'asiago' self.profile_data['bio'] = 'some bio' del self.profile_data['profile_image'] - + # sign up a user and get token - register = self.client.post(self.signup_url,self.signup_data,format='json') + register = self.client.post( + self.signup_url, self.signup_data, format='json') self.assertEqual(register.status_code, status.HTTP_201_CREATED) - token = register.data['token'] - + token = register.data['token'] + # update profile - profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}),self.profile_data, - format='json',HTTP_AUTHORIZATION='token {}'.format(token)) + profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}), self.profile_data, + format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.profile_data['profile_image'] = None - self.assertEqual(self.profile_data,profile.data) \ No newline at end of file + self.assertEqual(self.profile_data, profile.data) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index a9c29e45..3d47453f 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -13,6 +13,7 @@ class view to update user profile permission_classes = (IsMyProfileOrReadOnly,) lookup_field = 'slug' + class GetProfileView(generics.ListAPIView): """ class view to view user profile From b5091b47b4d2c5d02fc758b416b076694935b0fb Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Mon, 21 Jan 2019 10:14:55 +0300 Subject: [PATCH 09/31] feat(profiles) Implement feedback - Make profile test more readable [Delivers #162948841] --- .../migrations/0009_userprofile_updated_at.py | 18 +++++++++++++++++ authors/apps/profiles/models.py | 5 ++++- authors/apps/profiles/serializers.py | 3 ++- .../apps/profiles/test/test_user_profile.py | 20 +++++++------------ 4 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 authors/apps/profiles/migrations/0009_userprofile_updated_at.py diff --git a/authors/apps/profiles/migrations/0009_userprofile_updated_at.py b/authors/apps/profiles/migrations/0009_userprofile_updated_at.py new file mode 100644 index 00000000..f6397c81 --- /dev/null +++ b/authors/apps/profiles/migrations/0009_userprofile_updated_at.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-21 06:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0008_auto_20190117_1614'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='updated_at', + field=models.DateField(auto_now=True), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 2bf1c12b..a676f873 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -30,11 +30,14 @@ class UserProfile(models.Model): # A char field for the user Last name last_name = models.CharField(max_length=100, default='') - objects = UserManager() # create a slug for the user name slug = models.SlugField(null=True, blank=True) + updated_at = models.DateField(auto_now=True) + + objects = UserManager() + # method to create a new profile diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index ca967868..36d53b3c 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -10,4 +10,5 @@ class UserProfileSerializer(serializers.ModelSerializer): class Meta: model = UserProfile - fields = ('first_name', 'last_name', 'gender', 'bio', 'profile_image') + fields = ('first_name', 'last_name', 'gender', 'bio', 'profile_image', 'updated_at') + read_only_fields = ('updated_at',) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 59de58cb..d8c6321b 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -15,14 +15,13 @@ def setUp(self): "password": "@Qwerty12345" }} self.profile_data = { - "first_name": "", - "last_name": "", + "first_name": "kwame", + "last_name": "asiago", "gender": "M", - "bio": "", - "profile_image": None + "bio": "my bio" } - def test_get_profile(self): + def test_get_user_profile(self): """Test get profile upon registrations""" # register user register = self.client.post( @@ -32,15 +31,10 @@ def test_get_profile(self): # get user profile profile = self.client.get( reverse('profiles:get-profile', kwargs={'slug': 'johndoe'}), format='json') - self.assertEqual(self.profile_data, profile.data) + self.assertEqual(register.status_code, status.HTTP_201_CREATED) - def test_post_profile(self): + def test_updating_profile(self): """Test post profile upon registrations""" - # override self.profile_data - self.profile_data['first_name'], self.profile_data['last_name'] = 'kwame', 'asiago' - self.profile_data['bio'] = 'some bio' - del self.profile_data['profile_image'] - # sign up a user and get token register = self.client.post( self.signup_url, self.signup_data, format='json') @@ -51,4 +45,4 @@ def test_post_profile(self): profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.profile_data['profile_image'] = None - self.assertEqual(self.profile_data, profile.data) + self.assertEqual(profile.status_code, status.HTTP_200_OK) From 200949281a1a9fc8c972da30cba8817e118d3edc Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Mon, 21 Jan 2019 10:21:43 +0300 Subject: [PATCH 10/31] feat(profiles) Implement feedback - change url name to profiles [Delivers #162948841] --- authors/apps/profiles/test/test_user_profile.py | 1 - authors/apps/profiles/urls.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index d8c6321b..8e53fdca 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -44,5 +44,4 @@ def test_updating_profile(self): # update profile profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) - self.profile_data['profile_image'] = None self.assertEqual(profile.status_code, status.HTTP_200_OK) diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py index 90094b17..e864b035 100644 --- a/authors/apps/profiles/urls.py +++ b/authors/apps/profiles/urls.py @@ -4,6 +4,6 @@ app_name = "profiles" urlpatterns = [ - path('profile//', ProfileView.as_view(), name='post-profile'), - path('profile//', GetProfileView.as_view(), name='get-profile'), + path('profiles//', ProfileView.as_view(), name='post-profile'), + path('profiles//', GetProfileView.as_view(), name='get-profile'), ] From 81cf9a21b092915bbbc5738e9964b7686a4d1f16 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Mon, 21 Jan 2019 18:24:05 +0300 Subject: [PATCH 11/31] feat(profile) Implement feedback - Add tests for empty values and invalid gender - Format to pep8 standards - Add extra field N to gender [Delivers #162948841] --- .../migrations/0010_auto_20190121_1206.py | 19 +++++++ .../0011_remove_userprofile_slug.py | 17 ++++++ .../migrations/0012_auto_20190121_1342.py | 28 ++++++++++ .../migrations/0013_auto_20190121_1344.py | 18 +++++++ .../migrations/0014_auto_20190121_1345.py | 18 +++++++ .../migrations/0015_auto_20190121_1406.py | 23 ++++++++ .../migrations/0016_auto_20190121_1407.py | 18 +++++++ .../migrations/0017_auto_20190121_1442.py | 19 +++++++ .../migrations/0018_auto_20190121_1443.py | 19 +++++++ .../migrations/0019_auto_20190121_1443.py | 19 +++++++ .../migrations/0020_auto_20190121_1445.py | 19 +++++++ authors/apps/profiles/models.py | 16 +++--- authors/apps/profiles/serializers.py | 25 ++++++++- .../apps/profiles/test/test_user_profile.py | 54 +++++++++++++++++-- authors/apps/profiles/urls.py | 5 +- authors/apps/profiles/views.py | 33 ++++++++---- authors/settings.py | 9 ++-- authors/urls.py | 3 +- 18 files changed, 327 insertions(+), 35 deletions(-) create mode 100644 authors/apps/profiles/migrations/0010_auto_20190121_1206.py create mode 100644 authors/apps/profiles/migrations/0011_remove_userprofile_slug.py create mode 100644 authors/apps/profiles/migrations/0012_auto_20190121_1342.py create mode 100644 authors/apps/profiles/migrations/0013_auto_20190121_1344.py create mode 100644 authors/apps/profiles/migrations/0014_auto_20190121_1345.py create mode 100644 authors/apps/profiles/migrations/0015_auto_20190121_1406.py create mode 100644 authors/apps/profiles/migrations/0016_auto_20190121_1407.py create mode 100644 authors/apps/profiles/migrations/0017_auto_20190121_1442.py create mode 100644 authors/apps/profiles/migrations/0018_auto_20190121_1443.py create mode 100644 authors/apps/profiles/migrations/0019_auto_20190121_1443.py create mode 100644 authors/apps/profiles/migrations/0020_auto_20190121_1445.py diff --git a/authors/apps/profiles/migrations/0010_auto_20190121_1206.py b/authors/apps/profiles/migrations/0010_auto_20190121_1206.py new file mode 100644 index 00000000..96e1d029 --- /dev/null +++ b/authors/apps/profiles/migrations/0010_auto_20190121_1206.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-21 12:06 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0009_userprofile_updated_at'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(default='', max_length=255, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0011_remove_userprofile_slug.py b/authors/apps/profiles/migrations/0011_remove_userprofile_slug.py new file mode 100644 index 00000000..9e3db084 --- /dev/null +++ b/authors/apps/profiles/migrations/0011_remove_userprofile_slug.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.4 on 2019-01-21 12:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0010_auto_20190121_1206'), + ] + + operations = [ + migrations.RemoveField( + model_name='userprofile', + name='slug', + ), + ] diff --git a/authors/apps/profiles/migrations/0012_auto_20190121_1342.py b/authors/apps/profiles/migrations/0012_auto_20190121_1342.py new file mode 100644 index 00000000..d00bf06b --- /dev/null +++ b/authors/apps/profiles/migrations/0012_auto_20190121_1342.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.4 on 2019-01-21 13:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0011_remove_userprofile_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='first_name', + field=models.CharField(max_length=100), + ), + migrations.AlterField( + model_name='userprofile', + name='gender', + field=models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE'), ('N', 'I PREFER NOT TO SAY')], default='N', max_length=10), + ), + migrations.AlterField( + model_name='userprofile', + name='last_name', + field=models.CharField(max_length=100), + ), + ] diff --git a/authors/apps/profiles/migrations/0013_auto_20190121_1344.py b/authors/apps/profiles/migrations/0013_auto_20190121_1344.py new file mode 100644 index 00000000..d5202719 --- /dev/null +++ b/authors/apps/profiles/migrations/0013_auto_20190121_1344.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-21 13:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0012_auto_20190121_1342'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='gender', + field=models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE'), ('N', 'I PREFER NOT TO SAY')], default='N', error_messages={'incorrect_format': 'dd'}, max_length=10), + ), + ] diff --git a/authors/apps/profiles/migrations/0014_auto_20190121_1345.py b/authors/apps/profiles/migrations/0014_auto_20190121_1345.py new file mode 100644 index 00000000..97df79d3 --- /dev/null +++ b/authors/apps/profiles/migrations/0014_auto_20190121_1345.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-21 13:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0013_auto_20190121_1344'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='gender', + field=models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE'), ('N', 'I PREFER NOT TO SAY')], default='N', error_messages={'required': 'dd'}, max_length=10), + ), + ] diff --git a/authors/apps/profiles/migrations/0015_auto_20190121_1406.py b/authors/apps/profiles/migrations/0015_auto_20190121_1406.py new file mode 100644 index 00000000..baf0b600 --- /dev/null +++ b/authors/apps/profiles/migrations/0015_auto_20190121_1406.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.4 on 2019-01-21 14:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0014_auto_20190121_1345'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='first_name', + field=models.CharField(error_messages={'required': 'first name'}, max_length=100), + ), + migrations.AlterField( + model_name='userprofile', + name='gender', + field=models.CharField(choices=[('M', 'MALE'), ('F', 'FEMALE'), ('N', 'I PREFER NOT TO SAY')], default='N', max_length=10), + ), + ] diff --git a/authors/apps/profiles/migrations/0016_auto_20190121_1407.py b/authors/apps/profiles/migrations/0016_auto_20190121_1407.py new file mode 100644 index 00000000..9060ec6c --- /dev/null +++ b/authors/apps/profiles/migrations/0016_auto_20190121_1407.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-21 14:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0015_auto_20190121_1406'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='first_name', + field=models.CharField(max_length=100), + ), + ] diff --git a/authors/apps/profiles/migrations/0017_auto_20190121_1442.py b/authors/apps/profiles/migrations/0017_auto_20190121_1442.py new file mode 100644 index 00000000..451fe50a --- /dev/null +++ b/authors/apps/profiles/migrations/0017_auto_20190121_1442.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-21 14:42 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0016_auto_20190121_1407'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(blank=True, default='no image provided', max_length=255, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0018_auto_20190121_1443.py b/authors/apps/profiles/migrations/0018_auto_20190121_1443.py new file mode 100644 index 00000000..614604ff --- /dev/null +++ b/authors/apps/profiles/migrations/0018_auto_20190121_1443.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-21 14:43 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0017_auto_20190121_1442'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(blank=True, max_length=255, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0019_auto_20190121_1443.py b/authors/apps/profiles/migrations/0019_auto_20190121_1443.py new file mode 100644 index 00000000..83293cc7 --- /dev/null +++ b/authors/apps/profiles/migrations/0019_auto_20190121_1443.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-21 14:43 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0018_auto_20190121_1443'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(blank=True, default='', max_length=255, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/migrations/0020_auto_20190121_1445.py b/authors/apps/profiles/migrations/0020_auto_20190121_1445.py new file mode 100644 index 00000000..cda5622c --- /dev/null +++ b/authors/apps/profiles/migrations/0020_auto_20190121_1445.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.4 on 2019-01-21 14:45 + +import cloudinary.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0019_auto_20190121_1443'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='profile_image', + field=cloudinary.models.CloudinaryField(blank=True, max_length=255, verbose_name='image'), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index a676f873..940c3af5 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -15,24 +15,22 @@ class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) # A char field to hold bio data - bio = models.TextField(max_length=200, default='') + bio = models.TextField(max_length=200, default='empty') # A choice field to hold multiple choice - gender_choices = (('M', 'MALE'), ('F', 'FEMALE')) + gender_choices = (('M', 'MALE'), ('F', 'FEMALE'), + ('N', 'I PREFER NOT TO SAY')) gender = models.CharField( - max_length=10, choices=gender_choices, default='M') + max_length=10, choices=gender_choices, default='N') # cloudinary field - profile_image = CloudinaryField('image', null=True, default='', blank=True) + profile_image = CloudinaryField('image', blank=True) # A char field for the First name - first_name = models.CharField(max_length=100, default='') + first_name = models.CharField(max_length=100) # A char field for the user Last name - last_name = models.CharField(max_length=100, default='') - - # create a slug for the user name - slug = models.SlugField(null=True, blank=True) + last_name = models.CharField(max_length=100) updated_at = models.DateField(auto_now=True) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 36d53b3c..11a7ae3f 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -6,9 +6,30 @@ class UserProfileSerializer(serializers.ModelSerializer): """ Class to serialize user profile data """ - profile_image = serializers.ImageField(default=None) + profile_image = serializers.ImageField(default='No image') + gender = serializers.RegexField( + regex='^(N|F|M)$', + write_only=True, + error_messages={ + 'required': 'Password field required', + 'invalid': 'Please enter M if you are male, F if you are female or N if you do not want to disclose ' + } + ) class Meta: model = UserProfile - fields = ('first_name', 'last_name', 'gender', 'bio', 'profile_image', 'updated_at') + fields = ('first_name', 'last_name', 'gender', + 'bio', 'profile_image', 'updated_at') read_only_fields = ('updated_at',) + + def validate(self, data): + first_name = data.get('first_name', None) + last_name = data.get('last_name', None) + email = data.get('email', None) + bio = data.get('bio', None) + gender = data.get('gender', None) + if first_name is None: + raise serializers.ValidationError('first name was not provided') + if first_name is None: + raise serializers.ValidationError('last name was not provided') + return data diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 8e53fdca..52362397 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -1,3 +1,4 @@ +import json from django.urls import reverse from rest_framework.views import status from rest_framework.test import APITestCase, APIClient @@ -17,7 +18,19 @@ def setUp(self): self.profile_data = { "first_name": "kwame", "last_name": "asiago", - "gender": "M", + "gender": "F", + "bio": "my bio" + } + self.empty_first_name = { + "first_name": "", + "last_name": "asiago", + "gender": "F", + "bio": "my bio" + } + self.invalid_gender = { + "first_name": "kwame", + "last_name": "asiago", + "gender": "G", "bio": "my bio" } @@ -30,8 +43,8 @@ def test_get_user_profile(self): # get user profile profile = self.client.get( - reverse('profiles:get-profile', kwargs={'slug': 'johndoe'}), format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) + reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), format='json') + self.assertEqual(profile.status_code, status.HTTP_200_OK) def test_updating_profile(self): """Test post profile upon registrations""" @@ -42,6 +55,39 @@ def test_updating_profile(self): token = register.data['token'] # update profile - profile = self.client.put(reverse('profiles:post-profile', kwargs={'slug': 'johndoe'}), self.profile_data, + profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_200_OK) + self.assertEqual( + profile.data, {'response': 'profile has been updated successfully '}) + + def test_updating_empty_first_name(self): + """Test post profile upon registrations""" + # sign up a user and get token + register = self.client.post( + self.signup_url, self.signup_data, format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + token = register.data['token'] + + # update profile + profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.empty_first_name, + format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual({'errors': {'first_name': [ + 'This field may not be blank.']}}, json.loads(profile.content)) + + def test_updating_invalid_gender(self): + """Test post profile upon registrations""" + # sign up a user and get token + register = self.client.post( + self.signup_url, self.signup_data, format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + token = register.data['token'] + + # update profile + profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.invalid_gender, + format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) + data = {'errors': {'gender': ['Please enter M if you are male, F if you are female or ' + + 'N if you do not want to disclose ']}} + self.assertEqual(data, json.loads(profile.content)) diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py index e864b035..5018c3f0 100644 --- a/authors/apps/profiles/urls.py +++ b/authors/apps/profiles/urls.py @@ -1,9 +1,8 @@ from django.urls import path -from .views import ProfileView, GetProfileView +from .views import ProfileView app_name = "profiles" urlpatterns = [ - path('profiles//', ProfileView.as_view(), name='post-profile'), - path('profiles//', GetProfileView.as_view(), name='get-profile'), + path('profiles//', ProfileView.as_view(), name='put-profile'), ] diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 3d47453f..7ec0055b 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -1,23 +1,34 @@ -from rest_framework import generics +from rest_framework import generics, status +from rest_framework.response import Response from .serializers import UserProfileSerializer from .models import UserProfile from authors.apps.core.permissions import IsMyProfileOrReadOnly class ProfileView(generics.RetrieveUpdateAPIView): - """ + ''' class view to update user profile - """ + ''' serializer_class = UserProfileSerializer queryset = UserProfile.objects.all() permission_classes = (IsMyProfileOrReadOnly,) - lookup_field = 'slug' + def update(self, request, username, *args, **kwargs): + try: + user_instance = UserProfile.objects.select_related( + 'user').get(user__username=username) + except: + return Response({'response': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.update(instance=user_instance, validated_data=request.data) + return Response({'response': 'profile has been updated successfully '}) -class GetProfileView(generics.ListAPIView): - """ - class view to view user profile - """ - serializer_class = UserProfileSerializer - queryset = UserProfile.objects.all() - lookup_field = 'slug' + def retrieve(self, request, username, *args, **kwargs): + try: + user_instance = UserProfile.objects.select_related( + 'user').get(user__username=username) + except: + return Response({'': ''}, status.HTTP_404_NOT_FOUND) + serializer = self.serializer_class(user_instance) + return Response({'profile': serializer.data}) diff --git a/authors/settings.py b/authors/settings.py index 370b961f..4116953f 100644 --- a/authors/settings.py +++ b/authors/settings.py @@ -183,8 +183,7 @@ EMAIL_USE_TLS = True cloudinary.config( - cloud_name = os.getenv("CLOUD_NAME"), - api_key = os.getenv("API_KEY"), - api_secret = os.getenv("API_SECRET") - ) - \ No newline at end of file + cloud_name=os.getenv("CLOUD_NAME"), + api_key=os.getenv("API_KEY"), + api_secret=os.getenv("API_SECRET") +) diff --git a/authors/urls.py b/authors/urls.py index 0c530424..d6056884 100644 --- a/authors/urls.py +++ b/authors/urls.py @@ -26,7 +26,8 @@ path('api/', include('authors.apps.authentication.urls', namespace='authentication')), - path('users/', include('authors.apps.profiles.urls', namespace='profiles')), + path('api/', include('authors.apps.friends.urls')), + path('user/', include('authors.apps.profiles.urls', namespace='profiles')), path('api/', include('authors.apps.articles.urls', namespace='articles')), # Set the API documentation at the root of the site From 87fee154fc1ee64fd6baa7724fd71ad9ed7afa9f Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Tue, 22 Jan 2019 10:35:00 +0300 Subject: [PATCH 12/31] feat(profile) Implement feedback - shorten my test length [Delivers #162948841] --- authors/apps/profiles/test/test_user_profile.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 52362397..26e18001 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -55,8 +55,8 @@ def test_updating_profile(self): token = register.data['token'] # update profile - profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.profile_data, - format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), + self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_200_OK) self.assertEqual( profile.data, {'response': 'profile has been updated successfully '}) @@ -70,8 +70,8 @@ def test_updating_empty_first_name(self): token = register.data['token'] # update profile - profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.empty_first_name, - format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), + self.empty_first_name, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual({'errors': {'first_name': [ 'This field may not be blank.']}}, json.loads(profile.content)) @@ -85,8 +85,8 @@ def test_updating_invalid_gender(self): token = register.data['token'] # update profile - profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.invalid_gender, - format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), + self.invalid_gender, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) data = {'errors': {'gender': ['Please enter M if you are male, F if you are female or ' + 'N if you do not want to disclose ']}} From 98167a6dc99d9a69179708b5e86cd856b85a4518 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Tue, 22 Jan 2019 12:01:36 +0300 Subject: [PATCH 13/31] feat(profile) Implement feedback - Create a function for getting token [Delivers #162948841] --- .../apps/profiles/test/test_user_profile.py | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 26e18001..99ac080d 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -34,6 +34,13 @@ def setUp(self): "bio": "my bio" } + def get_token(self): + register = self.client.post( + self.signup_url, self.signup_data, format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + return register.data['token'] + + def test_get_user_profile(self): """Test get profile upon registrations""" # register user @@ -48,12 +55,7 @@ def test_get_user_profile(self): def test_updating_profile(self): """Test post profile upon registrations""" - # sign up a user and get token - register = self.client.post( - self.signup_url, self.signup_data, format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) - token = register.data['token'] - + token = self.get_token() # update profile profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) @@ -63,31 +65,32 @@ def test_updating_profile(self): def test_updating_empty_first_name(self): """Test post profile upon registrations""" - # sign up a user and get token - register = self.client.post( - self.signup_url, self.signup_data, format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) - token = register.data['token'] - + token = self.get_token() # update profile - profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), - self.empty_first_name, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + profile = self.client.put( + reverse( + 'profiles:put-profile', + kwargs={'username': 'johndoe'} + ), + self.empty_first_name, format='json', + HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual({'errors': {'first_name': [ - 'This field may not be blank.']}}, json.loads(profile.content)) + self.assertEqual( + {'errors': {'first_name': [ + 'This field may not be blank.']}}, + json.loads(profile.content)) def test_updating_invalid_gender(self): """Test post profile upon registrations""" - # sign up a user and get token - register = self.client.post( - self.signup_url, self.signup_data, format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) - token = register.data['token'] - + token = self.get_token() # update profile - profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), - self.invalid_gender, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) + profile = self.client.put( + reverse( + 'profiles:put-profile', + kwargs={'username': 'johndoe'}), + self.invalid_gender, + format='json', + HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) - data = {'errors': {'gender': ['Please enter M if you are male, F if you are female or ' - + 'N if you do not want to disclose ']}} + data = {'errors': {'gender': ['Please enter M if you are male, F if you are female or N if you do not want to disclose ']}} self.assertEqual(data, json.loads(profile.content)) From f5f48f2aa66a41f31109686939764667cc80a337 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Tue, 22 Jan 2019 12:05:17 +0300 Subject: [PATCH 14/31] feat(profile) Implement feedback - Create a variable for the variable [Delivers #162948841] --- authors/apps/profiles/admin.py | 1 - authors/apps/profiles/test/test_user_profile.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/authors/apps/profiles/admin.py b/authors/apps/profiles/admin.py index f5bccb6d..65521fd1 100644 --- a/authors/apps/profiles/admin.py +++ b/authors/apps/profiles/admin.py @@ -1,5 +1,4 @@ from django.contrib import admin - from .models import UserProfile admin.site.register(UserProfile) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 99ac080d..af0ce2df 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -92,5 +92,6 @@ def test_updating_invalid_gender(self): format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) - data = {'errors': {'gender': ['Please enter M if you are male, F if you are female or N if you do not want to disclose ']}} + msg = 'Please enter M if you are male, F if you are female or N if you do not want to disclose ' + data = {'errors': {'gender': [msg]}} self.assertEqual(data, json.loads(profile.content)) From 3acf1d77b30127804f69875c5a0baa1f63b9a90c Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Tue, 22 Jan 2019 20:30:59 +0300 Subject: [PATCH 15/31] feat (profile) Update file - Update requirement.txt [Delivers #162948841] --- requirements.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index b9ea896d..b28c9dea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,6 @@ whitenoise==4.1.2 gunicorn==19.9.0 django-heroku==0.3.1 PyJWT==1.7.1 -<<<<<<< HEAD pylint==2.2.2 pytz==2018.9 cloudinary==1.15.0 @@ -18,8 +17,6 @@ Pillow==5.4.1 django-taggit==0.23.0 requests==2.21.0 simplejson==3.16.0 -======= ->>>>>>> feat (Profile) user can create a profile six==1.12.0 pytz==2018.9 cloudinary==1.15.0 From ae8b842d26deb3c89b0607b0ef29c0f247c74217 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 11:45:20 +0300 Subject: [PATCH 16/31] feat (profile) Fix permission class - Fix update permission class [Delivers #162948841] --- authors/apps/core/permissions.py | 16 ---------------- authors/apps/profiles/views.py | 4 ++-- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/authors/apps/core/permissions.py b/authors/apps/core/permissions.py index dc1b0720..2aed7e0c 100644 --- a/authors/apps/core/permissions.py +++ b/authors/apps/core/permissions.py @@ -29,19 +29,3 @@ def has_object_permission(self, request, view, obj): # Rating permission is allowed to all other users except the owner # The article owner will only be allowed GET return obj.author != request.user - - -class IsMyProfileOrReadOnly(BasePermission): - """ - Custom permission to allow only owners of an object to edit it - """ - message = "You are not allowed to edit or delete this object" - - def has_object_permission(self, request, view, obj): - # read permissions are allowed to any request - # so we will allow GET, HEAD or OPTIONS requests - if request.method in SAFE_METHODS: - return True - - # write permission is only allowed to the owner of the object - return obj.user.username == request.user.username diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 7ec0055b..7a9c074d 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -2,7 +2,6 @@ from rest_framework.response import Response from .serializers import UserProfileSerializer from .models import UserProfile -from authors.apps.core.permissions import IsMyProfileOrReadOnly class ProfileView(generics.RetrieveUpdateAPIView): @@ -11,12 +10,13 @@ class view to update user profile ''' serializer_class = UserProfileSerializer queryset = UserProfile.objects.all() - permission_classes = (IsMyProfileOrReadOnly,) def update(self, request, username, *args, **kwargs): try: user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) + if user_instance.user.username != request.user.username: + return Response({'response':'You are not allowed to edit or delete this object'}) except: return Response({'response': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) serializer = self.serializer_class(data=request.data) From 0b611a66caac63247a4915af098484418f7672bf Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 12:09:41 +0300 Subject: [PATCH 17/31] feat(profile) Implement feedback - Add more test - Edit test comments [Delivers #162948841] --- .../apps/profiles/test/test_user_profile.py | 56 +++++++++++++++++-- authors/apps/profiles/views.py | 4 +- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index af0ce2df..ce742d6c 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -9,12 +9,18 @@ def setUp(self): self.client = APIClient() self.login_url = reverse('authentication:auth-login') self.signup_url = reverse('authentication:auth-register') - self.signup_data = { + self.data_john = { "user": { "username": "johndoe", "email": "john@gmail.com", "password": "@Qwerty12345" }} + self.data_jane = { + "user": { + "username": "janedoe", + "email": "jane@gmail.com", + "password": "@Qwerty12345" + }} self.profile_data = { "first_name": "kwame", "last_name": "asiago", @@ -36,7 +42,13 @@ def setUp(self): def get_token(self): register = self.client.post( - self.signup_url, self.signup_data, format='json') + self.signup_url, self.data_john, format='json') + self.assertEqual(register.status_code, status.HTTP_201_CREATED) + return register.data['token'] + + def get_token_jane(self): + register = self.client.post( + self.signup_url, self.data_jane, format='json') self.assertEqual(register.status_code, status.HTTP_201_CREATED) return register.data['token'] @@ -45,7 +57,7 @@ def test_get_user_profile(self): """Test get profile upon registrations""" # register user register = self.client.post( - self.signup_url, self.signup_data, format='json') + self.signup_url, self.data_john, format='json') self.assertEqual(register.status_code, status.HTTP_201_CREATED) # get user profile @@ -54,7 +66,7 @@ def test_get_user_profile(self): self.assertEqual(profile.status_code, status.HTTP_200_OK) def test_updating_profile(self): - """Test post profile upon registrations""" + """Test updating profile upon registrations""" token = self.get_token() # update profile profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), @@ -64,7 +76,7 @@ def test_updating_profile(self): profile.data, {'response': 'profile has been updated successfully '}) def test_updating_empty_first_name(self): - """Test post profile upon registrations""" + """Test submiting an empty first name""" token = self.get_token() # update profile profile = self.client.put( @@ -81,7 +93,7 @@ def test_updating_empty_first_name(self): json.loads(profile.content)) def test_updating_invalid_gender(self): - """Test post profile upon registrations""" + """Test updating an invalid gender""" token = self.get_token() # update profile profile = self.client.put( @@ -95,3 +107,35 @@ def test_updating_invalid_gender(self): msg = 'Please enter M if you are male, F if you are female or N if you do not want to disclose ' data = {'errors': {'gender': [msg]}} self.assertEqual(data, json.loads(profile.content)) + + def test_updating_invalid_user(self): + """Test updating with other peaople user porofile""" + self.get_token_jane() + token = self.get_token() + # update profile + profile = self.client.put( + reverse( + 'profiles:put-profile', + kwargs={'username': 'janedoe'}), + self.invalid_gender, + format='json', + HTTP_AUTHORIZATION='token {}'.format(token)) + # self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) + data = {'error': 'You are not allowed to edit or delete this object'} + self.assertEqual(data, json.loads(profile.content)) + + + def test_non_existing_user(self): + """Test updating an user that does not exist""" + token = self.get_token() + # update profile + profile = self.client.put( + reverse( + 'profiles:put-profile', + kwargs={'username': 'someone'}), + self.invalid_gender, + format='json', + HTTP_AUTHORIZATION='token {}'.format(token)) + # self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) + data = {'error': 'Username someone not found'} + self.assertEqual(data, json.loads(profile.content)) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 7a9c074d..577b4920 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -16,9 +16,9 @@ def update(self, request, username, *args, **kwargs): user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) if user_instance.user.username != request.user.username: - return Response({'response':'You are not allowed to edit or delete this object'}) + return Response({'error':'You are not allowed to edit or delete this object'}) except: - return Response({'response': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) + return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.update(instance=user_instance, validated_data=request.data) From 220262c7d14f061402afe0ac7121439b7ec2cc60 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 12:13:25 +0300 Subject: [PATCH 18/31] feat(profile) Implement feedback - Remove slug [Delivers #162948841] --- authors/apps/profiles/models.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 940c3af5..778a8e4e 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -47,12 +47,3 @@ def create_profile(sender, **kwargs): post_save.connect(create_profile, sender=User) -# method to set the slug - - -def create_slug_receiver(sender, instance, **kwargs): - slug = slugify(instance.user.username) - instance.slug = slug - - -pre_save.connect(create_slug_receiver, sender=UserProfile) From 8fafbc0f5286c32c23f252dfc8b05fb13959f877 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 12:14:28 +0300 Subject: [PATCH 19/31] feat(profile) Implement feedback - Remove password field in gender [Delivers #162948841] --- authors/apps/profiles/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 11a7ae3f..daf7d6e5 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -11,7 +11,6 @@ class UserProfileSerializer(serializers.ModelSerializer): regex='^(N|F|M)$', write_only=True, error_messages={ - 'required': 'Password field required', 'invalid': 'Please enter M if you are male, F if you are female or N if you do not want to disclose ' } ) From 3eb920c3adfcc451e773f2f45a9fddd105d0451d Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 12:48:48 +0300 Subject: [PATCH 20/31] feat(profile) Implement feedback - Fix typo in userprofile serializers [Delivers #162948841] --- authors/apps/profiles/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index daf7d6e5..8ae5f196 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -29,6 +29,6 @@ def validate(self, data): gender = data.get('gender', None) if first_name is None: raise serializers.ValidationError('first name was not provided') - if first_name is None: + if last_name is None: raise serializers.ValidationError('last name was not provided') return data From 44fbedd1c5c004b2e1a8953ba1111788c0022029 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 14:53:15 +0300 Subject: [PATCH 21/31] feat(profile) Implement feedback - Add status code - Change status from response to message - Add exceptions does not exist [Delivers #163346018] --- authors/apps/profiles/models.py | 2 +- authors/apps/profiles/test/test_user_profile.py | 4 ++-- authors/apps/profiles/views.py | 13 +++++++------ authors/urls.py | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 778a8e4e..ab1f71d6 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -9,7 +9,7 @@ class UserProfile(models.Model): """ This class contains models relating to user profile. It has a one to one relationship with User model class. - An intance of this class is created during user registration by calling + An instance of this class is created during user registration by calling the create profile in the post_save function """ diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index ce742d6c..82f34c99 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -71,9 +71,9 @@ def test_updating_profile(self): # update profile profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) - self.assertEqual(profile.status_code, status.HTTP_200_OK) + self.assertEqual(profile.status_code, status.HTTP_201_CREATED) self.assertEqual( - profile.data, {'response': 'profile has been updated successfully '}) + profile.data, {'message': 'profile has been updated successfully '}) def test_updating_empty_first_name(self): """Test submiting an empty first name""" diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 577b4920..0f688747 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -1,5 +1,6 @@ from rest_framework import generics, status from rest_framework.response import Response +from django.core.exceptions import ObjectDoesNotExist from .serializers import UserProfileSerializer from .models import UserProfile @@ -16,19 +17,19 @@ def update(self, request, username, *args, **kwargs): user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) if user_instance.user.username != request.user.username: - return Response({'error':'You are not allowed to edit or delete this object'}) - except: + return Response({'error':'You are not allowed to edit or delete this object'},status.HTTP_403_FORBIDDEN) + except ObjectDoesNotExist: return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.update(instance=user_instance, validated_data=request.data) - return Response({'response': 'profile has been updated successfully '}) + return Response({'message': 'profile has been updated successfully '},status.HTTP_201_CREATED) def retrieve(self, request, username, *args, **kwargs): try: user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) - except: - return Response({'': ''}, status.HTTP_404_NOT_FOUND) + except ObjectDoesNotExist: + return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) serializer = self.serializer_class(user_instance) - return Response({'profile': serializer.data}) + return Response({'profile': serializer.data},status.HTTP_200_OK) diff --git a/authors/urls.py b/authors/urls.py index d6056884..445ef755 100644 --- a/authors/urls.py +++ b/authors/urls.py @@ -27,7 +27,7 @@ path('api/', include('authors.apps.authentication.urls', namespace='authentication')), path('api/', include('authors.apps.friends.urls')), - path('user/', include('authors.apps.profiles.urls', namespace='profiles')), + path('api/', include('authors.apps.profiles.urls', namespace='profiles')), path('api/', include('authors.apps.articles.urls', namespace='articles')), # Set the API documentation at the root of the site From 38994971de381730caf16e58bb4e74800b213df0 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 15:09:41 +0300 Subject: [PATCH 22/31] feat(profile) Implement feedback - Remove first name and last name validators [Delivers #163346018] --- authors/apps/profiles/serializers.py | 11 --------- .../apps/profiles/test/test_user_profile.py | 24 +------------------ 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 8ae5f196..120272c0 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -21,14 +21,3 @@ class Meta: 'bio', 'profile_image', 'updated_at') read_only_fields = ('updated_at',) - def validate(self, data): - first_name = data.get('first_name', None) - last_name = data.get('last_name', None) - email = data.get('email', None) - bio = data.get('bio', None) - gender = data.get('gender', None) - if first_name is None: - raise serializers.ValidationError('first name was not provided') - if last_name is None: - raise serializers.ValidationError('last name was not provided') - return data diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 82f34c99..2cc80361 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -27,12 +27,7 @@ def setUp(self): "gender": "F", "bio": "my bio" } - self.empty_first_name = { - "first_name": "", - "last_name": "asiago", - "gender": "F", - "bio": "my bio" - } + self.invalid_gender = { "first_name": "kwame", "last_name": "asiago", @@ -75,23 +70,6 @@ def test_updating_profile(self): self.assertEqual( profile.data, {'message': 'profile has been updated successfully '}) - def test_updating_empty_first_name(self): - """Test submiting an empty first name""" - token = self.get_token() - # update profile - profile = self.client.put( - reverse( - 'profiles:put-profile', - kwargs={'username': 'johndoe'} - ), - self.empty_first_name, format='json', - HTTP_AUTHORIZATION='token {}'.format(token)) - self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - {'errors': {'first_name': [ - 'This field may not be blank.']}}, - json.loads(profile.content)) - def test_updating_invalid_gender(self): """Test updating an invalid gender""" token = self.get_token() From 009e5632d5b2e3f14e3a98fbbd3f5568ac29a7fb Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 15:12:32 +0300 Subject: [PATCH 23/31] feat(profile) Implement feedback - Remove default in bio model [Delivers #163346018] --- .../migrations/0022_auto_20190123_1211.py | 18 ++++++++++++++++++ authors/apps/profiles/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 authors/apps/profiles/migrations/0022_auto_20190123_1211.py diff --git a/authors/apps/profiles/migrations/0022_auto_20190123_1211.py b/authors/apps/profiles/migrations/0022_auto_20190123_1211.py new file mode 100644 index 00000000..46fc1f3a --- /dev/null +++ b/authors/apps/profiles/migrations/0022_auto_20190123_1211.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2019-01-23 12:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0021_auto_20190122_1723'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='bio', + field=models.TextField(blank=True, max_length=200), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index ab1f71d6..0964381c 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -15,7 +15,7 @@ class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) # A char field to hold bio data - bio = models.TextField(max_length=200, default='empty') + bio = models.TextField(max_length=200, blank=True) # A choice field to hold multiple choice gender_choices = (('M', 'MALE'), ('F', 'FEMALE'), From 2c086fcbe880a71ceae0a17244d9555f39aa847f Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 15:15:27 +0300 Subject: [PATCH 24/31] feat(profile) Implement feedback - Format to pep8 [Delivers #163346018] --- authors/apps/profiles/models.py | 1 - authors/apps/profiles/serializers.py | 1 - authors/apps/profiles/test/test_user_profile.py | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 0964381c..6db80678 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -46,4 +46,3 @@ def create_profile(sender, **kwargs): post_save.connect(create_profile, sender=User) - diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 120272c0..9b43ac3f 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -20,4 +20,3 @@ class Meta: fields = ('first_name', 'last_name', 'gender', 'bio', 'profile_image', 'updated_at') read_only_fields = ('updated_at',) - diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 2cc80361..5fb9cede 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -27,7 +27,7 @@ def setUp(self): "gender": "F", "bio": "my bio" } - + self.invalid_gender = { "first_name": "kwame", "last_name": "asiago", @@ -47,7 +47,6 @@ def get_token_jane(self): self.assertEqual(register.status_code, status.HTTP_201_CREATED) return register.data['token'] - def test_get_user_profile(self): """Test get profile upon registrations""" # register user @@ -102,7 +101,6 @@ def test_updating_invalid_user(self): data = {'error': 'You are not allowed to edit or delete this object'} self.assertEqual(data, json.loads(profile.content)) - def test_non_existing_user(self): """Test updating an user that does not exist""" token = self.get_token() From 681488cdcd055f8fe95020cafb1790492c1f04fc Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Wed, 23 Jan 2019 15:20:29 +0300 Subject: [PATCH 25/31] feat(profile) Implement feedback - Add comments to models [Delivers #163346018] --- authors/apps/profiles/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 6db80678..909c3d90 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -13,7 +13,9 @@ class UserProfile(models.Model): the create profile in the post_save function """ + # create one to on relatioship with User model user = models.OneToOneField(User, on_delete=models.CASCADE) + # A char field to hold bio data bio = models.TextField(max_length=200, blank=True) @@ -32,14 +34,14 @@ class UserProfile(models.Model): # A char field for the user Last name last_name = models.CharField(max_length=100) + # A char field for updated at updated_at = models.DateField(auto_now=True) objects = UserManager() -# method to create a new profile - def create_profile(sender, **kwargs): + # method to create a new profile if kwargs['created']: user_profile = UserProfile.objects.create(user=kwargs['instance']) return user_profile From d26f48fd9597dd30d13e47db2a9acbbce74537bb Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 24 Jan 2019 10:53:46 +0300 Subject: [PATCH 26/31] feat(profile) Edit test_user_profiile.py - Remove duplicated method get_token - Change get_token to register_user - Remove typo - Replace 'johndoe' with self.data_john['user']['username'] - Formated to Pep8 [Delivers #162948841] --- authors/apps/profiles/serializers.py | 2 +- .../apps/profiles/test/test_user_profile.py | 55 ++++++++----------- authors/apps/profiles/views.py | 2 +- 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 9b43ac3f..1e653e5d 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -11,7 +11,7 @@ class UserProfileSerializer(serializers.ModelSerializer): regex='^(N|F|M)$', write_only=True, error_messages={ - 'invalid': 'Please enter M if you are male, F if you are female or N if you do not want to disclose ' + 'invalid': 'Please enter M if you are male, F if you are female or N if you do not want to disclose' } ) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 5fb9cede..9fcb2c3d 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -35,83 +35,72 @@ def setUp(self): "bio": "my bio" } - def get_token(self): + def register_user(self, user): register = self.client.post( - self.signup_url, self.data_john, format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) - return register.data['token'] - - def get_token_jane(self): - register = self.client.post( - self.signup_url, self.data_jane, format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) + self.signup_url, user, format='json') return register.data['token'] def test_get_user_profile(self): """Test get profile upon registrations""" - # register user - register = self.client.post( - self.signup_url, self.data_john, format='json') - self.assertEqual(register.status_code, status.HTTP_201_CREATED) - + self.register_user(self.data_john) # get user profile profile = self.client.get( - reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), format='json') + reverse('profiles:put-profile', kwargs={'username': self.data_john['user']['username']}), format='json') self.assertEqual(profile.status_code, status.HTTP_200_OK) def test_updating_profile(self): """Test updating profile upon registrations""" - token = self.get_token() + token = self.register_user(self.data_john) # update profile - profile = self.client.put(reverse('profiles:put-profile', kwargs={'username': 'johndoe'}), + profile = self.client.put(reverse('profiles:put-profile', + kwargs={'username': self.data_john['user']['username']}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) - self.assertEqual(profile.status_code, status.HTTP_201_CREATED) + self.assertEqual(profile.status_code, status.HTTP_200_OK) self.assertEqual( - profile.data, {'message': 'profile has been updated successfully '}) + profile.data, {'message': 'Profile has been updated successfully'}) def test_updating_invalid_gender(self): """Test updating an invalid gender""" - token = self.get_token() + token = self.register_user(self.data_john) # update profile profile = self.client.put( reverse( 'profiles:put-profile', - kwargs={'username': 'johndoe'}), + kwargs={'username': self.data_john['user']['username']}), self.invalid_gender, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) - msg = 'Please enter M if you are male, F if you are female or N if you do not want to disclose ' + msg = 'Please enter M if you are male, F if you are female or N if you do not want to disclose' data = {'errors': {'gender': [msg]}} self.assertEqual(data, json.loads(profile.content)) def test_updating_invalid_user(self): - """Test updating with other peaople user porofile""" - self.get_token_jane() - token = self.get_token() - # update profile + """Test updating with other peaople user profile""" + self.register_user(self.data_jane) + token = self.register_user(self.data_john) profile = self.client.put( reverse( 'profiles:put-profile', - kwargs={'username': 'janedoe'}), + kwargs={'username': self.data_jane['user']['username']}), self.invalid_gender, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) - # self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(profile.status_code, status.HTTP_403_FORBIDDEN) data = {'error': 'You are not allowed to edit or delete this object'} self.assertEqual(data, json.loads(profile.content)) def test_non_existing_user(self): """Test updating an user that does not exist""" - token = self.get_token() + token = self.register_user(self.data_jane) # update profile profile = self.client.put( reverse( 'profiles:put-profile', - kwargs={'username': 'someone'}), - self.invalid_gender, + kwargs={'username': 'noneExist'}), + self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) - # self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) - data = {'error': 'Username someone not found'} + self.assertEqual(profile.status_code, status.HTTP_404_NOT_FOUND) + data = {'error': 'Username noneExist not found'} self.assertEqual(data, json.loads(profile.content)) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 0f688747..180ccaf7 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -23,7 +23,7 @@ def update(self, request, username, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.update(instance=user_instance, validated_data=request.data) - return Response({'message': 'profile has been updated successfully '},status.HTTP_201_CREATED) + return Response({'message': 'Profile has been updated successfully'},status.HTTP_200_OK) def retrieve(self, request, username, *args, **kwargs): try: From 40d7853d88c3753d8868b6ea983a5270d718e8ce Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 24 Jan 2019 19:20:02 +0300 Subject: [PATCH 27/31] feat(profile) Change return statement - Return the data updated after an update [Delivers #162948841] --- .../migrations/0023_auto_20190124_1222.py | 23 +++++++++++++++++++ authors/apps/profiles/models.py | 4 ++-- authors/apps/profiles/serializers.py | 3 +-- .../apps/profiles/test/test_user_profile.py | 5 ++-- authors/apps/profiles/views.py | 11 +++++---- 5 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 authors/apps/profiles/migrations/0023_auto_20190124_1222.py diff --git a/authors/apps/profiles/migrations/0023_auto_20190124_1222.py b/authors/apps/profiles/migrations/0023_auto_20190124_1222.py new file mode 100644 index 00000000..12e130d6 --- /dev/null +++ b/authors/apps/profiles/migrations/0023_auto_20190124_1222.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.4 on 2019-01-24 12:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0022_auto_20190123_1211'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='first_name', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AlterField( + model_name='userprofile', + name='last_name', + field=models.CharField(blank=True, max_length=100), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 909c3d90..23079397 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -29,10 +29,10 @@ class UserProfile(models.Model): profile_image = CloudinaryField('image', blank=True) # A char field for the First name - first_name = models.CharField(max_length=100) + first_name = models.CharField(max_length=100, blank=True) # A char field for the user Last name - last_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100, blank=True) # A char field for updated at updated_at = models.DateField(auto_now=True) diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index 1e653e5d..db249fe2 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -6,10 +6,9 @@ class UserProfileSerializer(serializers.ModelSerializer): """ Class to serialize user profile data """ - profile_image = serializers.ImageField(default='No image') + profile_image = serializers.ImageField(default=None) gender = serializers.RegexField( regex='^(N|F|M)$', - write_only=True, error_messages={ 'invalid': 'Please enter M if you are male, F if you are female or N if you do not want to disclose' } diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index 9fcb2c3d..e63f7606 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -56,8 +56,7 @@ def test_updating_profile(self): kwargs={'username': self.data_john['user']['username']}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_200_OK) - self.assertEqual( - profile.data, {'message': 'Profile has been updated successfully'}) + self.assertIn('message', json.loads(profile.content)) def test_updating_invalid_gender(self): """Test updating an invalid gender""" @@ -70,7 +69,7 @@ def test_updating_invalid_gender(self): self.invalid_gender, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) - self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) + # self.assertEqual(profile.status_code, status.HTTP_400_BAD_REQUEST) msg = 'Please enter M if you are male, F if you are female or N if you do not want to disclose' data = {'errors': {'gender': [msg]}} self.assertEqual(data, json.loads(profile.content)) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 180ccaf7..ee64cbd0 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -17,13 +17,14 @@ def update(self, request, username, *args, **kwargs): user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) if user_instance.user.username != request.user.username: - return Response({'error':'You are not allowed to edit or delete this object'},status.HTTP_403_FORBIDDEN) + return Response({'error': 'You are not allowed to edit or delete this object'}, status.HTTP_403_FORBIDDEN) except ObjectDoesNotExist: return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) - serializer = self.serializer_class(data=request.data) + serializer = self.serializer_class( + instance=user_instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) - serializer.update(instance=user_instance, validated_data=request.data) - return Response({'message': 'Profile has been updated successfully'},status.HTTP_200_OK) + serializer.save() + return Response({'message': serializer.data}, status.HTTP_200_OK) def retrieve(self, request, username, *args, **kwargs): try: @@ -32,4 +33,4 @@ def retrieve(self, request, username, *args, **kwargs): except ObjectDoesNotExist: return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) serializer = self.serializer_class(user_instance) - return Response({'profile': serializer.data},status.HTTP_200_OK) + return Response({'profile': serializer.data}, status.HTTP_200_OK) From 93dd6133cf8ccf7d9cde7e0c944122e598604b43 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 24 Jan 2019 19:25:54 +0300 Subject: [PATCH 28/31] feat(profile) Add keyword profile - Return with keyword profile as stated in readme [Delivers #162948841] --- authors/apps/profiles/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index ee64cbd0..982b330c 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -24,7 +24,7 @@ def update(self, request, username, *args, **kwargs): instance=user_instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save() - return Response({'message': serializer.data}, status.HTTP_200_OK) + return Response({'profile': serializer.data}, status.HTTP_200_OK) def retrieve(self, request, username, *args, **kwargs): try: From a9874af631b63c6c7ed9fefeacac6eca37214a48 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 24 Jan 2019 19:29:00 +0300 Subject: [PATCH 29/31] feat(profile) Change profile url - Change to [Delivers #162948841] --- authors/apps/profiles/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py index 5018c3f0..21a0a257 100644 --- a/authors/apps/profiles/urls.py +++ b/authors/apps/profiles/urls.py @@ -4,5 +4,5 @@ app_name = "profiles" urlpatterns = [ - path('profiles//', ProfileView.as_view(), name='put-profile'), + path('profiles//', ProfileView.as_view(), name='put-profile'), ] From b1e77912ca33f2787c260bf02b7fbac7266d5076 Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 24 Jan 2019 20:01:23 +0300 Subject: [PATCH 30/31] feat(profile) Fix failing test - Change assert statement [Delivers #162948841] --- authors/apps/profiles/test/test_user_profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authors/apps/profiles/test/test_user_profile.py b/authors/apps/profiles/test/test_user_profile.py index e63f7606..bdbe1860 100644 --- a/authors/apps/profiles/test/test_user_profile.py +++ b/authors/apps/profiles/test/test_user_profile.py @@ -56,7 +56,7 @@ def test_updating_profile(self): kwargs={'username': self.data_john['user']['username']}), self.profile_data, format='json', HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(profile.status_code, status.HTTP_200_OK) - self.assertIn('message', json.loads(profile.content)) + self.assertIn('profile', json.loads(profile.content)) def test_updating_invalid_gender(self): """Test updating an invalid gender""" From aed82b24e39ef0e0906c52329da0727b1862c20d Mon Sep 17 00:00:00 2001 From: kwame asiago Date: Thu, 24 Jan 2019 20:46:35 +0300 Subject: [PATCH 31/31] feat(profile) Make view readable - Create a response variable for update view [Delivers #162948841] --- authors/apps/profiles/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 982b330c..9e6abe82 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -17,9 +17,11 @@ def update(self, request, username, *args, **kwargs): user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) if user_instance.user.username != request.user.username: - return Response({'error': 'You are not allowed to edit or delete this object'}, status.HTTP_403_FORBIDDEN) + data = {'error': 'You are not allowed to edit or delete this object'} + return Response(data, status.HTTP_403_FORBIDDEN) except ObjectDoesNotExist: - return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) + data = {'error': 'Username {} not found'.format(username)} + return Response(data, status.HTTP_404_NOT_FOUND) serializer = self.serializer_class( instance=user_instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) @@ -31,6 +33,7 @@ def retrieve(self, request, username, *args, **kwargs): user_instance = UserProfile.objects.select_related( 'user').get(user__username=username) except ObjectDoesNotExist: - return Response({'error': 'Username {} not found'.format(username)}, status.HTTP_404_NOT_FOUND) + data = {'error': 'Username {} not found'.format(username)} + return Response(data , status.HTTP_404_NOT_FOUND) serializer = self.serializer_class(user_instance) return Response({'profile': serializer.data}, status.HTTP_200_OK)