diff --git a/docker-compose.yml b/docker-compose.yml
index d806e06..60fa317 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,7 +6,7 @@ services:
image: postgres
env_file: ./.enviroments/.dev
volumes:
- - postgres_data:/var/lib/postgresql/data
+ - "./data/postgres:/var/lib/postgresql/data"
acacia-back:
container_name: acacia_backend
@@ -25,7 +25,4 @@ services:
volumes:
- .:/code
depends_on:
- - db
-
-volumes:
- postgres_data:
+ - db
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index c1ed842..adf7b8a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,7 @@
[![codecov](https://codecov.io/gh/fga-eps-mds/2019.2-Acacia/branch/develop/graph/badge.svg)](https://codecov.io/gh/fga-eps-mds/2019.2-Acacia)
+[![Maintainability](https://api.codeclimate.com/v1/badges/9ceab9b0533182362c16/maintainability)](https://codeclimate.com/github/fga-eps-mds/2019.2-Acacia/maintainability)
## Visão geral
@@ -68,4 +69,4 @@ Após esses passos a aplicação deverá estar acessível em:
#### Front-end:
-Para instalar a camada front-end da aplicação basta seguir os passos de instalação descritos [aqui](https://github.com/fga-eps-mds/2019.2-Acacia-Frontend)
\ No newline at end of file
+Para instalar a camada front-end da aplicação basta seguir os passos de instalação descritos [aqui](https://github.com/fga-eps-mds/2019.2-Acacia-Frontend)
diff --git a/requirements.txt b/requirements.txt
index b4aeb67..c6327f2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,12 @@
+Pillow
+whitenoise
+flake8==3.7.8
Django==2.2.4
-djangorestframework==3.10
-django-phonenumber-field==3.0
-phonenumbers==8.10
psycopg2==2.8.3
-django-cors-headers==3.1.0
-flake8==3.7.8
-djangorestframework-simplejwt==4.3.0
coverage==4.5.4
+phonenumbers==8.10
django-localflavor==2.2
-pillow
-whitenoise
+djangorestframework==3.10
+django-cors-headers==3.1.0
+django-phonenumber-field==3.0
+djangorestframework-simplejwt==4.3.0
diff --git a/src/acacia/settings.py b/src/acacia/settings.py
index 8fd6d04..e0d5082 100644
--- a/src/acacia/settings.py
+++ b/src/acacia/settings.py
@@ -119,6 +119,8 @@
('fr-CA', _('French Canadian')),
)
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
@@ -178,8 +180,7 @@
"http://localhost:8080",
"http://45.55.46.19:8080",
"http://45.55.46.19:8081",
-
-]
+]
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=5),
diff --git a/src/acacia/urls.py b/src/acacia/urls.py
index 8287cd0..c1ada09 100644
--- a/src/acacia/urls.py
+++ b/src/acacia/urls.py
@@ -1,7 +1,7 @@
from django.contrib import admin
from django.urls import path, include
from .helpers import list_all_endpoints
-from harvest.viewsets import WeekHarvests
+from harvest.viewsets import WeekHarvests, MonthlyHarvests
urlpatterns = [
@@ -25,6 +25,12 @@
WeekHarvests.as_view({'get': 'list'}),
name='weekly_harvests'
),
+
+ path(
+ 'monthly_harvest///',
+ MonthlyHarvests.as_view({'get': 'list'}),
+ name='monthly_harvest'
+ ),
]
urlpatterns = list_all_endpoints(urlpatterns)
diff --git a/src/harvest/migrations/0001_initial.py b/src/harvest/migrations/0001_initial.py
index 867ff59..5d00cde 100644
--- a/src/harvest/migrations/0001_initial.py
+++ b/src/harvest/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.2.4 on 2019-11-16 15:35
+# Generated by Django 2.2.4 on 2019-11-19 23:47
from django.db import migrations, models
import django.db.models.deletion
diff --git a/src/harvest/models.py b/src/harvest/models.py
index 605ed3a..2e98d5a 100644
--- a/src/harvest/models.py
+++ b/src/harvest/models.py
@@ -33,17 +33,6 @@ class Harvest(models.Model):
max_volunteers = models.PositiveSmallIntegerField()
min_volunteers = models.PositiveSmallIntegerField()
- # TODO: check if this field will continue in this model
- # ACCESS_TYPES = (
- # ('Restrict Access', 'Restrict Access'),
- # ('Free Access', 'Free Access'),
- # )
-
- # access = models.CharField(
- # choices=ACCESS_TYPES,
- # max_length=15
- # )
-
def __str__(self):
return str(self.date)
diff --git a/src/harvest/tests/test_tree_views.py b/src/harvest/tests/test_tree_views.py
index b2dcb05..b7e1ead 100644
--- a/src/harvest/tests/test_tree_views.py
+++ b/src/harvest/tests/test_tree_views.py
@@ -394,3 +394,152 @@ def test_delete_harvest(self):
0,
msg='Failed to delete the harvest'
)
+
+
+class MonthlyHarvestsTestCase(APITestCase):
+
+ def create_authentication_tokens(self, user_credentials):
+ url_token = reverse('users:token_obtain_pair')
+
+ response = self.client.post(
+ url_token,
+ user_credentials,
+ format='json'
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ msg='Failed to create user tokens credentials'
+ )
+
+ self.credentials = {
+ 'HTTP_AUTHORIZATION': 'Bearer ' + response.data['access']
+ }
+
+ def create_user(self):
+ user_data = {
+ "username": 'vitas',
+ 'email': 'vitas@vitas.com',
+ 'password': 'vitasIsNice',
+ 'confirm_password': 'vitasIsNice'
+ }
+
+ url_user_signup = reverse('users:register')
+
+ response = self.client.post(
+ url_user_signup,
+ user_data,
+ format='json'
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 201,
+ msg='Failed during user creation'
+ )
+
+ user_data.pop('username')
+ user_data.pop('confirm_password')
+
+ self.create_authentication_tokens(user_data)
+
+ def create_property(self):
+ property_data = {
+ 'type_of_address': 'House',
+ 'BRZipCode': '73021498',
+ 'state': 'DF',
+ 'city': 'Gama',
+ 'district': 'Leste',
+ 'address': "Quadra 4",
+ }
+
+ url_property_creation = reverse(
+ 'property:property-list',
+ )
+
+ response = self.client.post(
+ path=url_property_creation,
+ data=property_data,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 201,
+ msg='Failed to create property'
+ )
+
+ self.property = Property.objects.get(pk=1)
+
+ def setUp(self):
+ self.create_user()
+ self.create_property()
+
+ self.harvest_data = {
+ 'date': '2019-11-21',
+ 'description': 'Apple Harvest',
+ 'status': 'Open',
+ 'max_volunteers': 10,
+ 'min_volunteers': 5,
+ }
+
+ self.url_list = reverse(
+ 'property:harvest:harvest-list',
+ kwargs={'property_pk': self.property.pk}
+ )
+
+ self.url_detail = reverse(
+ 'property:harvest:harvest-detail',
+ kwargs={
+ 'property_pk': self.property.pk,
+ 'pk': '1'
+ }
+ )
+
+ def tearDown(self):
+ Property.objects.all().delete()
+ User.objects.all().delete()
+ Harvest.objects.all().delete()
+
+ def test_get_harvests_of_the_week(self):
+
+ today = datetime.date(datetime(2020, 1, 1))
+
+ for i in range(0, 12):
+ date = today + timedelta(days=i*31)
+
+ self.harvest_data['date'] = date
+
+ response = self.client.post(
+ path=self.url_list,
+ data=self.harvest_data,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 201,
+ msg='Failed to create a harvest'
+ )
+
+ for month in range(1, 13):
+ weekly_harvests_url = reverse(
+ 'monthly_harvest',
+ kwargs={
+ 'year': date.year,
+ 'month': month
+ }
+ )
+
+ response = self.client.get(
+ path=weekly_harvests_url,
+ )
+
+ self.assertEqual(
+ len(response.data),
+ 1,
+ msg='Failed return only 1 harvest'
+ )
diff --git a/src/harvest/viewsets.py b/src/harvest/viewsets.py
index 70ba824..1635872 100644
--- a/src/harvest/viewsets.py
+++ b/src/harvest/viewsets.py
@@ -7,6 +7,7 @@
from . import serializers
import datetime
+import calendar
class HarvestViewSet(viewsets.ModelViewSet):
@@ -74,3 +75,26 @@ def get_queryset(self):
)
return queryset
+
+
+class MonthlyHarvests(ListModelMixin, viewsets.GenericViewSet):
+
+ queryset = Harvest.objects.all()
+ serializer_class = serializers.HarvestSerializer
+ permission_classes = (permissions.AllowAny,)
+
+ def get_queryset(self):
+
+ month = self.kwargs['month']
+ year = self.kwargs['year']
+
+ _, last_day = calendar.monthrange(year, month)
+
+ start_date = datetime.datetime(year, month, 1)
+ end_date = datetime.datetime(year, month, last_day)
+
+ queryset = Harvest.objects.filter(
+ date__range=(start_date, end_date)
+ )
+
+ return queryset
diff --git a/src/property/migrations/0001_initial.py b/src/property/migrations/0001_initial.py
index 250c635..43d46ae 100644
--- a/src/property/migrations/0001_initial.py
+++ b/src/property/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.2.4 on 2019-11-16 15:35
+# Generated by Django 2.2.4 on 2019-11-19 23:47
from django.conf import settings
from django.db import migrations, models
diff --git a/src/property/permissions.py b/src/property/permissions.py
index 3b99a55..ec32ade 100644
--- a/src/property/permissions.py
+++ b/src/property/permissions.py
@@ -7,7 +7,7 @@ class UserIsPropertyOwner(permissions.BasePermission):
Assumes the model instance has an `user` attribute.
"""
- def has_object_permition(self, request, view, property):
+ def has_object_permission(self, request, view, property):
return bool(
request.method in permissions.SAFE_METHODS or
request.user and
diff --git a/src/property/tests/test_models.py b/src/property/tests/test_models.py
index 72896b0..7dbed25 100644
--- a/src/property/tests/test_models.py
+++ b/src/property/tests/test_models.py
@@ -30,6 +30,13 @@ def test_verbose_name_plural(self):
_('Properties')
)
+ def test_unique_together(self):
+ self.assertEqual(
+ Property._meta.unique_together,
+ (('BRZipCode', 'type_of_address', 'address'),),
+ msg='Property unique key is not being set properly'
+ )
+
def test_property_creation(self):
self.assertEqual(
Property.objects.last(),
diff --git a/src/property/tests/test_views.py b/src/property/tests/test_views.py
index fa892a7..ab997c6 100644
--- a/src/property/tests/test_views.py
+++ b/src/property/tests/test_views.py
@@ -6,6 +6,7 @@
from property import viewsets
from property.models import Property
+from property.permissions import UserIsPropertyOwner
from users.models import User
@@ -64,6 +65,13 @@ def test_create_property(self):
response = self.view_list(request)
self.assertEqual(201, response.status_code)
+ def test_create_property_with_same_unique_key(self):
+ # changing zip code to create a unique property
+ request = self.factory.post(self.url_list, self.data)
+ force_authenticate(request, user=self.user)
+ response = self.view_list(request)
+ self.assertEqual(400, response.status_code)
+
def test_list_property(self):
request = self.factory.get(self.url_list)
force_authenticate(request, user=self.user)
@@ -80,6 +88,14 @@ def test_delete_property(self):
)
self.assertEqual(204, response.status_code)
+ def test_delete_property_without_authentication(self):
+ request = self.factory.delete(self.url_detail)
+ response = self.view_detail(
+ request,
+ pk=self.property.pk
+ )
+ self.assertEqual(401, response.status_code)
+
def test_retrieve_property(self):
request = self.factory.get(self.url_detail)
force_authenticate(request, user=self.user)
@@ -90,14 +106,65 @@ def test_retrieve_property(self):
self.assertEqual(200, response.status_code)
self.assertDictContainsSubset(self.data, response.data)
- def test_update_property(self):
+ def test_patch_update_property(self):
+ request = self.factory.patch(
+ self.url_detail,
+ {'state': 'GO'}
+ )
+ force_authenticate(request, user=self.user)
+ response = self.view_detail(
+ request,
+ pk=self.property.pk
+ )
+ self.assertEqual(200, response.status_code)
+
+ self.data['state'] = 'GO'
+
+ self.assertEqual(
+ response.data['state'],
+ self.data['state']
+ )
+
+ self.assertDictContainsSubset(self.data, response.data)
+
+ def test_put_update_property(self):
self.data['state'] = 'GO'
- request = self.factory.patch(self.url_detail, self.data)
+ self.data['city'] = 'Damianópolis'
+ request = self.factory.put(self.url_detail, self.data)
force_authenticate(request, user=self.user)
+
response = self.view_detail(
request,
pk=self.property.pk
)
+
self.assertEqual(200, response.status_code)
- self.assertEqual(response.data['state'], self.data['state'])
+
+ self.assertEqual(
+ response.data['state'],
+ self.data['state']
+ )
+
self.assertDictContainsSubset(self.data, response.data)
+
+ def test_read_only_permission(self):
+
+ another_user = User.objects.create(
+ username='MrRobot',
+ email='robot@mr.com',
+ password='hackerman'
+ )
+
+ request = self.factory.get(self.url_detail)
+ force_authenticate(request, user=another_user)
+
+ permission = UserIsPropertyOwner()
+ ans = permission.has_object_permission(
+ request,
+ self.view_detail,
+ self.property
+ )
+
+ self.assertTrue(
+ ans
+ )
diff --git a/src/tree/migrations/0001_initial.py b/src/tree/migrations/0001_initial.py
index cb87460..bd3a1eb 100644
--- a/src/tree/migrations/0001_initial.py
+++ b/src/tree/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.2.4 on 2019-11-16 15:35
+# Generated by Django 2.2.4 on 2019-11-19 23:47
from django.db import migrations, models
import django.db.models.deletion
diff --git a/src/users/admin.py b/src/users/admin.py
index 6f0a077..40609da 100644
--- a/src/users/admin.py
+++ b/src/users/admin.py
@@ -1,6 +1,6 @@
from django.contrib.auth.admin import UserAdmin
from django.contrib import admin
-from .models import User
+from .models import User, Profile
class MyUserAdmin(UserAdmin):
@@ -27,4 +27,5 @@ class MyUserAdmin(UserAdmin):
search_fields = ('email', 'username')
-admin.site.register(User, MyUserAdmin)
+admin.site.register(User, UserAdmin)
+admin.site.register(Profile)
diff --git a/src/users/migrations/0001_initial.py b/src/users/migrations/0001_initial.py
index 4e1fb5c..a11bb75 100644
--- a/src/users/migrations/0001_initial.py
+++ b/src/users/migrations/0001_initial.py
@@ -1,8 +1,10 @@
-# Generated by Django 2.2.4 on 2019-11-16 15:35
+# Generated by Django 2.2.4 on 2019-11-19 23:47
+from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
+import django.db.models.deletion
import django.utils.timezone
import phonenumber_field.modelfields
@@ -49,4 +51,18 @@ class Migration(migrations.Migration):
('objects', django.contrib.auth.models.UserManager()),
],
),
+ migrations.CreateModel(
+ name='Profile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('phone_number', models.CharField(blank=True, default='', max_length=15)),
+ ('bio', models.TextField(blank=True, default='')),
+ ('birthdate', models.DateField(null=True)),
+ ('photo', models.ImageField(blank=True, null=True, upload_to='media/profile_photo')),
+ ('is_owner', models.BooleanField(blank=True, default=False, help_text='Designates if user has a propriety')),
+ ('is_volunteer', models.BooleanField(blank=True, default=False, help_text='Designates if user is a volunteer')),
+ ('is_leader', models.BooleanField(blank=True, default=False, help_text='Designates if user is a haverst leader')),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
]
diff --git a/src/users/models.py b/src/users/models.py
index 03b03c0..9c53449 100644
--- a/src/users/models.py
+++ b/src/users/models.py
@@ -14,7 +14,6 @@ class User(AbstractUser):
'unique': 'A user with that email already exists.',
}
)
-
phone_number = PhoneNumberField(
blank=True,
null=True
@@ -60,3 +59,57 @@ class User(AbstractUser):
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
+
+
+class Profile(models.Model):
+
+ objects = models.Manager()
+
+ user = models.OneToOneField(
+ User,
+ on_delete=models.CASCADE,
+ )
+
+ phone_number = models.CharField(
+ blank=True,
+ null=False,
+ default="",
+ max_length=15
+ )
+
+ bio = models.TextField(
+ blank=True,
+ null=False,
+ default=""
+ )
+
+ birthdate = models.DateField(
+ null=True,
+ )
+
+ photo = models.ImageField(
+ upload_to='media/profile_photo',
+ blank=True,
+ null=True
+ )
+
+ is_owner = models.BooleanField(
+ default=False,
+ help_text=_('Designates if user has a propriety'),
+ blank=True,
+ null=False
+ )
+
+ is_volunteer = models.BooleanField(
+ default=False,
+ help_text=_('Designates if user is a volunteer'),
+ blank=True,
+ null=False
+ )
+
+ is_leader = models.BooleanField(
+ default=False,
+ help_text=_('Designates if user is a haverst leader'),
+ blank=True,
+ null=False
+ )
diff --git a/src/users/permissions.py b/src/users/permissions.py
new file mode 100644
index 0000000..91e8a83
--- /dev/null
+++ b/src/users/permissions.py
@@ -0,0 +1,10 @@
+from rest_framework import permissions
+
+
+class IsOwner(permissions.BasePermission):
+ """
+ Custom permisson to only allow profile owner to edit it
+ """
+
+ def has_object_permission(self, request, view, obj):
+ return obj.user == request.user
diff --git a/src/users/serializers.py b/src/users/serializers.py
index 7e04391..90376ce 100644
--- a/src/users/serializers.py
+++ b/src/users/serializers.py
@@ -1,11 +1,14 @@
from django.conf import settings
from django.utils.translation import ugettext as _
-from .models import User
+# Models
+from .models import User, Profile
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
+from datetime import date
+
class UserSignUpSerializer(serializers.Serializer):
@@ -80,6 +83,8 @@ def create(self, validated_data):
is_verified=False
)
+ Profile.objects.create(user=user)
+
# TODO: SEND CONFIRMATION EMAIL
return user
@@ -92,14 +97,90 @@ class Meta:
'username',
'email',
'password',
- 'phone_number',
- 'bio',
- 'birth',
'speaks_french',
'speaks_english',
]
+class ProfileModelSerializer(serializers.ModelSerializer):
+
+ email = serializers.EmailField(source='user.email')
+ username = serializers.CharField(source='user.username')
+
+ class Meta:
+ model = Profile
+ fields = [
+ 'photo',
+ 'birthdate',
+ 'bio',
+ 'phone_number',
+ 'email',
+ 'username',
+ ]
+
+ def check_if_is_unique(self, field, field_name):
+ """
+ This method uses class meta data to
+ checks if the field is unique on database
+
+ Args:
+ field (string): field value from Profile Serializer
+ field_name (string): field name from Profile model
+
+ Raises:
+ ValidationError: this field is already in use
+
+ Returns:
+ string: same field passed as argument
+ """
+
+ current_field = self.instance.user.__dict__.get(field_name)
+
+ filter_params = {
+ (field_name + '__iexact'): field
+ }
+
+ exclude_params = {
+ (field_name + '__iexact'): current_field
+ }
+
+ # lookin if there is any user registered
+ # with the new field
+ user = User.objects.filter(
+ **filter_params
+ ).exclude(**exclude_params)
+
+ if user:
+ raise serializers.ValidationError(
+ f'This {field_name} is already in use.'
+ )
+
+ return field
+
+ def validate_email(self, email):
+ return self.check_if_is_unique(email, 'email')
+
+ def validate_username(self, username):
+ return self.check_if_is_unique(username, 'username')
+
+ def validate_phone_number(self, phone_number):
+ if phone_number.isdigit():
+ return phone_number
+ raise serializers.ValidationError('Invalid phone number')
+
+ def validate_bio(self, bio):
+ if len(bio) <= 140:
+ return bio
+ raise serializers.ValidationError(
+ 'Bio field is longer than 140 characters'
+ )
+
+ def validate_birthdate(self, birthdate):
+ if birthdate < date.today():
+ return birthdate
+ raise serializers.ValidationError('Invalid Date')
+
+
class UserPreferedLanguage(serializers.ModelSerializer):
class Meta:
diff --git a/src/users/tests.py b/src/users/tests.py
deleted file mode 100644
index 7dbcf8f..0000000
--- a/src/users/tests.py
+++ /dev/null
@@ -1,242 +0,0 @@
-from rest_framework.test import APITestCase
-from django.urls import reverse
-from django.db import IntegrityError
-
-
-class UserRegistrationAPIViewTestCase(APITestCase):
- url = reverse('users:register')
-
- def test_different_password_on_password_confirmation(self):
- """
- Test to try to create a user with a
- wrong verification password
- """
-
- user_data = {
- "username": "vitas",
- "email": "vitas@iAmGreat.com",
- "password": "VitasIsNice",
- "confirm_password": "VitasIsAwesome"
- }
-
- response = self.client.post(self.url, user_data)
- self.assertEqual(400, response.status_code)
-
- def test_password_less_than_8_characters(self):
- """
- Test to try to create a user with a
- password less than 8 characters
- """
-
- user_data = {
- "username": "vitas",
- "email": "vitas@iAmGreat.com",
- "password": "HiVitas",
- "confirm_password": "HiVitas"
- }
-
- response = self.client.post(self.url, user_data)
- self.assertEqual(400, response.status_code)
-
- def test_unique_email_validation(self):
- """
- Test to try to create a user with a registered email
- """
-
- user1_data = {
- "username": "vitas",
- "email": "vitas@iAmGreat.com",
- "password": "VitasIsAwesome",
- "confirm_password": "VitasIsAwesome"
- }
- user2_data = {
- "username": "Reanu_Reves",
- "email": "vitas@iAmGreat.com",
- "password": "cyberpunk2077",
- "confirm_password": "cyberpunk2077"
- }
- response = self.client.post(self.url, user1_data)
-
- self.assertEqual(201, response.status_code)
-
- with self.assertRaises(IntegrityError):
- response = self.client.post(self.url, user2_data)
-
- def test_unique_username_validation(self):
- """
- Test to try to create a user with
- a registered username
- """
-
- user_data = {
- "username": "vitas",
- "email": "vitas@iAmGreat.com",
- "password": "VitasIsAwesome",
- "confirm_password": "VitasIsAwesome"
- }
-
- response = self.client.post(self.url, user_data)
- self.assertEqual(201, response.status_code)
-
- user_data = {
- "username": "vitas",
- "email": "keanu@reeves.com",
- "password": "cyberpunk2077",
- "confirm_password": "cyberpunk2077"
- }
-
- response = self.client.post(self.url, user_data)
- self.assertEqual(400, response.status_code)
-
- def test_user_registration(self):
- """
- Test to create a user with valid data
- """
-
- user_data = {
- "username": "keanu_reeves",
- "email": "keanu@reeves.com",
- "password": "cyberpunk2077",
- "confirm_password": "cyberpunk2077"
- }
-
- response = self.client.post(self.url, user_data)
- self.assertEqual(201, response.status_code)
-
-
-class UserAuthenticationAPIViewTestCase(APITestCase):
- signup_url = reverse('users:register')
- token_url = reverse('users:token_obtain_pair')
- refresh_url = reverse('users:token_refresh')
-
- user_cleber = {
- "username": "cleber",
- "email": "cleber@gmail.com",
- "password": "cleber123",
- "confirm_password": "cleber123"
- }
-
- def create_user(self, user=user_cleber):
- """
- Set's up user in database.
- """
-
- self.assertEqual(
- 201,
- self.client.post(
- self.signup_url,
- user
- ).status_code,
- msg='User setup failed'
- )
-
- def authenticate_user(self, user=user_cleber):
- """
- Authenticates a set up user and returns it's tokens
- """
-
- response = self.client.post(
- self.token_url,
- {
- "email": user['email'],
- "password": user['password']
- }
- )
-
- self.assertEqual(
- 200,
- response.status_code,
- msg='User authentication failed'
- )
-
- return response.data
-
- def test_authenticate_valid_user(self):
- """
- Test authentication of valid user
- """
-
- self.create_user(self.user_cleber)
-
- authentication_data = {
- "email": self.user_cleber['email'],
- "password": self.user_cleber['password'],
- }
-
- response = self.client.post(
- self.token_url,
- authentication_data
- )
-
- self.assertEqual(200, response.status_code)
-
- self.assertIsNotNone(
- response.data['access'],
- msg='No access token found'
- )
-
- self.assertIsNotNone(
- response.data['refresh'],
- msg='No refresh token found'
- )
-
- def test_failed_password_authentication(self):
- """
- Tests if wrong password does now allow access to user
- """
-
- self.create_user(self.user_cleber)
-
- authentication_data = {
- "email": self.user_cleber['email'],
- "password": "asdfghjkl", # Wrong password
- }
-
- response = self.client.post(
- self.token_url,
- authentication_data
- )
-
- self.assertEqual(
- 401,
- response.status_code,
- )
-
- def test_failed_email_authentication(self):
- """
- Tests if wrong password does now allow access to user
- """
-
- self.create_user(self.user_cleber)
-
- authentication_data = {
- "email": 'batata@gmail.com', # Wrong email
- "password": self.user_cleber['password'],
- }
-
- response = self.client.post(
- self.token_url,
- authentication_data
- )
-
- self.assertEqual(401, response.status_code)
-
- def test_get_access_token_from_valid_refresh_token(self):
- """
- Tests if a valid refresh token can get
- a new instance of a access token
- """
-
- self.create_user(self.user_cleber)
- tokens = self.authenticate_user(self.user_cleber)
-
- response = self.client.post(
- self.refresh_url,
- {"refresh": tokens['refresh']}
- )
-
- self.assertEqual(200, response.status_code)
- self.assertIsNotNone(
- response.data['access'],
- msg='No access token found'
- )
diff --git a/src/users/tests/__init__.py b/src/users/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/users/tests/test_viewsets.py b/src/users/tests/test_viewsets.py
new file mode 100644
index 0000000..614d7e3
--- /dev/null
+++ b/src/users/tests/test_viewsets.py
@@ -0,0 +1,541 @@
+from rest_framework.test import APITestCase
+from django.urls import reverse
+
+
+class UserRegistrationAPIViewTestCase(APITestCase):
+ url = reverse('users:register')
+
+ def test_different_password_on_password_confirmation(self):
+ """
+ Test to try to create a user with a
+ wrong verification password
+ """
+
+ user_data = {
+ "username": "vitas",
+ "email": "vitas@iAmGreat.com",
+ "password": "VitasIsNice",
+ "confirm_password": "VitasIsAwesome"
+ }
+
+ response = self.client.post(self.url, user_data)
+ self.assertEqual(400, response.status_code)
+
+ def test_password_less_than_8_characters(self):
+ """
+ Test to try to create a user with a
+ password less than 8 characters
+ """
+
+ user_data = {
+ "username": "vitas",
+ "email": "vitas@iAmGreat.com",
+ "password": "HiVitas",
+ "confirm_password": "HiVitas"
+ }
+
+ response = self.client.post(self.url, user_data)
+ self.assertEqual(400, response.status_code)
+
+ def test_unique_username_validation(self):
+ """
+ Test to try to create a user with
+ a registered username
+ """
+
+ user_data = {
+ "username": "vitas",
+ "email": "vitas@iAmGreat.com",
+ "password": "VitasIsAwesome",
+ "confirm_password": "VitasIsAwesome"
+ }
+
+ response = self.client.post(self.url, user_data)
+ self.assertEqual(201, response.status_code)
+
+ user_data = {
+ "username": "vitas",
+ "email": "keanu@reeves.com",
+ "password": "cyberpunk2077",
+ "confirm_password": "cyberpunk2077"
+ }
+
+ response = self.client.post(self.url, user_data)
+ self.assertEqual(400, response.status_code)
+
+ def test_user_registration(self):
+ """
+ Test to create a user with valid data
+ """
+
+ user_data = {
+ "username": "keanu_reeves",
+ "email": "keanu@reeves.com",
+ "password": "cyberpunk2077",
+ "confirm_password": "cyberpunk2077"
+ }
+
+ response = self.client.post(self.url, user_data)
+ self.assertEqual(201, response.status_code)
+
+
+class UserAuthenticationAPIViewTestCase(APITestCase):
+ signup_url = reverse('users:register')
+ token_url = reverse('users:token_obtain_pair')
+ refresh_url = reverse('users:token_refresh')
+
+ user_cleber = {
+ "username": "cleber",
+ "email": "cleber@gmail.com",
+ "password": "cleber123",
+ "confirm_password": "cleber123"
+ }
+
+ def create_user(self, user=user_cleber):
+ """
+ Set's up user in database.
+ """
+
+ response = self.client.post(
+ self.signup_url,
+ user
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 201,
+ msg='User setup failed'
+ )
+
+ def authenticate_user(self, user=user_cleber):
+ """
+ Authenticates a set up user and returns it's tokens
+ """
+
+ response = self.client.post(
+ self.token_url,
+ {
+ "email": user['email'],
+ "password": user['password']
+ }
+ )
+
+ self.assertEqual(
+ 200,
+ response.status_code,
+ msg='User authentication failed'
+ )
+
+ return response.data
+
+ def test_authenticate_valid_user(self):
+ """
+ Test authentication of valid user
+ """
+
+ self.create_user(self.user_cleber)
+
+ authentication_data = {
+ "email": self.user_cleber['email'],
+ "password": self.user_cleber['password'],
+ }
+
+ response = self.client.post(
+ self.token_url,
+ authentication_data
+ )
+
+ self.assertEqual(200, response.status_code)
+
+ self.assertIsNotNone(
+ response.data['access'],
+ msg='No access token found'
+ )
+
+ self.assertIsNotNone(
+ response.data['refresh'],
+ msg='No refresh token found'
+ )
+
+ def test_failed_password_authentication(self):
+ """
+ Tests if wrong password does now allow access to user
+ """
+
+ self.create_user(self.user_cleber)
+
+ authentication_data = {
+ "email": self.user_cleber['email'],
+ "password": "asdfghjkl", # Wrong password
+ }
+
+ response = self.client.post(
+ self.token_url,
+ authentication_data
+ )
+
+ self.assertEqual(
+ 401,
+ response.status_code,
+ )
+
+ def test_failed_email_authentication(self):
+ """
+ Tests if wrong password does now allow access to user
+ """
+
+ self.create_user(self.user_cleber)
+
+ authentication_data = {
+ "email": 'batata@gmail.com', # Wrong email
+ "password": self.user_cleber['password'],
+ }
+
+ response = self.client.post(
+ self.token_url,
+ authentication_data
+ )
+
+ self.assertEqual(401, response.status_code)
+
+ def test_get_access_token_from_valid_refresh_token(self):
+ """
+ Tests if a valid refresh token can get
+ a new instance of a access token
+ """
+
+ self.create_user(self.user_cleber)
+ tokens = self.authenticate_user(self.user_cleber)
+
+ response = self.client.post(
+ self.refresh_url,
+ {"refresh": tokens['refresh']}
+ )
+
+ self.assertEqual(200, response.status_code)
+ self.assertIsNotNone(
+ response.data['access'],
+ msg='No access token found'
+ )
+
+
+class ProfileUpdateAPIViewTestCase(APITestCase):
+
+ def create_authentication_tokens(self, user_credentials):
+ url_token = reverse('users:token_obtain_pair')
+
+ response = self.client.post(
+ url_token,
+ user_credentials,
+ format='json'
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ msg='Failed to create user tokens credentials'
+ )
+
+ self.credentials = {
+ 'HTTP_AUTHORIZATION': 'Bearer ' + response.data['access']
+ }
+
+ def create_user(self, data=None):
+ user_data = {
+ 'username': 'vitas',
+ 'email': 'vitas@vitas.com',
+ 'password': 'vitasIsNice',
+ 'confirm_password': 'vitasIsNice'
+ }
+
+ user_data = data if data else user_data
+
+ url_user_signup = reverse('users:register')
+
+ response = self.client.post(
+ url_user_signup,
+ user_data,
+ format='json'
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 201,
+ msg='Failed during user creation'
+ )
+
+ user_data.pop('username')
+ user_data.pop('confirm_password')
+
+ self.create_authentication_tokens(user_data)
+
+ def setUp(self):
+ self.create_user()
+ self.url_user_profile = reverse('users:profile_update')
+
+ def test_get_user_profile(self):
+
+ response = self.client.get(
+ path=self.url_user_profile,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ msg='Failed to get user profile data'
+ )
+
+ data = response.data
+
+ self.assertEqual(
+ data['bio'],
+ '',
+ msg='User profile bio isn\'t being set to empty by default'
+ )
+
+ self.assertEqual(
+ data['username'],
+ 'vitas',
+ msg='User username isn\'t being set correctly'
+ )
+
+ self.assertEqual(
+ data['email'],
+ 'vitas@vitas.com',
+ msg='User username isn\'t being set correctly'
+ )
+
+ self.assertIsNone(
+ data['photo'],
+ msg='Photo field not being set to None by default'
+ )
+
+ self.assertIsNone(
+ data['birthdate'],
+ msg='Birthdate field not being set to None by default'
+ )
+
+ def test_patch_update_user_profile(self):
+
+ data = {
+ 'birthdate': '1990-01-01',
+ 'bio': ('Vitaliy Vladasovich Grachyov, better ' +
+ 'known by his stage name Vitas, is a ' +
+ 'Russian singer, author, composer and' +
+ ' actor.')
+ }
+
+ response = self.client.patch(
+ path=self.url_user_profile,
+ data=data,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ msg='Failed to patch update user profile'
+ )
+
+ self.assertEqual(
+ response.data['birthdate'],
+ data['birthdate'],
+ msg='Failed set user birthdate'
+ )
+
+ self.assertEqual(
+ response.data['bio'],
+ data['bio'],
+ msg='Failed set user bio'
+ )
+
+ def test_change_username_to_one_already_in_use(self):
+
+ user_data = {
+ 'username': 'LeBron James',
+ 'email': 'lebron@james.com',
+ 'password': 'IamTheGoat',
+ 'confirm_password': 'IamTheGoat'
+ }
+
+ self.create_user(data=user_data)
+
+ # that username is already in use
+ update_data = {
+ 'username': 'vitas'
+ }
+
+ response = self.client.patch(
+ path=self.url_user_profile,
+ data=update_data,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 400,
+ )
+
+ self.assertEqual(
+ str(response.data['username']),
+ ("[ErrorDetail(string='This username is " +
+ "already in use.', code='invalid')]"),
+ )
+
+ def test_get_prefered_language(self):
+
+ url = reverse('users:set_prefered_language')
+
+ response = self.client.get(
+ path=url,
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ )
+
+ self.assertEqual(
+ response.data['chosen_language'],
+ 'pt',
+ )
+
+ def test_update_prefered_language(self):
+
+ url = reverse('users:set_prefered_language')
+
+ data = {
+ 'chosen_language': 'en'
+ }
+
+ response = self.client.patch(
+ path=url,
+ data=data,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ )
+
+ self.assertEqual(
+ response.data['chosen_language'],
+ 'en',
+ )
+
+ def test_access_token(self):
+
+ url = reverse('users:test_access_token')
+
+ response = self.client.get(
+ path=url,
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ )
+
+ response = self.client.get(
+ path=url,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 401,
+ )
+
+ def test_change_email_to_one_already_in_use(self):
+
+ user_data = {
+ 'username': 'LeBron James',
+ 'email': 'lebron@james.com',
+ 'password': 'IamTheGoat',
+ 'confirm_password': 'IamTheGoat'
+ }
+
+ self.create_user(data=user_data)
+
+ # that email is already in use
+ update_data = {
+ 'email': 'vitas@vitas.com',
+ }
+
+ response = self.client.patch(
+ path=self.url_user_profile,
+ data=update_data,
+ format='json',
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 400,
+ )
+
+ self.assertEqual(
+ str(response.data['email']),
+ ("[ErrorDetail(string='This email is " +
+ "already in use.', code='invalid')]"),
+ )
+
+ def test_put_update_user_profile(self):
+
+ data = {
+ 'username': 'LeBron James',
+ 'email': 'lebron@james.com',
+ 'birthdate': '1980-01-01',
+ 'bio': ('LeBron Raymone James Sr., is an ' +
+ 'American professional basketball ' +
+ 'player for the Los Angeles Lakers')
+ }
+
+ self.client.put(
+ path=self.url_user_profile,
+ data=data,
+ format='json',
+ **self.credentials,
+ )
+
+ response = self.client.get(
+ path=self.url_user_profile,
+ **self.credentials,
+ )
+
+ self.assertEqual(
+ response.status_code,
+ 200,
+ msg='Failed to put update user profile'
+ )
+
+ self.assertEqual(
+ response.data['birthdate'],
+ data['birthdate'],
+ msg='Failed set user birthdate'
+ )
+
+ self.assertEqual(
+ response.data['username'],
+ data['username'],
+ msg='Failed set user username'
+ )
+
+ self.assertEqual(
+ response.data['email'],
+ data['email'],
+ msg='Failed set user email'
+ )
+
+ self.assertEqual(
+ response.data['bio'],
+ data['bio'],
+ msg='Failed set user bio'
+ )
diff --git a/src/users/urls.py b/src/users/urls.py
index a0a0fb4..e20bbe8 100644
--- a/src/users/urls.py
+++ b/src/users/urls.py
@@ -5,6 +5,7 @@
UserRegistrationAPIView,
RetrieveUpdatePreferedLanguageAPIView,
test_access_token,
+ ProfileUpdateAPIView,
CreateAccessToken,
RefreshAccessToken,
)
@@ -42,4 +43,9 @@
test_access_token,
name='test_access_token'
),
+ path(
+ 'profile/',
+ ProfileUpdateAPIView.as_view(),
+ name='profile_update'
+ ),
]
diff --git a/src/users/viewsets.py b/src/users/viewsets.py
index d8de690..474c738 100644
--- a/src/users/viewsets.py
+++ b/src/users/viewsets.py
@@ -4,9 +4,12 @@
from rest_framework.generics import CreateAPIView, RetrieveUpdateAPIView
from rest_framework.permissions import IsAuthenticated
-from .models import User
-
-from .serializers import UserSignUpSerializer, UserPreferedLanguage
+from .models import User, Profile
+from .serializers import (
+ UserSignUpSerializer,
+ UserPreferedLanguage,
+ ProfileModelSerializer
+)
from rest_framework_simplejwt.views import (
TokenObtainPairView,
@@ -44,7 +47,7 @@ def get(self, request, *args, **kwargs):
"""
required_fields = {
- 'meta': 'Refresh token sending`refresh token` in the request body',
+ 'meta': 'Send the `refresh token` in the request body',
'refresh': '',
}
@@ -62,6 +65,32 @@ class UserRegistrationAPIView(CreateAPIView):
queryset = User.objects.all()
+class ProfileUpdateAPIView(RetrieveUpdateAPIView):
+ """
+ Endpoint for update profile info
+ """
+ permission_classes = (IsAuthenticated, )
+ serializer_class = ProfileModelSerializer
+
+ def get_object(self):
+ return Profile.objects.get(user=self.request.user.id)
+
+ def perform_update(self, serializer):
+ user_data = serializer.validated_data.pop('user', None)
+
+ if user_data:
+ username = user_data.get('username', None)
+ email = user_data.get('email', None)
+
+ user = self.request.user
+
+ user.username = username if username else user.username
+ user.email = email if email else user.email
+ user.save()
+
+ serializer.save()
+
+
class RetrieveUpdatePreferedLanguageAPIView(RetrieveUpdateAPIView):
"""
Returns a signed in users's prefered language
diff --git a/tox.ini b/tox.ini
index 52d21f9..586680f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,2 +1,2 @@
[flake8]
-exclude = .git,__pycache__,src/acacia/settings.py, */migrations/, src/users/migrations/,src/property/migrations/, src/harvest/migrations/
+exclude = __pycache__/ , settings.py, */migrations/
\ No newline at end of file