1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 13:15:18 +02:00

User profile settings API and remove old Dj-Rest-Auth code

This commit is contained in:
Sean Morley 2024-11-30 10:24:27 -05:00
parent c65fcc2558
commit 84566b8ec1
22 changed files with 514 additions and 791 deletions

View file

@ -11,7 +11,6 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from dotenv import load_dotenv
from datetime import timedelta
from os import getenv
from pathlib import Path
# Load environment variables from .env file
@ -35,8 +34,6 @@ DEBUG = getenv('DEBUG', 'True') == 'True'
# ]
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
@ -50,7 +47,7 @@ INSTALLED_APPS = (
"allauth_ui",
'allauth',
'allauth.account',
'allauth.mfa',
# 'allauth.mfa',
'allauth.headless',
'allauth.socialaccount',
"widget_tweaks",
@ -108,7 +105,7 @@ DATABASES = {
}
}
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
@ -123,8 +120,7 @@ USE_L10N = True
USE_TZ = True
ALLAUTH_UI_THEME = "dark"
SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
@ -138,6 +134,16 @@ MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
STATICFILES_DIRS = [BASE_DIR / 'static']
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
}
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -154,22 +160,22 @@ TEMPLATES = [
},
]
# Authentication settings
DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True'
DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.')
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
}
ALLAUTH_UI_THEME = "dark"
SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
AUTH_USER_MODEL = 'users.CustomUser'
ACCOUNT_ADAPTER = 'users.adapters.NoNewUsersAccountAdapter'
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
SESSION_SAVE_EVERY_REQUEST = True
FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000')
# HEADLESS_FRONTEND_URLS = {
@ -218,9 +224,6 @@ SWAGGER_SETTINGS = {
'LOGOUT_URL': 'logout',
}
from os import getenv
CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
@ -253,6 +256,4 @@ LOGGING = {
},
}
# https://github.com/dr5hn/countries-states-cities-database/tags
COUNTRY_REGION_JSON_VERSION = 'v2.4'
SESSION_SAVE_EVERY_REQUEST = True
COUNTRY_REGION_JSON_VERSION = 'v2.4'

View file

