Преглед на файлове

Merge branch 'kaajavi/dev' of kaajavi/BelvoApp into develop

Javier Guignard преди 5 години
родител
ревизия
b43796ca66

+ 6 - 2
Readme.md

@@ -2,7 +2,10 @@
 
 
 Author: Javier Guignard
 Author: Javier Guignard
 
 
-You can see demo in challenge.kaajavi.com
+You can see demo in https://demo.kaajavi.com/
+
+If you want to see the admin panel, 
+the default email is `admin@admin.com` and their password is `admin`
 
 
 ## Setup
 ## Setup
 
 
@@ -40,7 +43,8 @@ The home page is the API documentation.
 
 
 ## What do you need know about the challenge
 ## What do you need know about the challenge
 
 
-I didn't set up the authentication by JWT or similar, so the API is open for everybody.
+I remove the session authentication and I didn't set up the authentication by JWT
+or similar token, so the API is open for everybody.
 
 
 
 
 ## To run in docker-compose
 ## To run in docker-compose

+ 4 - 10
application/settings.py

@@ -15,7 +15,6 @@ from pathlib import Path
 # Build paths inside the project like this: BASE_DIR / 'subdir'.
 # Build paths inside the project like this: BASE_DIR / 'subdir'.
 BASE_DIR = Path(__file__).resolve().parent.parent
 BASE_DIR = Path(__file__).resolve().parent.parent
 
 
-
 # Quick-start development settings - unsuitable for production
 # Quick-start development settings - unsuitable for production
 # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
 # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
 
 
@@ -27,9 +26,8 @@ DEBUG = True
 
 
 ALLOWED_HOSTS = ['*']
 ALLOWED_HOSTS = ['*']
 
 
-
 # Application definition
 # Application definition
-#Django applications
+# Django applications
 DJ_APPS = [
 DJ_APPS = [
     'django.contrib.admin',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.auth',
@@ -38,12 +36,12 @@ DJ_APPS = [
     'django.contrib.messages',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'django.contrib.staticfiles',
 ]
 ]
-#3th part applications
+# 3th part applications
 TP_APPS = [
 TP_APPS = [
     'rest_framework',
     'rest_framework',
     'django_extensions',
     'django_extensions',
 ]
 ]
-#Work applications
+# Work applications
 WT_APPS = [
 WT_APPS = [
     'users',
     'users',
     'transactions',
     'transactions',
@@ -81,7 +79,6 @@ TEMPLATES = [
 
 
 WSGI_APPLICATION = 'application.wsgi.application'
 WSGI_APPLICATION = 'application.wsgi.application'
 
 
-
 # Database
 # Database
 # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
 # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
 
 
@@ -92,7 +89,6 @@ DATABASES = {
     }
     }
 }
 }
 
 
-
 # Password validation
 # Password validation
 # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
 # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
 
 
@@ -111,7 +107,6 @@ AUTH_PASSWORD_VALIDATORS = [
     },
     },
 ]
 ]
 
 
-
 # Internationalization
 # Internationalization
 # https://docs.djangoproject.com/en/3.1/topics/i18n/
 # https://docs.djangoproject.com/en/3.1/topics/i18n/
 
 
@@ -125,7 +120,6 @@ USE_L10N = True
 
 
 USE_TZ = True
 USE_TZ = True
 
 
-
 # Static files (CSS, JavaScript, Images)
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/3.1/howto/static-files/
 # https://docs.djangoproject.com/en/3.1/howto/static-files/
 
 
@@ -133,4 +127,4 @@ STATIC_URL = '/static/'
 
 
 AUTH_USER_MODEL = 'users.User'
 AUTH_USER_MODEL = 'users.User'
 
 
-REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' }
+REST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'}

+ 4 - 2
application/urls.py

