Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ psycopg2 = "==2.7.5"
django-cors-headers = "==3.0.2"
gunicorn = "==19.9.0"
django-heroku = "==0.3.1"
django-phonenumber-field = "==3.0.1"
phonenumbers = "==8.10.14"

[requires]
python_version = "3.7"
93 changes: 58 additions & 35 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: gunicorn app.wsgi
web: gunicorn dscountr.wsgi
5 changes: 5 additions & 0 deletions app/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class MyappConfig(AppConfig):
name = 'app'
Empty file added app/authentication/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions app/authentication/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register models here.
5 changes: 5 additions & 0 deletions app/authentication/choices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
('A', 'Alien'),
)
43 changes: 43 additions & 0 deletions app/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 2.2.3 on 2019-07-02 20:00

import app.authentication.models
from django.db import migrations, models
import phonenumber_field.modelfields


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0011_update_proxy_permissions'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(db_index=True, max_length=255, unique=True)),
('email', models.EmailField(db_index=True, max_length=254, unique=True)),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(db_index=True, max_length=128, region=None, unique=True)),
('date_of_birth', models.DateField()),
('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female')], max_length=1)),
('password', models.CharField(max_length=128)),
('is_active', models.BooleanField(default=True)),
('is_staff', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name_plural': 'All Users',
},
managers=[
('objects', app.authentication.models.UserManager()),
],
),
]
Empty file.
85 changes: 85 additions & 0 deletions app/authentication/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin
)
from django.db import models
from django.core.exceptions import ValidationError
from decouple import config
from phonenumber_field.modelfields import PhoneNumberField
from firebase_admin import auth
from .choices import GENDER_CHOICES
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer absolute imports over relative imports
https://www.python.org/dev/peps/pep-0008/#imports



class UserManager(BaseUserManager):
use_in_migrations = True

def createuser(self, **fields):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like we're using the token param being passed to this function

email = fields.pop('email')
password = fields.get('password')

if not email:
raise ValueError("Email address is required")
email = self.normalize_email(email)
user = self.model(email=email, **fields)
user.set_password(password)
user.save(using=self._db)
return user

def create_superuser(self, **fields):
"""
Create and return a `User` with superuser (admin) permissions.
"""

user = self.createuser(**fields)
user.is_superuser = True
user.is_staff = True
user.save()

return user


class User(AbstractBaseUser, PermissionsMixin):

username = models.CharField(db_index=True, max_length=255, unique=True)
email = models.EmailField(db_index=True, unique=True)
phone_number = PhoneNumberField(db_index=True, unique=True)
date_of_birth = models.DateField()
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
password = models.CharField(max_length=128)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

# The `USERNAME_FIELD` property specifies the log in field.
USERNAME_FIELD = 'phone_number'
REQUIRED_FIELDS = ['username', 'email', 'date_of_birth', ]

# the UserManager class should manage objects of this type.
objects = UserManager()

class Meta:
verbose_name_plural = "All Users"

def __str__(self):
"""
Returns a string representation of this `User`.
used when a `User` is printed in the console.
"""
return self.email

@property
def token(self):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the token we get from firebase used for and how long does it take to expire?

Either way, unless you're sure you'll get the same token from firebase each time we call it, we should change how we're storing it otherwise it'll change each time you access this property.

"""
method allows us to get a user's token
"""
return self._generate_firebase_token()

def _generate_firebase_token(self):
"""
Generates a firebase token
"""
uid = config('UID')
token = auth.create_custom_token(uid)

return token.decode('utf-8')
29 changes: 29 additions & 0 deletions app/authentication/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from rest_framework import serializers
from django.contrib.auth import authenticate
from rest_framework.validators import UniqueValidator
from .models import User
from firebase_admin import auth
from phonenumber_field.serializerfields import PhoneNumberField


class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=128,
min_length=8,
write_only=True
)

token = serializers.CharField(max_length=255, read_only=True)
phone_number = PhoneNumberField(
validators=[
UniqueValidator(queryset=User.objects.all(),
message='User with this Phone Number already exists.',
)],)

class Meta:
model = User
fields = ['id', 'email', 'username', 'password', 'token',
'phone_number', 'date_of_birth', 'gender']

def create(self, validated_data):
return User.objects.createuser(**validated_data)
3 changes: 3 additions & 0 deletions app/authentication/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Write tests here.
17 changes: 17 additions & 0 deletions app/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import RegistrationViewSet

app_name = 'authentication'


class OptionalTrailingSlashRouter(SimpleRouter):
def __init_(self, trailing_slash='/?'):
self.trailing_slash = trailing_slash
super().__init__()


router = OptionalTrailingSlashRouter()
router.register('users', RegistrationViewSet, 'user')

urlpatterns = router.urls
18 changes: 18 additions & 0 deletions app/authentication/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from .serializers import (RegistrationSerializer,)
from . import models


class RegistrationViewSet(ModelViewSet):
permission_classes = (AllowAny,)
serializer_class = RegistrationSerializer
queryset = models.User.objects.all()
http_method_names = ['post']


class UserViewSet(ModelViewSet):
permission_classes = (IsAdminUser, IsAuthenticated)
serializer_class = RegistrationSerializer
queryset = models.User.objects.all()
http_method_names = ['post', 'get', 'patch']
Loading