@ -3,8 +3,7 @@ from django.contrib import admin
from django.views.generic import RedirectView, TemplateView
from django.conf import settings
from django.conf.urls.static import static
from adventures import urls as adventures
from users.views import ChangeEmailView, IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView
from users.views import IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView, UpdateUserMetadataView
from .views import get_csrf_token
from drf_yasg.views import get_schema_view
@ -19,56 +18,19 @@ schema_view = get_schema_view(
urlpatterns = [
path('api/', include('adventures.urls')),
path('api/', include('worldtravel.urls')),
path("_allauth/", include("allauth.headless.urls")),
path('auth/change-email/', ChangeEmailView.as_view(), name='change_email'),
path('auth/is-registration-disabled/', IsRegistrationDisabled.as_view(), name='is_registration_disabled'),
path('auth/users/', PublicUserListView.as_view(), name='public-user-list'),
path('auth/user/<uuid:user_id>/', PublicUserDetailView.as_view(), name='public-user-detail'),
path('auth/update-user/', UpdateUserMetadataView.as_view(), name='update-user-metadata'),
path('auth/user-metadata/', UserMetadataView.as_view(), name='user-metadata'),
path('auth/user-metadata/', UserMetadataView.as_view(), name='user-metadata'),
path('csrf/', get_csrf_token, name='get_csrf_token'),
re_path(r'^$', TemplateView.as_view(
template_name="home.html"), name='home'),
re_path(r'^signup/$', TemplateView.as_view(template_name="signup.html"),
name='signup'),
re_path(r'^email-verification/$',
TemplateView.as_view(template_name="email_verification.html"),
name='email-verification'),
re_path(r'^login/$', TemplateView.as_view(template_name="login.html"),
name='login'),
re_path(r'^logout/$', TemplateView.as_view(template_name="logout.html"),
name='logout'),
re_path(r'^password-reset/$',
TemplateView.as_view(template_name="password_reset.html"),
name='password-reset'),
re_path(r'^password-reset/confirm/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),
name='password-reset-confirm'),
re_path(r'^user-details/$',
TemplateView.as_view(template_name="user_details.html"),
name='user-details'),
re_path(r'^password-change/$',
TemplateView.as_view(template_name="password_change.html"),
name='password-change'),
re_path(r'^resend-email-verification/$',
TemplateView.as_view(
template_name="resend_email_verification.html"),
name='resend-email-verification'),
# this url is used to generate email content
re_path(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),
name='password_reset_confirm'),
re_path(r'^auth/', include('dj_rest_auth.urls')),
re_path(r'^auth/registration/',
include('dj_rest_auth.registration.urls')),
# re_path(r'^account/', include('allauth.urls')),
path('', TemplateView.as_view(template_name='home.html')),
re_path(r'^admin/', admin.site.urls),
re_path(r'^accounts/profile/$', RedirectView.as_view(url='/',
permanent=True), name='profile-redirect'),
@ -78,5 +40,5 @@ urlpatterns = [
path("accounts/", include("allauth.urls")),
# Include the API endpoints:
path("_allauth/", include("allauth.headless.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -1,7 +1,5 @@
Django==5.0.8
dj-rest-auth @ git+https://github.com/iMerica/dj-rest-auth.git@master
djangorestframework>=3.15.2
djangorestframework-simplejwt==5.3.1
django-allauth==0.63.3
drf-yasg==1.21.4
django-cors-headers==4.4.0

View file

@ -4,8 +4,8 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Django-dj-rest-auth demo" />
<meta name="author" content="iMerica, Inc." />
<meta name="description" content="AdventureLog Server" />
<meta name="author" content="Sean Morley" />
<title>AdventureLog API Server</title>
@ -31,39 +31,6 @@
<body role="document">
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
>API endpoints <span class="caret"></span
></a>
<ul class="dropdown-menu" role="menu">
<!-- these pages don't require user token -->
<li><a href="{% url 'signup' %}">Signup</a></li>
<li>
<a href="{% url 'email-verification' %}">E-mail verification</a>
</li>
<li>
<a href="{% url 'resend-email-verification' %}"
>Resend E-mail verification</a
>
</li>
<li><a href="{% url 'login' %}">Login</a></li>
<li><a href="{% url 'password-reset' %}">Password Reset</a></li>
<li>
<a href="{% url 'password-reset-confirm' %}"
>Password Reset Confirm</a
>
</li>
<li class="divider"></li>
<!-- these pages require user token -->
<li><a href="{% url 'user-details' %}">User details</a></li>
<li><a href="{% url 'logout' %}">Logout</a></li>
<li><a href="{% url 'password-change' %}">Password change</a></li>
</ul>
</li>
</ul>
<div class="navbar-header">
<button
type="button"
@ -80,20 +47,19 @@
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/">Demo</a></li>
<li class="active"><a href="/">Server Home</a></li>
<li>
<a
target="_blank"
href="http://dj-rest-auth.readthedocs.org/en/latest/"
<a target="_blank" href="http://adventurelog.app"
>Documentation</a
>
</li>
<li>
<a target="_blank" href="https://github.com/iMerica/dj-rest-auth"
>Source code</a
<a
target="_blank"
href="https://github.com/seanmorley15/AdventureLog"
>Source Code</a
>
</li>
<li><a target="_blank" href="{% url 'api_docs' %}">API Docs</a></li>
</ul>
</div>
<!--/.nav-collapse -->

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>E-mail verification</h3><hr/>
{% include "fragments/email_verification_form.html" %}
</div>
{% endblock %}

View file

@ -4,7 +4,12 @@
<h1>AdventureLog API Server</h1>
<p>
<a class="btn btn-primary btn-lg" href="/admin" role="button">Admin Site</a>
<a class="btn btn-secondary btn-lg" href="/docs" role="button">API Docs</a>
<a
class="btn btn-secondary btn-lg"
href="/accounts/password/change"
role="button"
>Account Managment</a
>
</p>
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Login</h3><hr/>
{% include "fragments/login_form.html" %}
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Logout</h3><hr/>
{% include "fragments/logout_form.html" %}
</div>
{% endblock %}

View file

@ -1,39 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="form-group">
<label for="token" class="col-sm-2 control-label">User Token</label>
<div class="col-sm-4">
<input name="token" type="text" class="form-control" id="token" placeholder="Token">
<p class="help-block">Token received after login</p>
</div>
</div>
</div>
<div class="row">
<h3>Update User Details</h3><hr/>
{% include "fragments/password_change_form.html" %}
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
$().ready(function(){
$('form button[type=submit]').click(function(){
var token = $('input[name=token]').val();
var form = $('form');
$.ajax({
url: form.attr('action'),
data: $('form').serialize(),
type: "POST",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Token '+token);}
}).fail(function(data){error_response(data);})
.done(function(data){susccess_response(data);});
return false;
});
});
</script>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Password reset</h3><hr/>
{% include "fragments/password_reset_form.html" %}
</div>
{% endblock %}

View file

@ -1,26 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Password reset confirmation</h3><hr/>
{% include "fragments/password_reset_confirm_form.html" %}
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
var url_elements = window.location.pathname.split('/');
if (url_elements.length == 6){
var uid = url_elements[url_elements.length - 3];
if (uid !== undefined){
$('input[name=uid]').val(uid);
}
var token = url_elements[url_elements.length - 2];
if (token !== undefined){
$('input[name=token]').val(token);
}
}
</script>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Resend E-mail verification</h3><hr/>
{% include "fragments/resend_email_verification_form.html" %}
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Signup</h3><hr/>
{% include "fragments/signup_form.html" %}
</div>
{% endblock %}

View file

@ -1,58 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Retrieve User Details</h3><hr/>
<div class="form-group">
<label for="token" class="col-sm-2 control-label">User Token</label>
<div class="col-sm-4">
<input name="token" type="text" class="form-control" id="token" placeholder="Token">
<p class="help-block">Token received after login</p>
</div>
<button id="get-user-details" class="btn btn-primary">GET user details</button>
</div>
</div>
<div class="row">
<h3>Update User Details</h3><hr/>
{% include "fragments/user_details_form.html" %}
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
$().ready(function(){
$('#get-user-details').click(function(){
var token = $('input[name=token]').val();
$.ajax({
url: "{% url 'rest_user_details' %}",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Token '+token);},
type: "GET",
success: function(data) {
$('input[name=username]').val(data.username);
$('input[name=email]').val(data.email);
$('input[name=first_name]').val(data.first_name);
$('input[name=last_name]').val(data.last_name);
}
});
return false;
});
$('form button[type=submit]').click(function(){
var token = $('input[name=token]').val();
var form = $('form');
$.ajax({
url: form.attr('action'),
data: $('form').serialize(),
type: "PUT",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Token '+token);}
}).fail(function(data){error_response(data);})
.done(function(data){susccess_response(data);});
return false;
});
});
</script>
{% endblock %}

View file

@ -106,6 +106,7 @@ class CustomUserDetailsSerializer(UserDetailsSerializer):
class Meta(UserDetailsSerializer.Meta):
model = CustomUser
fields = UserDetailsSerializer.Meta.fields + ('profile_pic', 'uuid', 'public_profile')
read_only_fields = UserDetailsSerializer.Meta.read_only_fields + ('uuid',)
def to_representation(self, instance):
representation = super().to_representation(instance)

View file

@ -97,4 +97,27 @@ class UserMetadataView(APIView):
def get(self, request):
user = request.user
serializer = PublicUserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.data, status=status.HTTP_200_OK)
class UpdateUserMetadataView(APIView):
"""
Update user metadata using fields from the PublicUserSerializer.
Using patch opposed to put allows for partial updates, covers the case where it checks the username and says it's already taken. Duplicate uesrname values should not be included in the request to avoid this.
"""
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
request_body=PublicUserSerializer,
responses={
200: openapi.Response('User metadata updated'),
400: 'Bad Request'
},
operation_description="Update user metadata."
)
def patch(self, request):
user = request.user
serializer = PublicUserSerializer(user, data=request.data, partial=True, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)