@@ -21,14 +21,16 @@ from transactions.urls import router as transaction_router
 from rest_framework.documentation import include_docs_urls
 from rest_framework.documentation import include_docs_urls
 
 
 title = format_html('''API Documentation <span style="font-size:11px">
 title = format_html('''API Documentation <span style="font-size:11px">
-By <a style="font-size:10px" target="_blank" href="https://www.linkedin.com/in/javierguignard">Javier Guignard 
+By <a style="font-size:10px" target="_blank" href="https://www.linkedin.com/in/javierguignard">
+Javier Guignard 
 <i class="fa fa-linkedin-square" aria-hidden="true"></i> </a></style>''')
 <i class="fa fa-linkedin-square" aria-hidden="true"></i> </a></style>''')
 # Wire up our API using automatic URL routing.
 # Wire up our API using automatic URL routing.
 # Additionally, we include login URLs for the browsable API.
 # Additionally, we include login URLs for the browsable API.
 urlpatterns = [
 urlpatterns = [
     path('', include_docs_urls(title=title)),
     path('', include_docs_urls(title=title)),
     path('api/users/', include((user_router.urls, 'users'),namespace='users_path')),
     path('api/users/', include((user_router.urls, 'users'),namespace='users_path')),
-    path('api/transactions/', include((transaction_router.urls, 'transactions'), namespace='transactions')),
+    path('api/transactions/', include((transaction_router.urls, 'transactions'),
+                                      namespace='transactions')),
     path('admin/', admin.site.urls),
     path('admin/', admin.site.urls),
     path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
     path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
 ]
 ]

+ 4 - 1
transactions/admin.py

@@ -1,4 +1,7 @@
+"""
+Admin registry file
+"""
 from django.contrib import admin
 from django.contrib import admin
 from .models import Transaction
 from .models import Transaction
 # Register your models here.
 # Register your models here.
-admin.site.register(Transaction)
+admin.site.register(Transaction)

+ 6 - 0
transactions/apps.py

@@ -1,7 +1,13 @@
+"""
+App Config file
+"""
 from django.apps import AppConfig
 from django.apps import AppConfig
 
 
 
 
 class TransactionsConfig(AppConfig):
 class TransactionsConfig(AppConfig):
+    """
+    Transactions app config class
+    """
     name = 'transactions'
     name = 'transactions'
     verbose_name = "Transaction"
     verbose_name = "Transaction"
     verbose_name_plural= "Transactions"
     verbose_name_plural= "Transactions"

+ 6 - 1
transactions/mixins.py

@@ -1,10 +1,15 @@
+"""
+Transactions mixins
+"""
 class TransactionsMixin:
 class TransactionsMixin:
     """
     """
     TransactionMixin are essentially just a type of class mixin
     TransactionMixin are essentially just a type of class mixin
     when define common functions for all Viewsets
     when define common functions for all Viewsets
     """
     """
-
     def get_filtered_transactions(self):
     def get_filtered_transactions(self):
+        """
+        Get user transactions queryset filtered
+        """
         date_from = self.request.query_params.get('date_from', None)
         date_from = self.request.query_params.get('date_from', None)
         date_to = self.request.query_params.get('date_from', None)
         date_to = self.request.query_params.get('date_from', None)
         transactions = self.user.transactions
         transactions = self.user.transactions

+ 9 - 3
transactions/models.py

@@ -1,13 +1,15 @@
+"""
+Transaction Modles
+"""
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
-from django.utils.timezone import now
-User = get_user_model()
 
 
+User = get_user_model()
 
 
-# Create your models here.
 def validate_type(value):
 def validate_type(value):
