initial commit. added user model
This commit is contained in:
parent
41fdf6a15b
commit
a673e032f6
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.vscode/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.wav
|
||||||
|
*.mp3
|
||||||
|
*.aac
|
||||||
|
*.opus
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.zip
|
162
backend/README.md
Normal file
162
backend/README.md
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Selah Django backend API
|
||||||
|
|
||||||
|
|
||||||
|
*** DJANGO ENVIRONMENT LOCAL SETUP (installing libraries/dependencies) ***
|
||||||
|
|
||||||
|
|
||||||
|
- install ffmpeg for audio file processing on your system:
|
||||||
|
sudo apt-get install net-tools
|
||||||
|
sudo apt install ffmpeg
|
||||||
|
|
||||||
|
|
||||||
|
- install postgres
|
||||||
|
sudo apt install libpq-dev
|
||||||
|
sudo apt install postgresql
|
||||||
|
sudo apt install postgresql-contrib
|
||||||
|
|
||||||
|
- make sure python is installed (should come with ubuntu):
|
||||||
|
python (or python3) --version
|
||||||
|
Django setup (Using Python -v 3.10.6 and Django -v 4.2 as of April, 29, 2023):
|
||||||
|
|
||||||
|
- make dir on local machine where this project will be located
|
||||||
|
|
||||||
|
- clone this repo from github (ssh or https, see top level servers_ssl_db_setup digitalocean_server.txt)
|
||||||
|
|
||||||
|
- install python venv:
|
||||||
|
sudo apt install python3.10-venv
|
||||||
|
|
||||||
|
- Create python virtual environment on local machine (shouldn't need to do this step if cloning from git, just install venv and try to activate the env in next step):
|
||||||
|
|
||||||
|
python3 -m venv env
|
||||||
|
|
||||||
|
- Activate python virtual environment on local machine:
|
||||||
|
|
||||||
|
. env/bin/activate
|
||||||
|
(you'll see '(env)' on the left-most side the terminal signature when activated)
|
||||||
|
|
||||||
|
- Once your env is activated, install Django and other dependencies (see requirements.txt):
|
||||||
|
|
||||||
|
pip install django (the backend framework)
|
||||||
|
pip install django-rest-framework (creating the backend API, creates djangorestframework dir as well)
|
||||||
|
pip install djangorestframework-simplejwt (Simple JWT provides a JSON Web Token authentication backend for the Django REST Framework. Updated framework from djangorestframework-jwt which is now deprecated)
|
||||||
|
pip install pyjwt (Python library which allows you to encode and decode JSON Web Tokens JWT)
|
||||||
|
pip install social-auth-app-django (for 3rd party auth Facebook, Google, LinkedIn, etc.)
|
||||||
|
pip install django-cors-headers (provides security between backend and API)
|
||||||
|
pip install djoser (assists with user auth)
|
||||||
|
pip install pillow (image processing)
|
||||||
|
pip install django-imagekit (django image processing for track image)
|
||||||
|
pip install pydub (audio file processing)
|
||||||
|
pip install django-environ (for environment variables)
|
||||||
|
pip install stripe (payment processor for handling secure payments)
|
||||||
|
pip3 install --upgrade stripe (upgrade stripe)
|
||||||
|
pip install psycopg2 (database adapter for PostgreSQL DB. Use this for local development, but in the production server, install the binary:
|
||||||
|
pip install psycopg2-binary
|
||||||
|
)
|
||||||
|
install pgadmin4 desktop tool for DB management (https://www.pgadmin.org/download/)
|
||||||
|
pip install gunicorn
|
||||||
|
|
||||||
|
*** Note: You need to create empty DB in either psql shell or pgadmin tool before running python manage.py makemigrations and python manage.py migrate (If you want to use custom user model, DO NOT RUN python manage.py makemigrations/migrate until you have created your custom user model, otherwise Django will revert to its default User Model. Changing from Django's default user model to a custom user model is possible, but unsupported and prone to many errors. Do this first before anything else if that's what you want for your app)
|
||||||
|
|
||||||
|
- activate env
|
||||||
|
(linux) . env/bin.activate
|
||||||
|
- create new Django project (make sure you are in dir you want project to be created):
|
||||||
|
django-admin startproject yourprojectname
|
||||||
|
- start Django dev server:
|
||||||
|
|
||||||
|
set backend env DEV_MODE var:
|
||||||
|
DEV_MODE=True
|
||||||
|
|
||||||
|
|
||||||
|
- start Django dev server in terminal:
|
||||||
|
DJANGO_SETTINGS_MODULE=sheriff_crandy_project.dev_settings python manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
|
- you should be able to access backend at
|
||||||
|
127.0.0.1:8000
|
||||||
|
|
||||||
|
- you can now create apps (for models --users, tracks, artists, etc; make sure you are in project root directory when creating apps. After creating an app, make sure you register it by adding it to the main project's settings.py file INSTALLED_APPS dict)
|
||||||
|
|
||||||
|
python manage.py startapp my_app_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To run vs code debugger:
|
||||||
|
In top level directory, add this launch.json file:
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Django",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "/your/path/to/manage.py",
|
||||||
|
"args": [
|
||||||
|
"runserver"
|
||||||
|
],
|
||||||
|
"django": true,
|
||||||
|
"env": {
|
||||||
|
"DJANGO_SETTINGS_MODULE": "main_project.dev_settings"
|
||||||
|
},
|
||||||
|
"justMyCode": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In main_project folder, create .env file
|
||||||
|
add these env vars:
|
||||||
|
|
||||||
|
DOMAIN=https://sheriffcrandymusic.local:9443/api
|
||||||
|
DEV_DOMAIN=http://127.0.0.1:8000
|
||||||
|
DEV_MODE=True
|
||||||
|
|
||||||
|
Should be it. Activate your python env:
|
||||||
|
. env/bin/activate
|
||||||
|
And start Django's dev server with the dev settings:
|
||||||
|
|
||||||
|
start dev server using dev mode settings:
|
||||||
|
DJANGO_SETTINGS_MODULE=sheriff_crandy_project.dev_settings python manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
|
Now go to an API endpoint and verify the dev site is being served:
|
||||||
|
http://127.0.0.1:8000/api/sc/v1/tracks/
|
||||||
|
|
||||||
|
|
||||||
|
If you were preveiously in production mode, stop all nginx and gunicorn servers and open
|
||||||
|
your browser's dev tools and clear all cache and cookie data:
|
||||||
|
|
||||||
|
sudo systemctl stop nginx
|
||||||
|
sudo systemctl stop gunicorn
|
||||||
|
sudo systemctl stop gunicorn.socket
|
||||||
|
|
||||||
|
|
||||||
|
*** PRODUCTION SIMULATION SERVER (https self-signed SSL certs) ***
|
||||||
|
|
||||||
|
TO START SIMULATED PROD SERVER
|
||||||
|
access site over https with self-signed ssl certs
|
||||||
|
(see servers_ssl_db_setup folder and complete those steps first if you haven't set up the servers locally in your machine yet):
|
||||||
|
set backend env DEV_MODE var:
|
||||||
|
DEV_MODE=False
|
||||||
|
|
||||||
|
|
||||||
|
restart nginx and gunicorn
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
sudo systemctl restart gunicorn.socket
|
||||||
|
sudo systemctl restart gunicorn
|
||||||
|
|
||||||
|
access the backend API/admin page at:
|
||||||
|
|
||||||
|
whatever you named your hosts name in nginx/gnicorn ssl setup
|
||||||
|
|
||||||
|
|
||||||
|
After everything is working, you can begin developing and making changes to code.
|
||||||
|
After you have made a code change, restart servers to see updates:
|
||||||
|
|
||||||
|
reset nginx and gunicorn
|
||||||
|
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
sudo systemctl restart gunicorn
|
0
backend/main_project/__init__.py
Normal file
0
backend/main_project/__init__.py
Normal file
7
backend/main_project/asgi.py
Normal file
7
backend/main_project/asgi.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main_project.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
27
backend/main_project/custom_user_notes.txt
Normal file
27
backend/main_project/custom_user_notes.txt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
***
|
||||||
|
|
||||||
|
If you want to use custom user model,
|
||||||
|
DO NOT RUN python manage.py makemigrations/migrate until
|
||||||
|
you have created your custom user model,
|
||||||
|
otherwise Django will revert to its default User Model.
|
||||||
|
Changing from Django's default user model to a
|
||||||
|
custom user model is possible, but unsupported
|
||||||
|
and prone to many errors. Do this first before anything
|
||||||
|
else if that's what you want for your app
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
in root dir:
|
||||||
|
|
||||||
|
- activate env
|
||||||
|
(linux) . env/bin/activate
|
||||||
|
- create your user app
|
||||||
|
python manage.py startapp your_custom_user_app_name
|
||||||
|
- go to your main project's settings.py file and in the INSTALLED_APPS dictionary add your user app:
|
||||||
|
'your_custom_user_app_name'
|
||||||
|
see src for model/view/urls setup. Modify as desired.
|
||||||
|
|
||||||
|
- When you are satisfied with your user, run:
|
||||||
|
python manage.py makemigrations
|
||||||
|
- and then:
|
||||||
|
python manage.py migrate
|
49
backend/main_project/dev_settings.py
Normal file
49
backend/main_project/dev_settings.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# same settings as production but with debug to true and ssl redirects off
|
||||||
|
# useful for running vs code or python debugger
|
||||||
|
# needed to run local Django dev server over http
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
TO START DEV SERVER
|
||||||
|
|
||||||
|
run dev server to access site over http:
|
||||||
|
set backend env DEV_MODE var:
|
||||||
|
DEV_MODE=True
|
||||||
|
|
||||||
|
(if you haven't set up these servers yet, you can ignore the rest of the steps and just run:
|
||||||
|
|
||||||
|
DJANGO_SETTINGS_MODULE=main_project.dev_settings python manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
stop nginx and gunicorn
|
||||||
|
sudo systemctl stop nginx
|
||||||
|
sudo systemctl stop gunicorn
|
||||||
|
sudo systemctl stop gunicorn.socket
|
||||||
|
|
||||||
|
in your browser, open dev tools
|
||||||
|
clear cache and cookie data in dev tools console (otherwise any previous http redirects will still be in affect)
|
||||||
|
|
||||||
|
start dev server:
|
||||||
|
DJANGO_SETTINGS_MODULE=main_project.dev_settings python manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
|
make sure you can access the frontend at
|
||||||
|
127.0.0.1:8080
|
||||||
|
and the backend at
|
||||||
|
127.0.0.1:8000/api/
|
||||||
|
'''
|
||||||
|
|
||||||
|
from .settings import *
|
||||||
|
|
||||||
|
# for development
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
SECURE_SSL_REDIRECT=False
|
||||||
|
SESSION_COOKIE_SECURE=False
|
||||||
|
CSRF_COOKIE_SECURE=False
|
||||||
|
SECURE_REDIRECT_EXEMPT = ['^']
|
||||||
|
|
||||||
|
print('\nDEV MODE: ' + str(env("DEV_MODE")) + '\n')
|
226
backend/main_project/settings.py
Normal file
226
backend/main_project/settings.py
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
# global settings and live production settings
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
import environ
|
||||||
|
env = environ.Env()
|
||||||
|
environ.Env.read_env()
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# set to False for production
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
# SECURE_SSL_REDIRECT=True
|
||||||
|
# SESSION_COOKIE_SECURE=True
|
||||||
|
# CSRF_COOKIE_SECURE=True
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = env('SECRET_KEY')
|
||||||
|
|
||||||
|
'''
|
||||||
|
TODO: set these when testing payments
|
||||||
|
|
||||||
|
# get stripe endpoint's webhook secret by running `stripe listen` in CLI
|
||||||
|
# STRIPE_WEBHOOK_SECRET = env("STRIPE_WEBHOOK_SECRET")
|
||||||
|
|
||||||
|
# stripe data
|
||||||
|
# STRIPE_PK = env("STRIPEPK")
|
||||||
|
# STRIPE_SK = env("STRIPESK")
|
||||||
|
# STRIPE_DOMAIN = env("STRIPE_DOMAIN")
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# add server ips
|
||||||
|
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
|
||||||
|
|
||||||
|
|
||||||
|
# rest framework global settings
|
||||||
|
# authentication classes: https://www.django-rest-framework.org/api-guide/settings/#default_authentication_classes
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
),
|
||||||
|
# permission policy: https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy
|
||||||
|
# can override these in views. Don't forget to add a comma
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
# allow anyone to access api data
|
||||||
|
# 'rest_framework.permissions.AllowAny',
|
||||||
|
# do not allow anyone to access API endpoints unless user is authenticated
|
||||||
|
# 'rest_framework.permissions.IsAuthenticated',
|
||||||
|
# allow full access to authenticated users, but allow read-only access to unauthenticated users
|
||||||
|
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
|
||||||
|
),
|
||||||
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.JSONRenderer', # makes api views JSON data only
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# DRF dependencies
|
||||||
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
|
'corsheaders',
|
||||||
|
# djoser is a REST implementation of Django authentication system. Provides token based authentication
|
||||||
|
'djoser',
|
||||||
|
# Django image processing
|
||||||
|
'imagekit',
|
||||||
|
# custom user model
|
||||||
|
'users_app',
|
||||||
|
]
|
||||||
|
|
||||||
|
# set custom user model (appname.model name) to prevent Django from using
|
||||||
|
# its default model
|
||||||
|
AUTH_USER_MODEL = 'users_app.Users'
|
||||||
|
|
||||||
|
|
||||||
|
# add custom backend, if it fails, Django will use default backend
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'users_app.custom_backend.Custom_Backend',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'corsheaders.middleware.CorsMiddleware', # this has to go above CommonMiddleware
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# change to whatever hostname react-native runs on
|
||||||
|
# CORS_ALLOWED_ORIGINS = [
|
||||||
|
# "http://127.0.0.1:8080",
|
||||||
|
# "http://localhost:8080",
|
||||||
|
# "https://sheriffcrandymusic.local:9443",
|
||||||
|
# "https://sheriffcrandymusic.com"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'main_project.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR,'templates')],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'main_project.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
|
# PostgreSQL config
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
# postgresql
|
||||||
|
'ENGINE': env('DBENGINE'),
|
||||||
|
# name of database
|
||||||
|
'NAME': env('DBNAME'),
|
||||||
|
# owner of database
|
||||||
|
'USER': env('DBUSER'),
|
||||||
|
'PASSWORD': env('DBPASSWORD'),
|
||||||
|
# specify which machine where db is installed
|
||||||
|
# connect through TCP sockets,
|
||||||
|
'HOST': env('DBHOST')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation. Turning this off, handling custom password validation manually
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
|
'''
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
'''
|
||||||
|
# allow python to send emails
|
||||||
|
# Simple Mail Transfer Protocol SMPT config
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
EMAIL_HOST = env('EMAIL_HOST')
|
||||||
|
EMAIL_PORT = env('EMAIL_PORT')
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
# email address that sends emails
|
||||||
|
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
|
||||||
|
# EMAIL_HOST_PASSWORD generated by Google. First you have to enable 2-step verification
|
||||||
|
# Go to Google-> manage Google account -> Security -> Sigining in to Google -> App passwords ->
|
||||||
|
# if you can't find app passwords. Search for it in the searchbar above
|
||||||
|
# click select app and choose Mail or Other -> select device name (make a custom name) -> generate -> get password and put it in .env file
|
||||||
|
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
||||||
|
|
||||||
|
EMAIL_ACTIVE_FIELD = 'is_active'
|
||||||
|
'''
|
||||||
|
|
||||||
|
# URL to use when referring to static files located in STATIC_ROOT.
|
||||||
|
# manually add this folder to the parent (backend) dir
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, 'static'),
|
||||||
|
]
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR,'staticfiles')
|
||||||
|
# media dir for files. This dir is created when the first model objects are created (either through admin page or otherwise)
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
|
||||||
|
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# receive error details of exceptions raised in the request/response cycle.
|
||||||
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-ADMINS
|
||||||
|
# ADMINS = [('name', 'email')]
|
||||||
|
|
||||||
|
# managers get broken link notifications
|
||||||
|
# MANAGERS = [('name', 'email')]
|
18
backend/main_project/urls.py
Normal file
18
backend/main_project/urls.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('api/admin/', admin.site.urls),
|
||||||
|
# djoser library is a REST implementation of Django authentication system
|
||||||
|
# url of API
|
||||||
|
path('api/selah/', include('djoser.urls')),
|
||||||
|
path('api/selah/', include('djoser.urls.authtoken')),
|
||||||
|
# include app urls --can check api endpoints at http://localhost:8000/api/selah/app-specific-urls
|
||||||
|
path('api/selah/', include ('users_app.urls')),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
16
backend/main_project/wsgi.py
Normal file
16
backend/main_project/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for main_project project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main_project.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
backend/manage.py
Executable file
22
backend/manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main_project.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
backend/users_app/__init__.py
Normal file
0
backend/users_app/__init__.py
Normal file
13
backend/users_app/admin.py
Normal file
13
backend/users_app/admin.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Users
|
||||||
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
@admin.register(Users)
|
||||||
|
class CustomUserAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
# put all other fields you want to be shown in listing
|
||||||
|
'username',
|
||||||
|
)
|
||||||
|
readonly_fields = ['id','date_user_added']
|
8
backend/users_app/apps.py
Normal file
8
backend/users_app/apps.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UsersAppConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'users_app'
|
||||||
|
# for admin page
|
||||||
|
verbose_name = 'Users App'
|
41
backend/users_app/custom_backend.py
Normal file
41
backend/users_app/custom_backend.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# custom auth backend to allow user to login with either username or email address
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
user_obj = get_user_model()
|
||||||
|
print("\nUSER: " + (str(user_obj)) +'\n')
|
||||||
|
|
||||||
|
# called when making api attempts to authenticate users and create auth tokens
|
||||||
|
class Custom_Backend(object):
|
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
|
try:
|
||||||
|
# Try to fetch the user by searching the username or email field
|
||||||
|
user = user_obj.objects.get(Q(username=username)|Q(email=username))
|
||||||
|
print("\nUSER: " + (str(user)) +'\n')
|
||||||
|
# if password was correct, authenticate using token auth
|
||||||
|
if user.check_password(password):
|
||||||
|
|
||||||
|
# create token for this user:
|
||||||
|
# first try to get this token (if the user's token was deleted somehow)
|
||||||
|
try:
|
||||||
|
print("\nttempting to get token for user\n")
|
||||||
|
token = Token.objects.get(user=user)
|
||||||
|
except:
|
||||||
|
print("\nToken doesn't exist for user, creating one\n")
|
||||||
|
token = Token.objects.create(user=user)
|
||||||
|
|
||||||
|
|
||||||
|
return user
|
||||||
|
except user_obj.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# called when saving a new model obj
|
||||||
|
def get_user(self, user_id):
|
||||||
|
|
||||||
|
try:
|
||||||
|
return user_obj.objects.get(pk=user_id)
|
||||||
|
except user_obj.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
38
backend/users_app/migrations/0001_initial.py
Normal file
38
backend/users_app/migrations/0001_initial.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2023-06-14 10:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Users',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('email', models.EmailField(db_index=True, max_length=100, unique=True)),
|
||||||
|
('username', models.CharField(db_index=True, max_length=64, unique=True)),
|
||||||
|
('user_first_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||||
|
('user_last_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||||
|
('user_profile_pic', models.ImageField(blank=True, null=True, upload_to='user_pfps')),
|
||||||
|
('user_favorite_bible_verse', models.CharField(blank=True, max_length=20, null=True)),
|
||||||
|
('date_user_added', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('is_staff', models.BooleanField(default=False)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('is_superuser', models.BooleanField(default=False)),
|
||||||
|
('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_custom', related_query_name='user_custom', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set_custom', related_query_name='user_custom', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'users',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
backend/users_app/migrations/__init__.py
Normal file
0
backend/users_app/migrations/__init__.py
Normal file
151
backend/users_app/models.py
Normal file
151
backend/users_app/models.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
# custom user model
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
# import classes needed for custom user model
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
|
||||||
|
from main_project import settings
|
||||||
|
|
||||||
|
|
||||||
|
# set up logger
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# create file handler and set level to INFO
|
||||||
|
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_logs.txt')
|
||||||
|
fh = logging.FileHandler(log_file)
|
||||||
|
fh.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# create formatter
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
fh.setFormatter(formatter)
|
||||||
|
logger.addHandler(fh)
|
||||||
|
|
||||||
|
|
||||||
|
# custom user manager
|
||||||
|
class Users_CustomUserManager(BaseUserManager):
|
||||||
|
|
||||||
|
|
||||||
|
# called when creating user through drf terminal and frontend
|
||||||
|
# only pass in req params, all other unrequired params like
|
||||||
|
# first/lastname, favorite color, etc. will be held in **extra_fields param
|
||||||
|
def create_user(self, email, username, password, **extra_fields):
|
||||||
|
|
||||||
|
if not email:
|
||||||
|
raise ValueError("Email must be provided")
|
||||||
|
if not username:
|
||||||
|
raise ValueError("Username must be provided")
|
||||||
|
|
||||||
|
user = Users(
|
||||||
|
email = self.normalize_email(email),
|
||||||
|
username = username,
|
||||||
|
**extra_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
user.set_password(password)
|
||||||
|
user.save(using=self._db)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
# called when creating superuser through terminal drf
|
||||||
|
def create_superuser(self, email, username, password, **extra_fields):
|
||||||
|
|
||||||
|
extra_fields.setdefault('is_staff',True)
|
||||||
|
extra_fields.setdefault('is_superuser',True)
|
||||||
|
if extra_fields.get('is_staff') is not True:
|
||||||
|
raise ValueError('Superuser must have is_staff=True.')
|
||||||
|
if extra_fields.get('is_superuser') is not True:
|
||||||
|
raise ValueError('Superuser must have is_superuser=True.')
|
||||||
|
return self.create_user(email, username, password, **extra_fields)
|
||||||
|
|
||||||
|
|
||||||
|
# Custom User Model
|
||||||
|
class Users(AbstractBaseUser, PermissionsMixin):
|
||||||
|
|
||||||
|
# ; define fields you want to be included in User Model, can be updated and changed later
|
||||||
|
# this is the power of implementing a custom user model
|
||||||
|
# allow authentication by username or email, or whatever else!
|
||||||
|
email = models.EmailField(db_index=True, max_length=100, unique=True)
|
||||||
|
username = models.CharField(db_index=True, max_length=64, unique=True)
|
||||||
|
|
||||||
|
# non-req'd fields
|
||||||
|
user_first_name = models.CharField(max_length=50, blank=True, null=True)
|
||||||
|
user_last_name = models.CharField(max_length=50, blank=True, null=True)
|
||||||
|
# will be uploaded to /media/ directory
|
||||||
|
user_profile_pic = models.ImageField(upload_to='user_pfps', blank=True, null=True)
|
||||||
|
# just for fun
|
||||||
|
user_favorite_bible_verse = models.CharField(max_length=20, blank=True, null=True)
|
||||||
|
date_user_added = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
# if user is allowed access to admin site.
|
||||||
|
is_staff = models.BooleanField(default=False)
|
||||||
|
# if user is currently active
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
is_superuser = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
# connect user class to manager class
|
||||||
|
objects = Users_CustomUserManager()
|
||||||
|
|
||||||
|
# choose wisely, password resets will use this field
|
||||||
|
# needs to have unique=True
|
||||||
|
USERNAME_FIELD = 'username'
|
||||||
|
# used by python manage.py createsuperuser and presumably drf for token authentication
|
||||||
|
# should not contain the USERNAME_FIELD or password as these fields will always be prompted for.
|
||||||
|
REQUIRED_FIELDS = ['user_first_name', 'user_last_name', 'email']
|
||||||
|
|
||||||
|
# this custom user model's name was clashing with Django's default user names, so adding these two
|
||||||
|
# fields for groups and user_permissions is required
|
||||||
|
groups = models.ManyToManyField(
|
||||||
|
'auth.Group',
|
||||||
|
related_name='user_set_custom',
|
||||||
|
related_query_name='user_custom',
|
||||||
|
blank=True,
|
||||||
|
help_text='The groups this user belongs to. A user will get all permissions '
|
||||||
|
'granted to each of their groups.',
|
||||||
|
verbose_name='groups',
|
||||||
|
)
|
||||||
|
user_permissions = models.ManyToManyField(
|
||||||
|
'auth.Permission',
|
||||||
|
related_name='user_set_custom',
|
||||||
|
related_query_name='user_custom',
|
||||||
|
blank=True,
|
||||||
|
help_text='Specific permissions for this user.',
|
||||||
|
verbose_name='user permissions',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'users'
|
||||||
|
# for admin page
|
||||||
|
verbose_name_plural = "Users"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# return self.email
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def has_perm(perm, obj=None, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def has_module_perms(app_label, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# get user profile pic
|
||||||
|
def get_profile_pic(self):
|
||||||
|
if self.user_profile_pic:
|
||||||
|
if settings.env('DEV_MODE') == 'True':
|
||||||
|
return settings.env('DEV_DOMAIN') + self.user_profile_pic.url
|
||||||
|
else:
|
||||||
|
return settings.env('DOMAIN') + self.user_profile_pic.url
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
# create user obj for performing user actions
|
||||||
|
# get_user_model defined in settings.py
|
||||||
|
# has to be defined below since user model hasn't been installed/loaded yet
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
user_obj = get_user_model()
|
80
backend/users_app/serializers.py
Normal file
80
backend/users_app/serializers.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# manually created serializers.py file created to turn DB data into JSON to be used by frontend
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import *
|
||||||
|
# import Users
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
# return authenticated user as well as user's cart data
|
||||||
|
user = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# creating new users
|
||||||
|
class CustomUserCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Users
|
||||||
|
fields = ('email',
|
||||||
|
'username',
|
||||||
|
'password',
|
||||||
|
'user_first_name',
|
||||||
|
'user_last_name',
|
||||||
|
'user_profile_pic',
|
||||||
|
'user_favorite_bible_verse')
|
||||||
|
extra_kwargs = {'password': {'write_only': True}}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
profile_pic = validated_data.pop('user_profile_pic', None)
|
||||||
|
current_user = self.Meta.model(**validated_data)
|
||||||
|
current_user.user_profile_pic = profile_pic
|
||||||
|
current_user.save() # Save user instance after adding profile_pic
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# for updating user account info
|
||||||
|
class UpdateUserAccountDataSerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(required=False)
|
||||||
|
email = serializers.EmailField(required=False)
|
||||||
|
user_first_name = serializers.CharField(required=False)
|
||||||
|
user_last_name = serializers.CharField(required=False)
|
||||||
|
user_favorite_bible_verse = serializers.CharField(required=False)
|
||||||
|
|
||||||
|
# automatically called during validation process
|
||||||
|
def validate_username(self, value):
|
||||||
|
request = self.context.get('request')
|
||||||
|
current_user = request.user
|
||||||
|
if user.objects.filter(username=value).exists() and current_user.username != value:
|
||||||
|
raise serializers.ValidationError('Username already exists.')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def validate_email(self, value):
|
||||||
|
request = self.context.get('request')
|
||||||
|
current_user = request.user
|
||||||
|
if user.objects.filter(email=value).exists() and current_user.email != value:
|
||||||
|
raise serializers.ValidationError('Email already exists.')
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# for displaying account info to user
|
||||||
|
class GetUserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Users
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"email",
|
||||||
|
"username",
|
||||||
|
"user_first_name",
|
||||||
|
"user_last_name",
|
||||||
|
"user_favorite_bible_verse",
|
||||||
|
"get_profile_pic"
|
||||||
|
)
|
||||||
|
|
||||||
|
# get user's pfp
|
||||||
|
class GetUserPfpSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Users
|
||||||
|
fields = (
|
||||||
|
"get_profile_pic",
|
||||||
|
)
|
3
backend/users_app/tests.py
Normal file
3
backend/users_app/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
25
backend/users_app/urls.py
Normal file
25
backend/users_app/urls.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# have to manually create this file
|
||||||
|
# manually created urls.py file
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from users_app import views
|
||||||
|
|
||||||
|
|
||||||
|
# user actions
|
||||||
|
urlpatterns = [
|
||||||
|
path('create-basic-user/', views.SaveCustomBasicUser.as_view()),
|
||||||
|
# for username validation, allow a string to be input as a param
|
||||||
|
path('check-username/<str:username>/', views.check_username),
|
||||||
|
path('check-email/<str:email>/', views.check_email),
|
||||||
|
path('logout', views.LogoutView.as_view()),
|
||||||
|
path('get-device-data/', views.get_user_device),
|
||||||
|
# path('send-password-reset-link/', views.send_password_reset_link),
|
||||||
|
# path('reset-user-password/', views.reset_password),
|
||||||
|
path('get-user-account-data/', views.get_user_account_data),
|
||||||
|
path('update-user-account-data/', views.update_user_account_data),
|
||||||
|
path('delete-user-account-data/', views.delete_user_account_data),
|
||||||
|
path('get-user-pfp/', views.get_user_pfp),
|
||||||
|
]
|
0
backend/users_app/user_logs.txt
Normal file
0
backend/users_app/user_logs.txt
Normal file
349
backend/users_app/views.py
Normal file
349
backend/users_app/views.py
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import authentication, permissions
|
||||||
|
from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from users_app.serializers import *
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
# converts html template to a string message for emails
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.conf import settings
|
||||||
|
from main_project import settings
|
||||||
|
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||||
|
from django.utils.encoding import force_bytes
|
||||||
|
from django.utils.http import urlsafe_base64_encode
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
from django.utils.http import urlsafe_base64_decode
|
||||||
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
|
from rest_framework import permissions, status
|
||||||
|
|
||||||
|
# for saving profilepic
|
||||||
|
from PIL import Image
|
||||||
|
from django.core.files import File
|
||||||
|
from io import BytesIO
|
||||||
|
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
|
# set up logger
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# create file handler and set level to INFO
|
||||||
|
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_logs.txt')
|
||||||
|
fh = logging.FileHandler(log_file)
|
||||||
|
fh.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# create formatter
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
fh.setFormatter(formatter)
|
||||||
|
logger.addHandler(fh)
|
||||||
|
|
||||||
|
|
||||||
|
user_obj = get_user_model()
|
||||||
|
|
||||||
|
# email stuff for sending email notifications
|
||||||
|
EMAIL_ON = False
|
||||||
|
URL = settings.env('DEV_DOMAIN') if settings.env('DEV_MODE') == 'True' else None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# save user pfp on signup
|
||||||
|
# SaveUserProfilePicture API view
|
||||||
|
class SaveCustomBasicUser(APIView):
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
serializer = CustomUserCreateSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
profile_pic = request.FILES.get('user_profile_pic', None)
|
||||||
|
if profile_pic:
|
||||||
|
profile_pic.file.seek(0)
|
||||||
|
image = Image.open(BytesIO(profile_pic.read()))
|
||||||
|
if image.mode == 'RGBA':
|
||||||
|
image = image.convert('RGB')
|
||||||
|
pfp_name = serializer.validated_data['username'] + '_pfp'
|
||||||
|
buffer = BytesIO()
|
||||||
|
image.save(buffer, format='JPEG')
|
||||||
|
image_file = InMemoryUploadedFile(
|
||||||
|
buffer, None, pfp_name + '.jpg', 'image/jpeg',
|
||||||
|
buffer.getbuffer().nbytes, None
|
||||||
|
)
|
||||||
|
# Call create_user method of custom user manager to create user instance
|
||||||
|
user = Users_CustomUserManager().create_user(
|
||||||
|
email=serializer.validated_data['email'],
|
||||||
|
username=serializer.validated_data['username'],
|
||||||
|
password=serializer.validated_data['password'],
|
||||||
|
user_profile_pic=image_file,
|
||||||
|
user_first_name=serializer.validated_data['user_first_name'],
|
||||||
|
user_last_name=serializer.validated_data['user_last_name'],
|
||||||
|
user_favorite_bible_verse=serializer.validated_data['user_favorite_bible_verse']
|
||||||
|
)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
|
||||||
|
# save user without profile pic
|
||||||
|
user = Users_CustomUserManager().create_user(
|
||||||
|
email=serializer.validated_data['email'],
|
||||||
|
username=serializer.validated_data['username'],
|
||||||
|
password=serializer.validated_data['password'],
|
||||||
|
user_profile_pic=image_file,
|
||||||
|
user_first_name=serializer.validated_data['user_first_name'],
|
||||||
|
user_last_name=serializer.validated_data['user_last_name'],
|
||||||
|
user_favorite_bible_verse=serializer.validated_data['user_favorite_bible_verse']
|
||||||
|
)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
# get user pfp on login
|
||||||
|
@api_view(['GET'])
|
||||||
|
@authentication_classes([authentication.TokenAuthentication])
|
||||||
|
@permission_classes([permissions.IsAuthenticated])
|
||||||
|
def get_user_pfp(request):
|
||||||
|
current_user = user_obj.objects.get(pk=request.user.pk)
|
||||||
|
user_pfp_serializer = GetUserPfpSerializer(current_user)
|
||||||
|
|
||||||
|
response = Response(user_pfp_serializer.data, status=status.HTTP_200_OK)
|
||||||
|
# added this to prevent vuejs from caching cart data
|
||||||
|
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
|
response['Expires'] = '0'
|
||||||
|
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
# delete user account
|
||||||
|
@api_view(['POST'])
|
||||||
|
@authentication_classes([authentication.TokenAuthentication])
|
||||||
|
@permission_classes([permissions.IsAuthenticated])
|
||||||
|
def delete_user_account_data(request):
|
||||||
|
|
||||||
|
# use first to prevent exception from being raised
|
||||||
|
try:
|
||||||
|
|
||||||
|
user_to_be_deleted = user_obj.objects.get(pk=request.user.pk)
|
||||||
|
user_to_be_deleted.delete()
|
||||||
|
# return no content, everything worked
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
except:
|
||||||
|
return Response({'error': 'Invalid token or user'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
# update user account info
|
||||||
|
@api_view(['POST'])
|
||||||
|
@authentication_classes([authentication.TokenAuthentication])
|
||||||
|
@permission_classes([permissions.IsAuthenticated])
|
||||||
|
def update_user_account_data(request):
|
||||||
|
|
||||||
|
# get the user
|
||||||
|
current_user = user_obj.objects.get(pk=request.user.pk)
|
||||||
|
# Deserialize incoming data
|
||||||
|
# serializer = UpdateUserAccountDataSerializer(data=request.data)
|
||||||
|
serializer = UpdateUserAccountDataSerializer(data=request.data, context={'request': request})
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
|
||||||
|
# get the img file
|
||||||
|
profile_pic = request.FILES.get('user_profile_pic', None)
|
||||||
|
|
||||||
|
if profile_pic:
|
||||||
|
|
||||||
|
profile_pic.file.seek(0)
|
||||||
|
# Open the uploaded image file
|
||||||
|
image = Image.open(BytesIO(profile_pic.read()))
|
||||||
|
|
||||||
|
# Convert RGBA to RGB mode if it exists
|
||||||
|
if image.mode == 'RGBA':
|
||||||
|
image = image.convert('RGB')
|
||||||
|
|
||||||
|
pfp_name = current_user.username + '_pfp'
|
||||||
|
|
||||||
|
buffer = BytesIO()
|
||||||
|
image.save(buffer, format='JPEG')
|
||||||
|
image_file = InMemoryUploadedFile(
|
||||||
|
buffer, None, pfp_name + '.jpg', 'image/jpeg',
|
||||||
|
buffer.getbuffer().nbytes, None
|
||||||
|
)
|
||||||
|
current_user.user_profile_pic.save(pfp_name + '.jpg', image_file)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('\n\n Image does not exist')
|
||||||
|
# save the rest of the text fields
|
||||||
|
# Update user fields
|
||||||
|
# validated_data is a dictionary that holds validated data ready to be saved to db
|
||||||
|
current_user.username = serializer.validated_data.get('username', current_user.username)
|
||||||
|
current_user.email = serializer.validated_data.get('email', current_user.email)
|
||||||
|
current_user.user_first_name = serializer.validated_data.get('user_first_name', current_user.first_name)
|
||||||
|
current_user.user_last_name = serializer.validated_data.get('user_last_name', current_user.last_name)
|
||||||
|
current_user.user_favorite_bible_verse = serializer.validated_data.get('user_favorite_bible_verse', current_user.favorite_color)
|
||||||
|
|
||||||
|
current_user.save()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
# get user account data
|
||||||
|
@api_view(['GET'])
|
||||||
|
@authentication_classes([authentication.TokenAuthentication])
|
||||||
|
@permission_classes([permissions.IsAuthenticated])
|
||||||
|
def get_user_account_data(request):
|
||||||
|
|
||||||
|
current_user = user.objects.get(pk=request.user.pk)
|
||||||
|
user_serializer = GetUserSerializer(current_user)
|
||||||
|
|
||||||
|
response = Response(user_serializer.data, status=status.HTTP_200_OK)
|
||||||
|
# added this to prevent frontend from caching cart data
|
||||||
|
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
|
response['Expires'] = '0'
|
||||||
|
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# reset password
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([permissions.AllowAny])
|
||||||
|
def reset_password(request):
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
uid = force_str(urlsafe_base64_decode(request.data.get('uidb64')))
|
||||||
|
|
||||||
|
current_user = user.objects.get(pk=uid)
|
||||||
|
|
||||||
|
except (TypeError, ValueError, OverflowError, user.DoesNotExist):
|
||||||
|
current_user = None
|
||||||
|
|
||||||
|
# Check if the token is valid
|
||||||
|
if current_user is not None and default_token_generator.check_token(current_user, request.data.get('token')):
|
||||||
|
# Set the new password for the user
|
||||||
|
password = request.data.get('password')
|
||||||
|
current_user.set_password(password)
|
||||||
|
current_user.save()
|
||||||
|
|
||||||
|
template = render_to_string('../templates/changed_account_notice_email.html', {'name':current_user.first_name})
|
||||||
|
email = EmailMessage(
|
||||||
|
# email subject title default is 'subject'
|
||||||
|
'There was a change to your account -- アカウント情報変更のお知らせ',
|
||||||
|
# email template default is 'body'
|
||||||
|
template,
|
||||||
|
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
# recipient list
|
||||||
|
[current_user.email],
|
||||||
|
)
|
||||||
|
email.fail_silently=False
|
||||||
|
# eonly send email if this flag is true
|
||||||
|
if EMAIL_ON:
|
||||||
|
email.send()
|
||||||
|
|
||||||
|
return Response({'success': 'Password reset successful'}, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response({'error': 'Invalid token or user'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
# send password reset link
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([permissions.AllowAny])
|
||||||
|
def send_password_reset_link(request):
|
||||||
|
|
||||||
|
# get the email address from the POST request
|
||||||
|
email = request.data.get('potential_email_address')
|
||||||
|
|
||||||
|
# check if the email address is valid
|
||||||
|
try:
|
||||||
|
get_user = user.objects.get(email=email)
|
||||||
|
|
||||||
|
# creating a password reset url unique for each user
|
||||||
|
token_generator = PasswordResetTokenGenerator()
|
||||||
|
uidb64 = urlsafe_base64_encode(force_bytes(get_user.pk))
|
||||||
|
token = token_generator.make_token(get_user)
|
||||||
|
# create the password reset URL using the generated token
|
||||||
|
password_reset_url = f'{URL}{request.data.get("password_reset_url")}/{uidb64}/{token}/'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EMAIL_ON = True
|
||||||
|
template = render_to_string('../templates/password-reset-email.html', {'name':get_user.first_name, 'password_reset_url': password_reset_url})
|
||||||
|
email = EmailMessage(
|
||||||
|
# email subject title default is 'subject'
|
||||||
|
'Password reset -- パスワードのリセット',
|
||||||
|
# email template default is 'body'
|
||||||
|
template,
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
# recipient list
|
||||||
|
[get_user.email],
|
||||||
|
)
|
||||||
|
email.fail_silently=False
|
||||||
|
# only send email if this flag is true
|
||||||
|
if EMAIL_ON:
|
||||||
|
email.send()
|
||||||
|
# just return a 200 response
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
except user.DoesNotExist:
|
||||||
|
# handle the case where the user does not exist
|
||||||
|
return Response({'error': 'User does not exist'}, status=200)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'error': 'Unknown error occurred'}, status=500)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# get user data
|
||||||
|
@api_view(['GET'])
|
||||||
|
def get_user_device(request):
|
||||||
|
user_agent = request.META.get('HTTP_USER_AGENT', None)
|
||||||
|
# do something with usr's device
|
||||||
|
return Response({'message': 'success'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# checking username in form validation
|
||||||
|
# need to include the request or else there will be an error
|
||||||
|
@api_view(['GET'])
|
||||||
|
def check_username(request, username):
|
||||||
|
username_available = not user.objects.filter(username=username).exists()
|
||||||
|
|
||||||
|
response = Response({'available': username_available})
|
||||||
|
# added this to prevent vuejs from caching cart data
|
||||||
|
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
|
response['Expires'] = '0'
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
# checking username in form validation
|
||||||
|
@api_view(['GET'])
|
||||||
|
def check_email(request, email):
|
||||||
|
email_available = not user.objects.filter(email=email).exists()
|
||||||
|
|
||||||
|
response = Response({'available': email_available})
|
||||||
|
# added this to prevent vuejs from caching cart data
|
||||||
|
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
|
response['Expires'] = '0'
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
# de-authenticate user by deleting auth token, and storing/updating and then saving the user's cart data
|
||||||
|
class LogoutView(APIView):
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
|
||||||
|
|
||||||
|
Token.objects.filter(user=user).delete()
|
||||||
|
return Response({'success': 'Logged out successfully.'})
|
Loading…
Reference in New Issue
Block a user