├── .gitignore ├── LICENSE.md ├── README.md └── accounts ├── __init__.py ├── admin.py ├── apps.py ├── decorators.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20211116_1049.py ├── 0003_auto_20211116_1050.py ├── 0004_auto_20211116_1051.py ├── 0005_auto_20211116_1053.py ├── 0006_user_has_profile.py ├── 0007_alter_profile_year_of_study.py └── __init__.py ├── models.py ├── requirements.txt ├── serializers.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .idea/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Melvin Thomas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-REST-TokenAuth 2 | A django app that can be added to your projects to implement TokenAuthentication. 3 | 4 | ## Features 5 | - Custom User model with email as the username field. 6 | - Extendable `UserProfile` model to add additional fields to the user. 7 | - TokenAuthentication for API endpoints. 8 | 9 | ## Before you start 10 | - please ensure that you do not have any migrations applied to the database. Otherwise, it may cause issues. 11 | - It is advised you add this app right after creating the project, before running any migrations. 12 | 13 | ## How to use 14 | 1. Clone this repository and copy the accounts directory to your project root directory. 15 | 2. Install requirements using `pip install -r accounts/requirements.txt` 16 | 3. Add the following to your INSTALLED_APPS in settings.py 17 | ```python 18 | INSTALLED_APPS = [ 19 | ... 20 | 'rest_framework', 21 | 'rest_framework.authtoken', 22 | 'import_export', 23 | 24 | 'accounts', 25 | ... 26 | ] 27 | ``` 28 | 3. Set "AUTH_USER_MODEL" in settings.py to "accounts.User" 29 | ```python 30 | AUTH_USER_MODEL = 'accounts.User' 31 | ``` 32 | 4. Add the following at the end of settings file 33 | ```python 34 | REST_FRAMEWORK = { 35 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 36 | 'rest_framework.authentication.BasicAuthentication', 37 | 'rest_framework.authentication.SessionAuthentication', 38 | 'rest_framework.authentication.TokenAuthentication', 39 | ] 40 | } 41 | ``` 42 | 5. In urls.py add the following 43 | ```python 44 | url = [ 45 | ... 46 | path('account/', include('accounts.urls')), 47 | ... 48 | ] 49 | ``` 50 | 6. Run migrations using `python manage.py migrate` 51 | 7. Now you can register a superuser using `python manage.py createsuperuser` and start using the API with TokenAuthentication. 52 | 8. Refer to the [API Documentation Available here](https://documenter.getpostman.com/view/10665006/VVBXxRK7) for more details. 53 | 54 | ## Contributing 55 | - Fork the repository 56 | - Create a new branch 57 | - Make your changes 58 | - Create a pull request 59 | 60 | 61 | ### Please Star the repo and share it with your friends if you find it useful. Follow me for more such projects. -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvinthomasdev/django-REST-TokenAuth/97112edb7bd6786f24affe00d60ebda88f79a3a7/accounts/__init__.py -------------------------------------------------------------------------------- /accounts/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | """Integrate with admin module.""" 3 | 4 | from django.contrib import admin 5 | from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin 6 | from django.utils.translation import gettext_lazy as _ 7 | from import_export import resources 8 | from import_export.admin import ImportExportModelAdmin 9 | 10 | from .models import User, Profile 11 | 12 | 13 | class UserResource(resources.ModelResource): 14 | 15 | class Meta: 16 | model = User 17 | 18 | @admin.register(User) 19 | class UserAdmin(DjangoUserAdmin, ImportExportModelAdmin): 20 | """Define admin model for custom User model with no email field.""" 21 | resource_class = UserResource 22 | fieldsets = ( 23 | (None, {'fields': ('email', 'password')}), 24 | (_('Personal info'), {'fields': ('first_name', 'last_name')}), 25 | (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 26 | 'groups', 'user_permissions')}), 27 | (_('Important dates'), {'fields': ('last_login', 'date_joined')}), 28 | ) 29 | add_fieldsets = ( 30 | (None, { 31 | 'classes': ('wide',), 32 | 'fields': ('email', 'password1', 'password2'), 33 | }), 34 | ) 35 | list_display = ('email', 'first_name', 'last_name', 'is_staff') 36 | search_fields = ('email', 'first_name', 'last_name') 37 | ordering = ('date_joined',) 38 | # readonly_fields = ('-date_joined',) 39 | 40 | 41 | class ProfileResource(resources.ModelResource): 42 | 43 | class Meta: 44 | model = Profile 45 | 46 | 47 | class ProfileAdmin(ImportExportModelAdmin): 48 | list_display = ('full_name', 'contact', 'college_name', 'year_of_study') 49 | search_fields = ('full_name', 'contact', ) 50 | 51 | 52 | admin.site.register(Profile, ProfileAdmin) -------------------------------------------------------------------------------- /accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'accounts' 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /accounts/decorators.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ObjectDoesNotExist 2 | 3 | from rest_framework.response import Response 4 | from rest_framework import status 5 | 6 | from .models import Profile 7 | 8 | 9 | def need_profile(view_function): 10 | def wrapper(request, *args, **kwargs): 11 | try: 12 | Profile.objects.get(user=request.user) 13 | except ObjectDoesNotExist: 14 | return Response({"message": "You need to complete profile first"}, status=status.HTTP_400_BAD_REQUEST) 15 | return view_function(request, *args, **kwargs) 16 | return wrapper 17 | -------------------------------------------------------------------------------- /accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-10 18:29 2 | 3 | import accounts.models 4 | from django.db import migrations, models 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('auth', '0012_alter_user_first_name_max_length'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('password', models.CharField(max_length=128, verbose_name='password')), 22 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 23 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 24 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 25 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 26 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 27 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 28 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 29 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), 30 | ('created_at', models.DateTimeField(auto_now_add=True)), 31 | ('updated_at', models.DateTimeField(auto_now=True)), 32 | ('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')), 33 | ('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')), 34 | ], 35 | options={ 36 | 'verbose_name': 'user', 37 | 'verbose_name_plural': 'users', 38 | 'abstract': False, 39 | }, 40 | managers=[ 41 | ('objects', accounts.models.UserManager()), 42 | ], 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /accounts/migrations/0002_auto_20211116_1049.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-16 05:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='college_name', 16 | field=models.CharField(default=None, max_length=150), 17 | ), 18 | migrations.AddField( 19 | model_name='user', 20 | name='contact', 21 | field=models.CharField(default=None, max_length=13), 22 | ), 23 | migrations.AddField( 24 | model_name='user', 25 | name='full_name', 26 | field=models.CharField(default=None, max_length=65), 27 | ), 28 | migrations.AddField( 29 | model_name='user', 30 | name='year_of_study', 31 | field=models.IntegerField(choices=[(1, '1st Year'), (2, '2nd Year'), (3, '3rd Year'), (4, '4th Year')], default=None), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /accounts/migrations/0003_auto_20211116_1050.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-16 05:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0002_auto_20211116_1049'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='college_name', 16 | field=models.CharField(default=None, max_length=150, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='user', 20 | name='contact', 21 | field=models.CharField(default=None, max_length=13, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='user', 25 | name='full_name', 26 | field=models.CharField(default=None, max_length=65, null=True), 27 | ), 28 | migrations.AlterField( 29 | model_name='user', 30 | name='year_of_study', 31 | field=models.IntegerField(choices=[(1, '1st Year'), (2, '2nd Year'), (3, '3rd Year'), (4, '4th Year')], default=None, null=True), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /accounts/migrations/0004_auto_20211116_1051.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-16 05:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0003_auto_20211116_1050'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='college_name', 16 | field=models.CharField(blank=True, default=None, max_length=150, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='user', 20 | name='contact', 21 | field=models.CharField(blank=True, default=None, max_length=13, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='user', 25 | name='full_name', 26 | field=models.CharField(blank=True, default=None, max_length=65, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /accounts/migrations/0005_auto_20211116_1053.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-16 05:23 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0004_auto_20211116_1051'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='user', 17 | name='college_name', 18 | ), 19 | migrations.RemoveField( 20 | model_name='user', 21 | name='contact', 22 | ), 23 | migrations.RemoveField( 24 | model_name='user', 25 | name='full_name', 26 | ), 27 | migrations.RemoveField( 28 | model_name='user', 29 | name='year_of_study', 30 | ), 31 | migrations.CreateModel( 32 | name='Profile', 33 | fields=[ 34 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('full_name', models.CharField(max_length=65)), 36 | ('contact', models.CharField(max_length=13)), 37 | ('college_name', models.CharField(max_length=150)), 38 | ('year_of_study', models.IntegerField(choices=[(1, '1st Year'), (2, '2nd Year'), (3, '3rd Year'), (4, '4th Year')])), 39 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), 40 | ], 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /accounts/migrations/0006_user_has_profile.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-18 07:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0005_auto_20211116_1053'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='has_profile', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /accounts/migrations/0007_alter_profile_year_of_study.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-19 06:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0006_user_has_profile'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='profile', 15 | name='year_of_study', 16 | field=models.CharField(choices=[('1', '1st Year'), ('2', '2nd Year'), ('3', '3rd Year'), ('4', '4th Year')], max_length=1), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvinthomasdev/django-REST-TokenAuth/97112edb7bd6786f24affe00d60ebda88f79a3a7/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser, BaseUserManager # A new class is imported. ## 2 | from django.db import models 3 | 4 | from django.utils.translation import gettext_lazy as _ 5 | from django.core.exceptions import ObjectDoesNotExist 6 | 7 | YEAR_OF_STUDY_CHOICES = ( 8 | ("1", "1st Year"), 9 | ("2", "2nd Year"), 10 | ("3", "3rd Year"), 11 | ("4", "4th Year"), 12 | ) 13 | 14 | 15 | class UserManager(BaseUserManager): 16 | """Define a model manager for User model with no username field.""" 17 | 18 | use_in_migrations = True 19 | 20 | def _create_user(self, email, password, **extra_fields): 21 | """Create and save a User with the given email and password.""" 22 | if not email: 23 | raise ValueError('The given email must be set') 24 | email = self.normalize_email(email) 25 | user = self.model(email=email, **extra_fields) 26 | user.set_password(password) 27 | user.save(using=self._db) 28 | return user 29 | 30 | def create_user(self, email, password=None, **extra_fields): 31 | """Create and save a regular User with the given email and password.""" 32 | extra_fields.setdefault('is_staff', False) 33 | extra_fields.setdefault('is_superuser', False) 34 | return self._create_user(email, password, **extra_fields) 35 | 36 | def create_superuser(self, email, password, **extra_fields): 37 | """Create and save a SuperUser with the given email and password.""" 38 | extra_fields.setdefault('is_staff', True) 39 | extra_fields.setdefault('is_superuser', True) 40 | 41 | if extra_fields.get('is_staff') is not True: 42 | raise ValueError('Superuser must have is_staff=True.') 43 | if extra_fields.get('is_superuser') is not True: 44 | raise ValueError('Superuser must have is_superuser=True.') 45 | 46 | return self._create_user(email, password, **extra_fields) 47 | 48 | 49 | class User(AbstractUser): 50 | """User model.""" 51 | 52 | username = None 53 | email = models.EmailField(_('email address'), unique=True) 54 | 55 | has_profile = models.BooleanField(default=False, blank=False, null=False) 56 | 57 | created_at = models.DateTimeField(auto_now_add=True) 58 | updated_at = models.DateTimeField(auto_now=True) 59 | 60 | USERNAME_FIELD = 'email' 61 | REQUIRED_FIELDS = [] 62 | 63 | objects = UserManager() 64 | 65 | def get_profile(self): 66 | try: 67 | profile = Profile.objects.get(user=self) 68 | return profile 69 | except ObjectDoesNotExist: 70 | return None 71 | 72 | 73 | class Profile(models.Model): 74 | user = models.ForeignKey(to=User, related_name='profile', on_delete=models.CASCADE) 75 | full_name = models.CharField(max_length=65, blank=False, null=False) 76 | contact = models.CharField(max_length=13, blank=False, null=False) 77 | college_name = models.CharField(max_length=150, blank=False, null=False) 78 | year_of_study = models.CharField(max_length=1, choices=YEAR_OF_STUDY_CHOICES, blank=False, null=False) 79 | 80 | def __str__(self): 81 | return self.full_name 82 | -------------------------------------------------------------------------------- /accounts/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.5.2 2 | defusedxml==0.7.1 3 | diff-match-patch==20200713 4 | Django==4.1.1 5 | django-import-export==2.8.0 6 | djangorestframework==3.13.1 7 | et-xmlfile==1.1.0 8 | MarkupPy==1.14 9 | odfpy==1.4.1 10 | openpyxl==3.0.10 11 | pytz==2022.2.1 12 | PyYAML==6.0 13 | sqlparse==0.4.2 14 | tablib==3.2.1 15 | tzdata==2022.2 16 | xlrd==2.0.1 17 | xlwt==1.3.0 18 | -------------------------------------------------------------------------------- /accounts/serializers.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import gettext_lazy as _ 2 | from django.contrib.auth import authenticate 3 | 4 | from rest_framework import serializers 5 | 6 | from accounts.models import User, Profile 7 | 8 | 9 | class UserSerializer(serializers.ModelSerializer): 10 | class Meta: 11 | model = User 12 | fields = ['id', 'email', 'password', 'date_joined', 'last_login'] 13 | extra_kwargs = { 14 | 'id': { 15 | 'read_only': True 16 | }, 17 | 'email': { 18 | 'required': True 19 | }, 20 | 'password': { 21 | 'write_only': True, 22 | 'required': True 23 | }, 24 | 'date_joined': { 25 | 'read_only': True 26 | }, 27 | 'last_login': { 28 | 'read_only': True 29 | }, 30 | 31 | } 32 | 33 | 34 | class UpdateUserSerializer(serializers.ModelSerializer): 35 | class Meta: 36 | model = User 37 | fields = ['id', 'email'] 38 | extra_kwargs = { 39 | 'id': { 40 | 'read_only': True 41 | }, 42 | 'email': { 43 | 'required': True 44 | } 45 | } 46 | 47 | 48 | class UserLoginSerializer(serializers.Serializer): 49 | email = serializers.EmailField(label=_("Email")) 50 | password = serializers.CharField( 51 | label=_("password", ), 52 | style={"input_type": "password"}, 53 | trim_whitespace=False 54 | ) 55 | 56 | def validate(self, attrs): 57 | email = attrs.get('email') 58 | password = attrs.get('password') 59 | 60 | if email and password: 61 | user = authenticate(request=self.context.get('request'), email=email, password=password) 62 | 63 | if not user: 64 | msg = _("Unable to Login with the credentials provided") 65 | raise serializers.ValidationError(msg, code='authorization') 66 | else: 67 | msg = _("Must include email and password.") 68 | raise serializers.ValidationError(msg, code='authorization') 69 | attrs['user'] = user 70 | return attrs 71 | 72 | 73 | class ProfileSerializer(serializers.ModelSerializer): 74 | user = serializers.EmailField(label=_("Email")) 75 | 76 | class Meta: 77 | model = Profile 78 | fields = ['user', 'full_name', 'contact', 'college_name', 'year_of_study'] 79 | extra_kwargs = { 80 | "user": { 81 | "required": True 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import create_user_view, login_view, ProfileView 4 | 5 | urlpatterns = [ 6 | path('login/', login_view, name='login'), 7 | path('register/', create_user_view, name='register'), 8 | path('profile/', ProfileView.as_view(), name='profile'), 9 | ] -------------------------------------------------------------------------------- /accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ObjectDoesNotExist 2 | 3 | from rest_framework import status 4 | from rest_framework.response import Response 5 | from rest_framework.authtoken.views import ObtainAuthToken 6 | from rest_framework.authtoken.models import Token 7 | from rest_framework.decorators import api_view, permission_classes 8 | from rest_framework.permissions import AllowAny, IsAuthenticated 9 | from rest_framework.views import APIView 10 | 11 | from accounts.models import User, Profile 12 | from .serializers import UserLoginSerializer, UserSerializer, ProfileSerializer 13 | 14 | 15 | @api_view(["POST", ]) 16 | @permission_classes([AllowAny, ]) 17 | def create_user_view(request): 18 | serializer = UserSerializer(data=request.data) 19 | if serializer.is_valid(): 20 | email = serializer.validated_data.get("email") 21 | password = serializer.validated_data.get("password") 22 | print(password) 23 | try: 24 | user = User.objects.get(email=email) 25 | except ObjectDoesNotExist: 26 | user = User.objects.create_user(email=email, password=password) 27 | # user.set_password(password) 28 | # user.save() 29 | else: 30 | return Response(serializer.errors, status=status.HTTP_409_CONFLICT) 31 | return Response( 32 | { 33 | "message": "Account created", 34 | "email": user.email, 35 | }, 36 | status=status.HTTP_201_CREATED 37 | ) 38 | 39 | 40 | @api_view(["POST", ]) 41 | def login_view(request): 42 | serializer = UserLoginSerializer(data=request.data) 43 | if serializer.is_valid(): 44 | user = serializer.validated_data['user'] 45 | token, created = Token.objects.get_or_create(user=user) 46 | user.save() 47 | 48 | return Response( 49 | { 50 | 'token': token.key, 51 | 'user_id': user.pk, 52 | 'email': user.email, 53 | }, 54 | status=status.HTTP_200_OK 55 | ) 56 | else: 57 | try: 58 | message = serializer.errors['non_field_errors'][0] 59 | except (IndexError, KeyError) as e: 60 | message = "Some random message I don't know y" 61 | 62 | return Response({'message': message}, content_type='application/json', status=status.HTTP_400_BAD_REQUEST) 63 | 64 | 65 | class ProfileView(APIView): 66 | 67 | permission_classes(IsAuthenticated) 68 | 69 | def post(self, request): 70 | print(request.data.dict()) 71 | try: 72 | profile = Profile.objects.get(user=request.user) 73 | profile.delete() 74 | except ObjectDoesNotExist: 75 | pass 76 | profile = Profile(user=request.user, **request.data.dict()) 77 | profile.save() 78 | return Response({"message": "Profile Updated"}, status=status.HTTP_200_OK) 79 | 80 | def get(self, request): 81 | user = request.user 82 | try: 83 | profile = Profile.objects.get(user=user) 84 | except ObjectDoesNotExist: 85 | return Response({"message": "user profile doesn't exist"}, status=status.HTTP_404_NOT_FOUND) 86 | serializer = ProfileSerializer(profile) 87 | return Response(serializer.data, status=status.HTTP_200_OK) 88 | --------------------------------------------------------------------------------