+    """Validate type value"""
     if value not in ('inflow', 'outflow'):
     if value not in ('inflow', 'outflow'):
         raise ValidationError(
         raise ValidationError(
             _('%(value)s is not an even number'),
             _('%(value)s is not an even number'),
@@ -16,6 +18,9 @@ def validate_type(value):
 
 
 
 
 class Transaction(models.Model):
 class Transaction(models.Model):
+    """
+    Transaction core model
+    """
     user = models.ForeignKey(User,related_name='transactions', on_delete=models.CASCADE)
     user = models.ForeignKey(User,related_name='transactions', on_delete=models.CASCADE)
     reference = models.CharField(_('Transaction reference'), max_length=8, unique=True,
     reference = models.CharField(_('Transaction reference'), max_length=8, unique=True,
                                  help_text=_('Unique value'))
                                  help_text=_('Unique value'))
@@ -27,5 +32,6 @@ class Transaction(models.Model):
     amount = models.FloatField(_('Amount'), max_length=255,
     amount = models.FloatField(_('Amount'), max_length=255,
                                help_text=_('''Only negative numbers for "outflow" and
                                help_text=_('''Only negative numbers for "outflow" and
                                            positive numbers for "inflow"'''))
                                            positive numbers for "inflow"'''))
+
     category = models.CharField(_('Category'), max_length=255, help_text=_('Category Name'))
     category = models.CharField(_('Category'), max_length=255, help_text=_('Category Name'))
     date = models.DateField('Date',help_text=_('Transaction date. YYYY-MM-DD format.'))
     date = models.DateField('Date',help_text=_('Transaction date. YYYY-MM-DD format.'))

+ 28 - 11
transactions/serializers.py

@@ -1,45 +1,62 @@
+"""
+Transactions app serializer
+"""
 from rest_framework import serializers
 from rest_framework import serializers
-from .models import Transaction
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
+from .models import Transaction
 
 
 User = get_user_model()
 User = get_user_model()
 
 
 
 
 class CreateTransactionSerializer(serializers.ModelSerializer):
 class CreateTransactionSerializer(serializers.ModelSerializer):
+    """
+    Create transaction serializer
+    """
     user_id = serializers.PrimaryKeyRelatedField(
     user_id = serializers.PrimaryKeyRelatedField(
         queryset=User.objects.all(), source='user', write_only=True, help_text='User Id')
         queryset=User.objects.all(), source='user', write_only=True, help_text='User Id')
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         many = kwargs.pop('many', True)
         many = kwargs.pop('many', True)
-        super(CreateTransactionSerializer, self).__init__(many=many, *args, **kwargs)
+        super().__init__(many=many, *args, **kwargs)
 
 
     def validate(self, data):
     def validate(self, data):
         """
         """
         Check the type is outflow or inflow
         Check the type is outflow or inflow
         Check the inflow is positive and outflow is negative
         Check the inflow is positive and outflow is negative
         """
         """
-        if data['type'] not in ('outflow','inflow'):
-            raise serializers.ValidationError({"type": _("Must be `outflow` or `inflow` ")})
+        if data['type'] not in ('outflow', 'inflow'):
+            raise serializers.ValidationError(
+                {"type": _("Must be `outflow` or `inflow` ")})
         if data['amount'] > 0 and data['type'] == 'outflow':
         if data['amount'] > 0 and data['type'] == 'outflow':
-            raise serializers.ValidationError({"amount":_("Outflow type only allow negatives amount")})
+            raise serializers.ValidationError(
+                {"amount": _("Outflow type only allow negatives amount")})
         if data['amount'] < 0 and data['type'] == 'inflow':
         if data['amount'] < 0 and data['type'] == 'inflow':
-            raise serializers.ValidationError({"amount":_("Inflow type only allow positives amount")})
+            raise serializers.ValidationError(
+                {"amount": _("Inflow type only allow positives amount")})
         return data
         return data
 
 
     class Meta:
     class Meta:
+        """
+        Meta class definitions
+        """
         model = Transaction
         model = Transaction
-        fields = ('user_id', 'reference', 'account', 'type', 'amount', 'category','date')
+        fields = ('user_id', 'reference',
+                  'account', 'type', 'amount',
+                  'category', 'date')
 
 
 
 
 class BalanceByAccountSerializer(serializers.ModelSerializer):
 class BalanceByAccountSerializer(serializers.ModelSerializer):
-
+    """
+    Balanced by account serializer
+    """
     balance = serializers.FloatField(label='balance', read_only=True)
     balance = serializers.FloatField(label='balance', read_only=True)
     total_inflow = serializers.FloatField(label='balance', read_only=True)
     total_inflow = serializers.FloatField(label='balance', read_only=True)
     total_outflow = serializers.FloatField(label='balance', read_only=True)
     total_outflow = serializers.FloatField(label='balance', read_only=True)
 
 
     class Meta:
     class Meta:
+        """
+        Meta class definitions
+        """
         model = Transaction
         model = Transaction
-        fields = ['account', 'balance','total_inflow','total_outflow']
-
-
+        fields = ['account', 'balance', 'total_inflow', 'total_outflow']

+ 13 - 9
transactions/tests.py

@@ -1,14 +1,16 @@
+"""
+Tests for Transaction app
+"""
 from django.test import TestCase
 from django.test import TestCase
 from rest_framework.test import APITestCase
 from rest_framework.test import APITestCase
 from rest_framework import status
 from rest_framework import status
 from rest_framework.reverse import reverse
 from rest_framework.reverse import reverse
+from django.contrib.auth import get_user_model
 from .serializers import (CreateTransactionSerializer)
 from .serializers import (CreateTransactionSerializer)
 from .models import Transaction
 from .models import Transaction
-from django.contrib.auth import get_user_model
 
 
-User = get_user_model()
 
 
-# Create your tests here.
+User = get_user_model()
 
 
 class TransactionsSerializerTest(TestCase):
 class TransactionsSerializerTest(TestCase):
     """ Test module for test all serializers API """
     """ Test module for test all serializers API """
@@ -78,7 +80,7 @@ class TransactionsSerializerTest(TestCase):
 
 
 
 
     def setUp(self):
     def setUp(self):
-        user = User.objects.create(id=1,name='test',email='test@test.com',age=18)
+        User.objects.create(id=1,name='test',email='test@test.com',age=18)
 
 
     def test_wrong_outflow_value(self):
     def test_wrong_outflow_value(self):
         serializer = CreateTransactionSerializer(data=self.outflow_error)
         serializer = CreateTransactionSerializer(data=self.outflow_error)
@@ -146,18 +148,20 @@ class TransactionViewTest(APITestCase):
     }
     }
 
 
     def setUp(self):
     def setUp(self):
-        user = User.objects.create(id=1,name='test',email='test@test.com',age=18)
-        tr1 = Transaction.objects.create(**self.inflow_ok)
-        tr2 = Transaction.objects.create(**self.outflow_ok)
+        User.objects.create(id=1,name='test',email='test@test.com',age=18)
+        Transaction.objects.create(**self.inflow_ok)
+        Transaction.objects.create(**self.outflow_ok)
 
 
     def test_get_balance(self):
     def test_get_balance(self):
         # get API response
         # get API response
-        response = self.client.get(reverse('transactions:balance-detail', kwargs={'user_id':1}),{}, format='json')
+        response = self.client.get(reverse('transactions:balance-detail',
+                                           kwargs={'user_id':1}),{}, format='json')
         self.assertEqual(response.data, self.balance_expected)
         self.assertEqual(response.data, self.balance_expected)
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(response.status_code, status.HTTP_200_OK)
 
 
     def test_get_cashflow(self):
     def test_get_cashflow(self):
         # get API response
         # get API response
-        response = self.client.get(reverse('transactions:cashflow-detail', kwargs={'user_id':1}),{}, format='json')
+        response = self.client.get(reverse('transactions:cashflow-detail',
+                                           kwargs={'user_id':1}),{}, format='json')
         self.assertEqual(response.data, self.balance_cashflow)
         self.assertEqual(response.data, self.balance_cashflow)
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(response.status_code, status.HTTP_200_OK)

+ 4 - 2
transactions/urls.py

@@ -1,11 +1,13 @@
-from django.conf.urls import url, include
+"""
+
+"""
 from rest_framework import routers
 from rest_framework import routers
 
 
 from .views import TransactionViewSet,AccountBalanceViewSet,CashflowViewSet
 from .views import TransactionViewSet,AccountBalanceViewSet,CashflowViewSet
 
 
-app_name = 'transactions'
 router = routers.DefaultRouter()
 router = routers.DefaultRouter()
 router.register(r'', TransactionViewSet, basename='transaction')
 router.register(r'', TransactionViewSet, basename='transaction')
 router.register(r'balance', AccountBalanceViewSet, basename='balance')
 router.register(r'balance', AccountBalanceViewSet, basename='balance')
 router.register(r'cashflow', CashflowViewSet, basename='cashflow')
 router.register(r'cashflow', CashflowViewSet, basename='cashflow')
 
 
+

+ 67 - 17
transactions/views.py

@@ -1,18 +1,63 @@
+"""
+Transaction
+"""
+import coreschema
+import coreapi
 from django.http import Http404
 from django.http import Http404
 from rest_framework import viewsets, mixins
 from rest_framework import viewsets, mixins
-from rest_framework import permissions
+from django.contrib.auth import get_user_model
+from rest_framework.schemas import AutoSchema
 from rest_framework.response import Response
 from rest_framework.response import Response
-from .mixins import TransactionsMixin
 from django.db.models import (Sum, F, Case, FloatField,
 from django.db.models import (Sum, F, Case, FloatField,
                               When)
                               When)
+from .mixins import TransactionsMixin
 from .serializers import (CreateTransactionSerializer,
 from .serializers import (CreateTransactionSerializer,
                           BalanceByAccountSerializer)
                           BalanceByAccountSerializer)
 from .models import Transaction
 from .models import Transaction
-from django.contrib.auth import get_user_model
 
 
 User = get_user_model()
 User = get_user_model()
 
 
 
 
+class CustomSchema(AutoSchema):
+    """
+    Custom Scheme for documentation
+    """
+
+    def __init__(self, manual_fields=None):
+        """
+        Parameters:
+
+        Add Manual Fields
+        """
+        super().__init__(manual_fields=manual_fields)
+        self._manual_fields = [
+            coreapi.Field(
+                name='date_from',
+                location='query',
+                required=False,
+                schema=coreschema.Integer(title='date_from',
+                                          description='Date Format: `YYYY-MM-DD`')
+            ),
+            coreapi.Field(
+                name='date_to',
+                location='query',
+                required=False,
+                schema=coreschema.Integer(title='date_from',
+                                          description='Date Format: `YYYY-MM-DD`')
+            )
+        ]
+
+    def get_path_fields(self, path, method):
+        field = coreapi.Field(
+            name='user_id',
+            location='path',
+            required=True,
+            schema=coreschema.Integer(title='user_id',
+                                      description='A unique integer value identifying this user.')
+        )
+        return [field]
+
+
 class TransactionViewSet(mixins.CreateModelMixin,
 class TransactionViewSet(mixins.CreateModelMixin,
                          viewsets.GenericViewSet):
                          viewsets.GenericViewSet):
     """
     """
@@ -71,21 +116,25 @@ class AccountBalanceViewSet(viewsets.ViewSet, TransactionsMixin):
     """
     """
     permission_classes = []
     permission_classes = []
     lookup_field = 'user_id'
     lookup_field = 'user_id'
+    schema = CustomSchema()
 
 
-    def retrieve(self, request, format=None, user_id=None, *args, **kwargs):
+    def retrieve(self, request, user_id=None, *args, **kwargs):
         try:
         try:
             self.user = User.objects.get(pk=user_id)
             self.user = User.objects.get(pk=user_id)
         except User.DoesNotExist:
         except User.DoesNotExist:
             raise Http404
             raise Http404
         transactions = self.get_filtered_transactions()
         transactions = self.get_filtered_transactions()
-        with_annotations = transactions.values('account').annotate(balance=Sum('amount'),
-                                                                   total_inflow=Sum(Case(
-                                                                       When(type='inflow', then=F('amount')),
-                                                                       output_field=FloatField(),
-                                                                   )), total_outflow=Sum(Case(
-                When(type='outflow', then=F('amount')),
-                output_field=FloatField(),
-            )), )
+        with_annotations = transactions.values('account').\
+                        annotate(balance=Sum('amount'),
+                                total_inflow=Sum(Case(
+                                       When(type='inflow', then=F('amount')),
+                                       output_field=FloatField(),
+                                   )),
+                                 total_outflow=Sum(Case(
+                                    When(type='outflow', then=F('amount')),
+                                    output_field=FloatField(),
+                                )),
+                                 )
         serializer = BalanceByAccountSerializer(with_annotations, many=True)
         serializer = BalanceByAccountSerializer(with_annotations, many=True)
         return Response(serializer.data)
         return Response(serializer.data)
 
 
@@ -104,21 +153,22 @@ class CashflowViewSet(viewsets.ViewSet, TransactionsMixin):
         /api/transactions/cashflow/1/?date_from=2020-01-10
         /api/transactions/cashflow/1/?date_from=2020-01-10
 
 
     """
     """
+    schema = CustomSchema()
     permission_classes = []
     permission_classes = []
     lookup_field = 'user_id'
     lookup_field = 'user_id'
 
 
-    def retrieve(self, request, format=None, user_id=None, *args, **kwargs):
+    def retrieve(self, request, user_id=None, *args, **kwargs):
         try:
         try:
             self.user = User.objects.get(pk=user_id)
             self.user = User.objects.get(pk=user_id)
         except User.DoesNotExist:
         except User.DoesNotExist:
             raise Http404
             raise Http404
         transactions = self.get_filtered_transactions()
         transactions = self.get_filtered_transactions()
         inflows_qs = list(
         inflows_qs = list(
-            transactions.filter(type='inflow').values('category').annotate(amount=Sum('amount')).values('category',
-                                                                                                        'amount'))
+            transactions.filter(type='inflow').values('category').\
+                annotate(amount=Sum('amount')).values('category', 'amount'))
         outflow_qs = list(
         outflow_qs = list(
-            transactions.filter(type='outflow').values('category').annotate(amount=Sum('amount')).values('category',
-                                                                                                         'amount'))
+            transactions.filter(type='outflow').values('category').\
+                annotate(amount=Sum('amount')).values('category', 'amount'))
         inflow = {inf['category']: inf['amount'] for inf in inflows_qs}
         inflow = {inf['category']: inf['amount'] for inf in inflows_qs}
         outflow = {outf['category']: outf['amount'] for outf in outflow_qs}
         outflow = {outf['category']: outf['amount'] for outf in outflow_qs}
         return Response({'inflow': inflow, 'outflow': outflow})
         return Response({'inflow': inflow, 'outflow': outflow})

+ 34 - 10
users/admin.py

@@ -1,3 +1,6 @@
+"""
+Admin registry file
+"""
 from django.contrib import admin
 from django.contrib import admin
 from django.contrib.auth.forms import ReadOnlyPasswordHashField
 from django.contrib.auth.forms import ReadOnlyPasswordHashField
 from django.contrib.auth.models import Group
 from django.contrib.auth.models import Group
@@ -12,11 +15,16 @@ class UserCreationForm(forms.ModelForm):
     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
 
 
     class Meta:
     class Meta:
+        """
+        Meta class definitions
+        """
         model = User
         model = User
         fields = ('email', 'age', 'name')
         fields = ('email', 'age', 'name')
 
 
     def clean_password2(self):
     def clean_password2(self):
-        # Check that the two password entries match
+        """
+        Check that the two password entries match
+        """
         password1 = self.cleaned_data.get("password1")
         password1 = self.cleaned_data.get("password1")
         password2 = self.cleaned_data.get("password2")
         password2 = self.cleaned_data.get("password2")
         if password1 and password2 and password1 != password2:
         if password1 and password2 and password1 != password2:
@@ -25,8 +33,10 @@ class UserCreationForm(forms.ModelForm):
 
 
 
 
     def save(self, commit=True):
     def save(self, commit=True):
-        # Save the provided password in hashed format
-        user = super(UserCreationForm, self).save(commit=False)
+        """
+        Save the provided password in hashed format
+        """
+        user = super().save(commit=False)
         user.set_password(self.cleaned_data["password1"])
         user.set_password(self.cleaned_data["password1"])
         if commit:
         if commit:
             user.save()
             user.save()
@@ -39,22 +49,30 @@ class UserChangeForm(forms.ModelForm):
     password hash display field.
     password hash display field.
     """
     """
     password = ReadOnlyPasswordHashField(label=("Password"),
     password = ReadOnlyPasswordHashField(label=("Password"),
-                                         help_text=("Raw passwords are not stored, so there is no way to see "
-                                                    "this user's password, but you can change the password "
-                                                    "using <a href=\"password/\">this form</a>."))
+                     help_text=("Raw passwords are not stored, so there is no way to see "
+                                "this user's password, but you can change the password "
+                                "using <a href=\"password/\">this form</a>."))
 
 
     class Meta:
     class Meta:
+        """
+        Meta class definitions
+        """
         model = User
         model = User
         fields = ('email', 'age', 'name','password')
         fields = ('email', 'age', 'name','password')
 
 
     def clean_password(self):
     def clean_password(self):
-        # Regardless of what the user provides, return the initial value.
-        # This is done here, rather than on the field, because the
-        # field does not have access to the initial value
+        """
+        Regardless of what the user provides, return the initial value.
+        This is done here, rather than on the field, because the
+        field does not have access to the initial value
+        """
         return self.initial["password"]
         return self.initial["password"]
 
 
 
 
 class CustomUserAdmin(admin.ModelAdmin):
 class CustomUserAdmin(admin.ModelAdmin):
+    """
+    Custom user model admin class
+    """
     # The forms to add and change user instances
     # The forms to add and change user instances
     form = UserChangeForm
     form = UserChangeForm
     add_form = UserCreationForm
     add_form = UserCreationForm
@@ -79,15 +97,21 @@ class CustomUserAdmin(admin.ModelAdmin):
     filter_horizontal = ()
     filter_horizontal = ()
 
 
     def get_fieldsets(self, request, obj=None):
     def get_fieldsets(self, request, obj=None):
+        """
+        Get differents fieldset depends on exists or not object
+        """
         if not obj:
         if not obj:
             return self.add_fieldsets
             return self.add_fieldsets
         return self.fieldsets
         return self.fieldsets
 
 
     def get_form(self, request, obj=None, change=False, **kwargs):
     def get_form(self, request, obj=None, change=False, **kwargs):
+        """
+        Get differents forms depends on exists or not object
+        """
         if not obj:
         if not obj:
             return self.add_form
             return self.add_form
         return self.form
         return self.form
 
 
 
 
 admin.site.register(User, CustomUserAdmin)
 admin.site.register(User, CustomUserAdmin)
-admin.site.unregister(Group)
+admin.site.unregister(Group)

+ 7 - 1
users/apps.py

@@ -1,7 +1,13 @@
+"""
+App Config file
+"""
 from django.apps import AppConfig
 from django.apps import AppConfig
 
 
 
 
 class UsersConfig(AppConfig):
 class UsersConfig(AppConfig):
+    """
+    Users app config class
+    """
     name = 'users'
     name = 'users'
     verbose_name = "User"
     verbose_name = "User"
-    verbose_name_plural = "Users"
+    verbose_name_plural = "Users"

+ 0 - 0
users/management/__init__.py


+ 0 - 0
users/management/commands/__init__.py


+ 19 - 0
users/management/commands/create_superuser.py

@@ -0,0 +1,19 @@
+"""
+This command create superuser object.
+Run with>
+python manage.py create_superuser users
+"""
+
+from django.core.management.base import AppCommand
+from users.models import User
+
+
+class Command(AppCommand):
+
+    def handle(self, *args, **options):
+        if User.objects.filter(email='admin@admin.com').count() < 1:
+            user = User(email='admin@admin.com', name='Administrator', age=18,
+                     is_staff=True, is_superuser=True, is_active=True)
+            user.set_password('admin')
+            user.save()
+            print('User admin created')

+ 6 - 2
users/managers.py

@@ -33,11 +33,15 @@ class UserManager(BaseUserManager):
         return user
         return user
 
 
     def create_user(self, email, password=None, **extra_fields):
     def create_user(self, email, password=None, **extra_fields):
+        """
+        Function to create an user
+        """
         return self._create_user(email, password, False, False,
         return self._create_user(email, password, False, False,
                                  **extra_fields)
                                  **extra_fields)
 
 
     def create_superuser(self, email, password, **extra_fields):
     def create_superuser(self, email, password, **extra_fields):
+        """
+        Function to create an superuser
+        """
         return self._create_user(email, password, True, True,
         return self._create_user(email, password, True, True,
                                  **extra_fields)
                                  **extra_fields)
-
-

+ 8 - 3
users/models.py

@@ -1,3 +1,6 @@
+"""
+Users Models
+"""
 from django.db import models
 from django.db import models
 from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
 from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
@@ -11,12 +14,14 @@ class User(AbstractBaseUser, PermissionsMixin):
     USERNAME_FIELD = 'email'
     USERNAME_FIELD = 'email'
 
 
     objects = UserManager()
     objects = UserManager()
-    name = models.CharField(_(u'Name'), max_length=100, unique=False)
+    name = models.CharField(_(u'Name'), max_length=100, help_text='Your name here',
+                            unique=False)
 
 
     email = models.EmailField(verbose_name=_(u'Email'),
     email = models.EmailField(verbose_name=_(u'Email'),
                               max_length=255, unique=True,
                               max_length=255, unique=True,
+                              help_text='Unique field',
                               error_messages={'unique': _('This email already exists')})
                               error_messages={'unique': _('This email already exists')})
-    age = models.PositiveSmallIntegerField(_('Age'))
+    age = models.PositiveSmallIntegerField(_('Age'), help_text='Allow only positive integers')
     is_staff = models.BooleanField(
     is_staff = models.BooleanField(
         _('This user can see the admin panel?'), default=False)
         _('This user can see the admin panel?'), default=False)
-    is_active = models.BooleanField(_('Is Active'), default=False)
+    is_active = models.BooleanField(_('Is Active'), default=False)

+ 12 - 4
users/serializers.py

@@ -1,11 +1,19 @@
-from rest_framework import serializers
+"""
+Users Serializers
+"""
+from rest_framework.serializers import HyperlinkedModelSerializer
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
-User = get_user_model()
 
 
+User = get_user_model()
 
 
-class UserSerializer(serializers.HyperlinkedModelSerializer):
 
 
+class UserSerializer(HyperlinkedModelSerializer):
+    """
+    User Serializer Class
+    """
     class Meta:
     class Meta:
+        """
+        Meta class definitions
+        """
         model = User
         model = User
         fields = ['name', 'email', 'age']
         fields = ['name', 'email', 'age']
-

+ 6 - 6
users/tests.py

@@ -1,4 +1,6 @@
-from django.test import TestCase
+"""
+Test Cases
+"""
 from rest_framework.test import APITestCase
 from rest_framework.test import APITestCase
 from rest_framework import status
 from rest_framework import status
 from rest_framework.reverse import reverse
 from rest_framework.reverse import reverse
@@ -26,13 +28,14 @@ class UserViewTest(APITestCase):
     }
     }
 
 
     def setUp(self):
     def setUp(self):
-        user = User.objects.create(id=1, name='test', email='test@test.com', age=18)
+        User.objects.create(id=1, name='test', email='test@test.com', age=18)
 
 
     def test_pass_create(self):
     def test_pass_create(self):
         """
         """
         Create user test
         Create user test
         """
         """
-        response = self.client.post(reverse('users_path:users-list'), self.user_data_ok, format='json')
+        response = self.client.post(reverse('users_path:users-list'),
+                                    self.user_data_ok, format='json')
         self.assertEqual(response.data, self.user_data_ok)
         self.assertEqual(response.data, self.user_data_ok)
         self.assertEqual(User.objects.filter(email='test2@test2.com').count(), 1)
         self.assertEqual(User.objects.filter(email='test2@test2.com').count(), 1)
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
@@ -52,6 +55,3 @@ class UserViewTest(APITestCase):
         serializer = UserSerializer(data=self.user_data_duplicate_email)
         serializer = UserSerializer(data=self.user_data_duplicate_email)
         self.assertEqual(serializer.is_valid(), False)
         self.assertEqual(serializer.is_valid(), False)
         self.assertEqual(set(serializer.errors.keys()), set(['email']))
         self.assertEqual(set(serializer.errors.keys()), set(['email']))
-
-
-

+ 5 - 3
users/urls.py

@@ -1,6 +1,8 @@
-from .views import UserViewSet
+"""
+Users routes
+"""
 from rest_framework import routers
 from rest_framework import routers
+from .views import UserViewSet
 
 
-app_name = 'users'
 router = routers.DefaultRouter()
 router = routers.DefaultRouter()
-router.register(r'', UserViewSet, basename='users')
+router.register(r'', UserViewSet, basename='users')

+ 8 - 4
users/views.py

@@ -1,10 +1,14 @@
-from rest_framework import viewsets
-from rest_framework import permissions
-from .serializers import UserSerializer
+"""
+Users Views
+"""
+from rest_framework.viewsets import ModelViewSet
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
+from .serializers import UserSerializer
+
 User = get_user_model()
 User = get_user_model()
 
 
-class UserViewSet(viewsets.ModelViewSet):
+
+class UserViewSet(ModelViewSet):
     """
     """
     API endpoint that allows users to be viewed or edited.
     API endpoint that allows users to be viewed or edited.
     """
     """