Rip out social user management
This commit is contained in:
parent
f6b5ec45ed
commit
5c3c412968
|
|
@ -1,14 +1,13 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from rest_framework.routers import DefaultRouter, SimpleRouter
|
from rest_framework.routers import DefaultRouter, SimpleRouter
|
||||||
|
|
||||||
from vbv_lernwelt.users.api.views import UserViewSet
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
else:
|
else:
|
||||||
router = SimpleRouter()
|
router = SimpleRouter()
|
||||||
|
|
||||||
router.register("users", UserViewSet)
|
# router.register("users", UserViewSet)
|
||||||
|
|
||||||
|
|
||||||
app_name = "api"
|
app_name = "api"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ if READ_DOT_ENV_FILE:
|
||||||
# GENERAL
|
# GENERAL
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||||
DEBUG = env.bool("DJANGO_DEBUG", False)
|
DEBUG = env.bool("VBV_DJANGO_DEBUG", False)
|
||||||
# Local time zone. Choices are
|
# Local time zone. Choices are
|
||||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||||
# though not all of them may be available with every OS.
|
# though not all of them may be available with every OS.
|
||||||
|
|
@ -69,11 +69,6 @@ DJANGO_APPS = [
|
||||||
"django.forms",
|
"django.forms",
|
||||||
]
|
]
|
||||||
THIRD_PARTY_APPS = [
|
THIRD_PARTY_APPS = [
|
||||||
"crispy_forms",
|
|
||||||
"crispy_bootstrap5",
|
|
||||||
"allauth",
|
|
||||||
"allauth.account",
|
|
||||||
"allauth.socialaccount",
|
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"rest_framework.authtoken",
|
"rest_framework.authtoken",
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
|
|
@ -97,7 +92,6 @@ MIGRATION_MODULES = {"sites": "vbv_lernwelt.contrib.sites.migrations"}
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
|
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"django.contrib.auth.backends.ModelBackend",
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
"allauth.account.auth_backends.AuthenticationBackend",
|
|
||||||
]
|
]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
|
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
|
||||||
AUTH_USER_MODEL = "users.User"
|
AUTH_USER_MODEL = "users.User"
|
||||||
|
|
@ -186,7 +180,6 @@ TEMPLATES = [
|
||||||
"django.template.context_processors.static",
|
"django.template.context_processors.static",
|
||||||
"django.template.context_processors.tz",
|
"django.template.context_processors.tz",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"vbv_lernwelt.users.context_processors.allauth_settings",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -372,24 +365,6 @@ else:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# django-allauth
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
ACCOUNT_ALLOW_REGISTRATION = env.bool("VBV_DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
|
||||||
ACCOUNT_AUTHENTICATION_METHOD = "username"
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
|
||||||
ACCOUNT_EMAIL_REQUIRED = True
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
|
||||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
|
||||||
ACCOUNT_ADAPTER = "vbv_lernwelt.users.adapters.AccountAdapter"
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/forms.html
|
|
||||||
ACCOUNT_FORMS = {"signup": "vbv_lernwelt.users.forms.UserSignupForm"}
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
|
||||||
SOCIALACCOUNT_ADAPTER = "vbv_lernwelt.users.adapters.SocialAccountAdapter"
|
|
||||||
# https://django-allauth.readthedocs.io/en/latest/forms.html
|
|
||||||
SOCIALACCOUNT_FORMS = {"signup": "vbv_lernwelt.users.forms.UserSocialSignupForm"}
|
|
||||||
|
|
||||||
# django-rest-framework
|
# django-rest-framework
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
|
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,9 @@ from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
||||||
path(
|
path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),
|
||||||
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
|
|
||||||
),
|
|
||||||
# Django Admin, use {% url 'admin:index' %}
|
# Django Admin, use {% url 'admin:index' %}
|
||||||
path(settings.ADMIN_URL, admin.site.urls),
|
path(settings.ADMIN_URL, admin.site.urls),
|
||||||
# User management
|
|
||||||
path("users/", include("vbv_lernwelt.users.urls", namespace="users")),
|
|
||||||
path("accounts/", include("allauth.urls")),
|
|
||||||
# Your stuff: custom urls includes go here
|
# Your stuff: custom urls includes go here
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,2 +1,3 @@
|
||||||
export VBV_DATABASE_URL='postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt'
|
export VBV_DATABASE_URL='postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt'
|
||||||
export VBV_DJANGO_LOGGING_CONF=VBV_DJANGO_LOGGING_CONF_CONSOLE_COLOR
|
export VBV_DJANGO_LOGGING_CONF=VBV_DJANGO_LOGGING_CONF_CONSOLE_COLOR
|
||||||
|
export VBV_DJANGO_DEBUG=True
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,6 @@ uvicorn[standard] # https://github.com/encode/uvicorn
|
||||||
django<4 # https://www.djangoproject.com/
|
django<4 # https://www.djangoproject.com/
|
||||||
django-environ # https://github.com/joke2k/django-environ
|
django-environ # https://github.com/joke2k/django-environ
|
||||||
django-model-utils # https://github.com/jazzband/django-model-utils
|
django-model-utils # https://github.com/jazzband/django-model-utils
|
||||||
django-allauth # https://github.com/pennersr/django-allauth
|
|
||||||
django-crispy-forms # https://github.com/django-crispy-forms/django-crispy-forms
|
|
||||||
crispy-bootstrap5 # https://github.com/django-crispy-forms/crispy-bootstrap5
|
|
||||||
django-redis # https://github.com/jazzband/django-redis
|
django-redis # https://github.com/jazzband/django-redis
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
djangorestframework # https://github.com/encode/django-rest-framework
|
djangorestframework # https://github.com/encode/django-rest-framework
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Account Inactive" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Account Inactive" %}</h1>
|
|
||||||
|
|
||||||
<p>{% translate "This account is inactive." %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 offset-md-3">
|
|
||||||
{% block inner %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
|
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Account" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "E-mail Addresses" %}</h1>
|
|
||||||
|
|
||||||
{% if user.emailaddress_set.all %}
|
|
||||||
<p>{% translate 'The following e-mail addresses are associated with your account:' %}</p>
|
|
||||||
|
|
||||||
<form action="{% url 'account_email' %}" class="email_list" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset class="blockLabels">
|
|
||||||
|
|
||||||
{% for emailaddress in user.emailaddress_set.all %}
|
|
||||||
<div class="radio">
|
|
||||||
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
|
|
||||||
|
|
||||||
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
|
|
||||||
|
|
||||||
{{ emailaddress.email }}
|
|
||||||
{% if emailaddress.verified %}
|
|
||||||
<span class="verified">{% translate "Verified" %}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="unverified">{% translate "Unverified" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if emailaddress.primary %}<span class="primary">{% translate "Primary" %}</span>{% endif %}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<button class="secondaryAction btn btn-primary" type="submit" name="action_primary" >{% translate 'Make Primary' %}</button>
|
|
||||||
<button class="secondaryAction btn btn-primary" type="submit" name="action_send" >{% translate 'Re-send Verification' %}</button>
|
|
||||||
<button class="primaryAction btn btn-primary" type="submit" name="action_remove" >{% translate 'Remove' %}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<p><strong>{% translate 'Warning:'%}</strong> {% translate "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}</p>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
<h2>{% translate "Add E-mail Address" %}</h2>
|
|
||||||
|
|
||||||
<form method="post" action="{% url 'account_email' %}" class="add_email">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
<button class="btn btn-primary" name="action_add" type="submit">{% translate "Add E-mail" %}</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block inline_javascript %}
|
|
||||||
{{ block.super }}
|
|
||||||
<script type="text/javascript">
|
|
||||||
window.addEventListener('DOMContentLoaded',function() {
|
|
||||||
const message = "{% translate 'Do you really want to remove the selected e-mail address?' %}";
|
|
||||||
const actions = document.getElementsByName('action_remove');
|
|
||||||
if (actions.length) {
|
|
||||||
actions[0].addEventListener("click",function(e) {
|
|
||||||
if (!confirm(message)) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Array.from(document.getElementsByClassName('form-group')).forEach(x => x.classList.remove('row'));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load account %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Confirm E-mail Address" %}</h1>
|
|
||||||
|
|
||||||
{% if confirmation %}
|
|
||||||
|
|
||||||
{% user_display confirmation.email_address.user as user_display %}
|
|
||||||
|
|
||||||
<p>{% blocktranslate with confirmation.email_address.email as email %}Please confirm that <a href="mailto:{{ email }}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktranslate %}</p>
|
|
||||||
|
|
||||||
<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button class="btn btn-primary" type="submit">{% translate 'Confirm' %}</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% url 'account_email' as email_url %}
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}This e-mail confirmation link expired or is invalid. Please <a href="{{ email_url }}">issue a new e-mail confirmation request</a>.{% endblocktranslate %}</p>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load account socialaccount %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Sign In" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
|
|
||||||
<h1>{% translate "Sign In" %}</h1>
|
|
||||||
|
|
||||||
{% get_providers as socialaccount_providers %}
|
|
||||||
|
|
||||||
{% if socialaccount_providers %}
|
|
||||||
<p>
|
|
||||||
{% translate "Please sign in with one of your existing third party accounts:" %}
|
|
||||||
{% if ACCOUNT_ALLOW_REGISTRATION %}
|
|
||||||
{% blocktranslate trimmed %}
|
|
||||||
Or, <a href="{{ signup_url }}">sign up</a>
|
|
||||||
for a {{ site_name }} account and sign in below:
|
|
||||||
{% endblocktranslate %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="socialaccount_ballot">
|
|
||||||
|
|
||||||
<ul class="socialaccount_providers">
|
|
||||||
{% include "socialaccount/snippets/provider_list.html" with process="login" %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="login-or">{% translate "or" %}</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include "socialaccount/snippets/login_extra.html" %}
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
{% if ACCOUNT_ALLOW_REGISTRATION %}
|
|
||||||
<p>
|
|
||||||
{% blocktranslate trimmed %}
|
|
||||||
If you have not created an account yet, then please
|
|
||||||
<a href="{{ signup_url }}">sign up</a> first.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form class="login" method="POST" action="{% url 'account_login' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
{% if redirect_field_value %}
|
|
||||||
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
|
|
||||||
{% endif %}
|
|
||||||
<a class="button secondaryAction" href="{% url 'account_reset_password' %}">{% translate "Forgot Password?" %}</a>
|
|
||||||
<button class="primaryAction btn btn-primary" type="submit">{% translate "Sign In" %}</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Sign Out" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Sign Out" %}</h1>
|
|
||||||
|
|
||||||
<p>{% translate 'Are you sure you want to sign out?' %}</p>
|
|
||||||
|
|
||||||
<form method="post" action="{% url 'account_logout' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% if redirect_field_value %}
|
|
||||||
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-danger" type="submit">{% translate 'Sign Out' %}</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Change Password" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Change Password" %}</h1>
|
|
||||||
|
|
||||||
<form method="POST" action="{% url 'account_change_password' %}" class="password_change">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
<button class="btn btn-primary" type="submit" name="action">{% translate "Change Password" %}</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load account %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Password Reset" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
|
|
||||||
<h1>{% translate "Password Reset" %}</h1>
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% include "account/snippets/already_logged_in.html" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>{% translate "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
|
|
||||||
|
|
||||||
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
<input class="btn btn-primary" type="submit" value="{% translate 'Reset My Password' %}" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load account %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Password Reset" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Password Reset" %}</h1>
|
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% include "account/snippets/already_logged_in.html" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
{% block head_title %}{% translate "Change Password" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% if token_fail %}{% translate "Bad Token" %}{% else %}{% translate "Change Password" %}{% endif %}</h1>
|
|
||||||
|
|
||||||
{% if token_fail %}
|
|
||||||
{% url 'account_reset_password' as passwd_reset_url %}
|
|
||||||
<p>{% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktranslate %}</p>
|
|
||||||
{% else %}
|
|
||||||
{% if form %}
|
|
||||||
<form method="POST" action=".">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
<input class="btn btn-primary" type="submit" name="action" value="{% translate 'change password' %}"/>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<p>{% translate 'Your password is now changed.' %}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% block head_title %}{% translate "Change Password" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Change Password" %}</h1>
|
|
||||||
<p>{% translate 'Your password is now changed.' %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Set Password" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Set Password" %}</h1>
|
|
||||||
|
|
||||||
<form method="POST" action="{% url 'account_set_password' %}" class="password_set">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
<input class="btn btn-primary" type="submit" name="action" value="{% translate 'Set Password' %}"/>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Signup" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Sign Up" %}</h1>
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktranslate %}</p>
|
|
||||||
|
|
||||||
<form class="signup" id="signup_form" method="post" action="{% url 'account_signup' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
{% if redirect_field_value %}
|
|
||||||
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-primary" type="submit">{% translate "Sign Up" %} »</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Sign Up Closed" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Sign Up Closed" %}</h1>
|
|
||||||
|
|
||||||
<p>{% translate "We are sorry, but the sign up is currently closed." %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Verify Your E-mail Address" %}</h1>
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}</p>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
{% extends "account/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block inner %}
|
|
||||||
<h1>{% translate "Verify Your E-mail Address" %}</h1>
|
|
||||||
|
|
||||||
{% url 'account_email' as email_url %}
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}This part of the site requires us to verify that
|
|
||||||
you are who you claim to be. For this purpose, we require that you
|
|
||||||
verify ownership of your e-mail address. {% endblocktranslate %}</p>
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}We have sent an e-mail to you for
|
|
||||||
verification. Please click on the link inside this e-mail. Please
|
|
||||||
contact us if you do not receive it within a few minutes.{% endblocktranslate %}</p>
|
|
||||||
|
|
||||||
<p>{% blocktranslate %}<strong>Note:</strong> you can still <a href="{{ email_url }}">change your e-mail address</a>.{% endblocktranslate %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -6,15 +6,12 @@
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<title>{% block title %}VBV Lernwelt{% endblock title %}</title>
|
<title>{% block title %}VBV Lernwelt{% endblock title %}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="Behold My Awesome Project!">
|
<meta name="description" content="VBV Lernumgebung">
|
||||||
<meta name="author" content="Daniel Egger">
|
<meta name="author" content="Iterativ GmbH">
|
||||||
|
|
||||||
<link rel="icon" href="{% static 'images/favicons/favicon.ico' %}">
|
<link rel="icon" href="{% static 'images/favicons/favicon.ico' %}">
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
<!-- Latest compiled and minified Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
||||||
|
|
||||||
<!-- Your stuff: Third-party CSS libraries go here -->
|
<!-- Your stuff: Third-party CSS libraries go here -->
|
||||||
<!-- This file stores project-specific CSS -->
|
<!-- This file stores project-specific CSS -->
|
||||||
<link href="{% static 'css/project.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/project.min.css' %}" rel="stylesheet">
|
||||||
|
|
@ -23,10 +20,7 @@
|
||||||
================================================== -->
|
================================================== -->
|
||||||
{# Placed at the top of the document so pages load faster with defer #}
|
{# Placed at the top of the document so pages load faster with defer #}
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<!-- Bootstrap JS -->
|
|
||||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.min.js" integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
||||||
<!-- Your stuff: Third-party javascript libraries go here -->
|
<!-- Your stuff: Third-party javascript libraries go here -->
|
||||||
|
|
||||||
<!-- place project specific Javascript in this file -->
|
<!-- place project specific Javascript in this file -->
|
||||||
<script defer src="{% static 'js/project.js' %}"></script>
|
<script defer src="{% static 'js/project.js' %}"></script>
|
||||||
|
|
||||||
|
|
@ -52,27 +46,27 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'about' %}">About</a>
|
<a class="nav-link" href="{% url 'about' %}">About</a>
|
||||||
</li>
|
</li>
|
||||||
{% if request.user.is_authenticated %}
|
{# {% if request.user.is_authenticated %}#}
|
||||||
<li class="nav-item">
|
{# <li class="nav-item">#}
|
||||||
{# URL provided by django-allauth/account/urls.py #}
|
{# URL provided by django-allauth/account/urls.py #}
|
||||||
<a class="nav-link" href="{% url 'users:detail' request.user.username %}">{% translate "My Profile" %}</a>
|
{# <a class="nav-link" href="{% url 'users:detail' request.user.username %}">{% translate "My Profile" %}</a>#}
|
||||||
</li>
|
{# </li>#}
|
||||||
<li class="nav-item">
|
{# <li class="nav-item">#}
|
||||||
{# URL provided by django-allauth/account/urls.py #}
|
{# URL provided by django-allauth/account/urls.py #}
|
||||||
<a class="nav-link" href="{% url 'account_logout' %}">{% translate "Sign Out" %}</a>
|
{# <a class="nav-link" href="{% url 'account_logout' %}">{% translate "Sign Out" %}</a>#}
|
||||||
</li>
|
{# </li>#}
|
||||||
{% else %}
|
{# {% else %}#}
|
||||||
{% if ACCOUNT_ALLOW_REGISTRATION %}
|
{# {% if ACCOUNT_ALLOW_REGISTRATION %}#}
|
||||||
<li class="nav-item">
|
{# <li class="nav-item">#}
|
||||||
{# URL provided by django-allauth/account/urls.py #}
|
{# URL provided by django-allauth/account/urls.py #}
|
||||||
<a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% translate "Sign Up" %}</a>
|
{# <a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% translate "Sign Up" %}</a>#}
|
||||||
</li>
|
{# </li>#}
|
||||||
{% endif %}
|
{# {% endif %}#}
|
||||||
<li class="nav-item">
|
{# <li class="nav-item">#}
|
||||||
{# URL provided by django-allauth/account/urls.py #}
|
{# URL provided by django-allauth/account/urls.py #}
|
||||||
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% translate "Sign In" %}</a>
|
{# <a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% translate "Sign In" %}</a>#}
|
||||||
</li>
|
{# </li>#}
|
||||||
{% endif %}
|
{# {% endif %}#}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}User: {{ object.username }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
|
|
||||||
<h2>{{ object.username }}</h2>
|
|
||||||
{% if object.name %}
|
|
||||||
<p>{{ object.name }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if object == request.user %}
|
|
||||||
<!-- Action buttons -->
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<a class="btn btn-primary" href="{% url 'users:update' %}" role="button">My Info</a>
|
|
||||||
<a class="btn btn-primary" href="{% url 'account_email' %}" role="button">E-Mail</a>
|
|
||||||
<!-- Your Stuff: Custom user template urls -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- End Action buttons -->
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block title %}{{ user.username }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ user.username }}</h1>
|
|
||||||
<form class="form-horizontal" method="post" action="{% url 'users:update' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="controls">
|
|
||||||
<button type="submit" class="btn btn-primary">Update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from allauth.account.adapter import DefaultAccountAdapter
|
|
||||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
|
||||||
from django.conf import settings
|
|
||||||
from django.http import HttpRequest
|
|
||||||
|
|
||||||
|
|
||||||
class AccountAdapter(DefaultAccountAdapter):
|
|
||||||
def is_open_for_signup(self, request: HttpRequest):
|
|
||||||
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
|
||||||
|
|
||||||
|
|
||||||
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
|
||||||
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
|
|
||||||
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
|
||||||
|
|
@ -3,19 +3,15 @@ from django.contrib.auth import admin as auth_admin
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from vbv_lernwelt.users.forms import UserAdminChangeForm, UserAdminCreationForm
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class UserAdmin(auth_admin.UserAdmin):
|
class UserAdmin(auth_admin.UserAdmin):
|
||||||
|
|
||||||
form = UserAdminChangeForm
|
|
||||||
add_form = UserAdminCreationForm
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {"fields": ("username", "password")}),
|
(None, {"fields": ("username", "password")}),
|
||||||
(_("Personal info"), {"fields": ("name", "email")}),
|
(_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
|
||||||
(
|
(
|
||||||
_("Permissions"),
|
_("Permissions"),
|
||||||
{
|
{
|
||||||
|
|
@ -30,5 +26,5 @@ class UserAdmin(auth_admin.UserAdmin):
|
||||||
),
|
),
|
||||||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||||
)
|
)
|
||||||
list_display = ["username", "name", "is_superuser"]
|
list_display = ["username", "first_name", "last_name", "is_active", "is_superuser"]
|
||||||
search_fields = ["name"]
|
search_fields = ["first_name", "last_name", "email", "username"]
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ["username", "name", "url"]
|
|
||||||
|
|
||||||
extra_kwargs = {
|
|
||||||
"url": {"view_name": "api:user-detail", "lookup_field": "username"}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.viewsets import GenericViewSet
|
|
||||||
|
|
||||||
from .serializers import UserSerializer
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
queryset = User.objects.all()
|
|
||||||
lookup_field = "username"
|
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
|
||||||
assert isinstance(self.request.user.id, int)
|
|
||||||
return self.queryset.filter(id=self.request.user.id)
|
|
||||||
|
|
||||||
@action(detail=False)
|
|
||||||
def me(self, request):
|
|
||||||
serializer = UserSerializer(request.user, context={"request": request})
|
|
||||||
return Response(status=status.HTTP_200_OK, data=serializer.data)
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
def allauth_settings(request):
|
|
||||||
"""Expose some settings from django-allauth in templates."""
|
|
||||||
return {
|
|
||||||
"ACCOUNT_ALLOW_REGISTRATION": settings.ACCOUNT_ALLOW_REGISTRATION,
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +1,3 @@
|
||||||
from allauth.account.forms import SignupForm
|
|
||||||
from allauth.socialaccount.forms import SignupForm as SocialSignupForm
|
|
||||||
from django.contrib.auth import forms as admin_forms
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class UserAdminChangeForm(admin_forms.UserChangeForm):
|
|
||||||
class Meta(admin_forms.UserChangeForm.Meta):
|
|
||||||
model = User
|
|
||||||
|
|
||||||
|
|
||||||
class UserAdminCreationForm(admin_forms.UserCreationForm):
|
|
||||||
"""
|
|
||||||
Form for User Creation in the Admin Area.
|
|
||||||
To change user signup, see UserSignupForm and UserSocialSignupForm.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta(admin_forms.UserCreationForm.Meta):
|
|
||||||
model = User
|
|
||||||
|
|
||||||
error_messages = {
|
|
||||||
"username": {"unique": _("This username has already been taken.")}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class UserSignupForm(SignupForm):
|
|
||||||
"""
|
|
||||||
Form that will be rendered on a user sign up section/screen.
|
|
||||||
Default fields will be added automatically.
|
|
||||||
Check UserSocialSignupForm for accounts created from social.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class UserSocialSignupForm(SocialSignupForm):
|
|
||||||
"""
|
|
||||||
Renders the form when user has signed up using social accounts.
|
|
||||||
Default fields will be added automatically.
|
|
||||||
See UserSignupForm otherwise.
|
|
||||||
"""
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# Generated by Django 3.2.9 on 2021-11-20 11:23
|
# Generated by Django 3.2.12 on 2022-02-02 13:01
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
@ -10,116 +11,34 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="User",
|
name='User',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
"id",
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
models.BigAutoField(
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
auto_created=True,
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
primary_key=True,
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
serialize=False,
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
verbose_name="ID",
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
),
|
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||||
),
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
('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')),
|
||||||
(
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
"last_login",
|
('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')),
|
||||||
models.DateTimeField(
|
('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')),
|
||||||
blank=True, null=True, verbose_name="last login"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"is_superuser",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
|
||||||
verbose_name="superuser status",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"username",
|
|
||||||
models.CharField(
|
|
||||||
error_messages={
|
|
||||||
"unique": "A user with that username already exists."
|
|
||||||
},
|
|
||||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
|
||||||
max_length=150,
|
|
||||||
unique=True,
|
|
||||||
validators=[
|
|
||||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
|
||||||
],
|
|
||||||
verbose_name="username",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"email",
|
|
||||||
models.EmailField(
|
|
||||||
blank=True, max_length=254, verbose_name="email address"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"is_staff",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Designates whether the user can log into this admin site.",
|
|
||||||
verbose_name="staff status",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"date_joined",
|
|
||||||
models.DateTimeField(
|
|
||||||
default=django.utils.timezone.now, verbose_name="date joined"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"name",
|
|
||||||
models.CharField(
|
|
||||||
blank=True, max_length=255, verbose_name="Name of User"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"groups",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
|
||||||
related_name="user_set",
|
|
||||||
related_query_name="user",
|
|
||||||
to="auth.Group",
|
|
||||||
verbose_name="groups",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user_permissions",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
help_text="Specific permissions for this user.",
|
|
||||||
related_name="user_set",
|
|
||||||
related_query_name="user",
|
|
||||||
to="auth.Permission",
|
|
||||||
verbose_name="user permissions",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
"verbose_name": "user",
|
'verbose_name': 'user',
|
||||||
"verbose_name_plural": "users",
|
'verbose_name_plural': 'users',
|
||||||
"abstract": False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
managers=[
|
managers=[
|
||||||
("objects", django.contrib.auth.models.UserManager()),
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,12 @@
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db.models import CharField
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
"""
|
"""
|
||||||
Default custom user model for VBV Lernwelt.
|
Default custom user model for VBV Lernwelt.
|
||||||
If adding fields that need to be filled at user signup,
|
If adding fields that need to be filled at user signup,
|
||||||
check forms.SignupForm and forms.SocialSignupForms accordingly.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: First and last name do not cover name patterns around the globe
|
|
||||||
name = CharField(_("Name of User"), blank=True, max_length=255)
|
|
||||||
first_name = None # type: ignore
|
|
||||||
last_name = None # type: ignore
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
"""Get url for user's detail view.
|
"""Get url for user's detail view.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
from typing import Any, Sequence
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from factory import Faker, post_generation
|
|
||||||
from factory.django import DjangoModelFactory
|
|
||||||
|
|
||||||
|
|
||||||
class UserFactory(DjangoModelFactory):
|
|
||||||
|
|
||||||
username = Faker("user_name")
|
|
||||||
email = Faker("email")
|
|
||||||
name = Faker("name")
|
|
||||||
|
|
||||||
@post_generation
|
|
||||||
def password(self, create: bool, extracted: Sequence[Any], **kwargs):
|
|
||||||
password = (
|
|
||||||
extracted
|
|
||||||
if extracted
|
|
||||||
else Faker(
|
|
||||||
"password",
|
|
||||||
length=42,
|
|
||||||
special_chars=True,
|
|
||||||
digits=True,
|
|
||||||
upper_case=True,
|
|
||||||
lower_case=True,
|
|
||||||
).evaluate(None, None, extra={"locale": None})
|
|
||||||
)
|
|
||||||
self.set_password(password)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = get_user_model()
|
|
||||||
django_get_or_create = ["username"]
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserAdmin:
|
|
||||||
def test_changelist(self, admin_client):
|
|
||||||
url = reverse("admin:users_user_changelist")
|
|
||||||
response = admin_client.get(url)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
def test_search(self, admin_client):
|
|
||||||
url = reverse("admin:users_user_changelist")
|
|
||||||
response = admin_client.get(url, data={"q": "test"})
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
def test_add(self, admin_client):
|
|
||||||
url = reverse("admin:users_user_add")
|
|
||||||
response = admin_client.get(url)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
response = admin_client.post(
|
|
||||||
url,
|
|
||||||
data={
|
|
||||||
"username": "test",
|
|
||||||
"password1": "My_R@ndom-P@ssw0rd",
|
|
||||||
"password2": "My_R@ndom-P@ssw0rd",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert response.status_code == 302
|
|
||||||
assert User.objects.filter(username="test").exists()
|
|
||||||
|
|
||||||
def test_view_user(self, admin_client):
|
|
||||||
user = User.objects.get(username="admin")
|
|
||||||
url = reverse("admin:users_user_change", kwargs={"object_id": user.pk})
|
|
||||||
response = admin_client.get(url)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.urls import resolve, reverse
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_detail(user: User):
|
|
||||||
assert (
|
|
||||||
reverse("api:user-detail", kwargs={"username": user.username})
|
|
||||||
== f"/api/users/{user.username}/"
|
|
||||||
)
|
|
||||||
assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_list():
|
|
||||||
assert reverse("api:user-list") == "/api/users/"
|
|
||||||
assert resolve("/api/users/").view_name == "api:user-list"
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_me():
|
|
||||||
assert reverse("api:user-me") == "/api/users/me/"
|
|
||||||
assert resolve("/api/users/me/").view_name == "api:user-me"
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.test import RequestFactory
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.api.views import UserViewSet
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserViewSet:
|
|
||||||
def test_get_queryset(self, user: User, rf: RequestFactory):
|
|
||||||
view = UserViewSet()
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
view.request = request
|
|
||||||
|
|
||||||
assert user in view.get_queryset()
|
|
||||||
|
|
||||||
def test_me(self, user: User, rf: RequestFactory):
|
|
||||||
view = UserViewSet()
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
view.request = request
|
|
||||||
|
|
||||||
response = view.me(request)
|
|
||||||
|
|
||||||
assert response.data == {
|
|
||||||
"username": user.username,
|
|
||||||
"name": user.name,
|
|
||||||
"url": f"http://testserver/api/users/{user.username}/",
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
Module for all Form Tests.
|
|
||||||
"""
|
|
||||||
import pytest
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.forms import UserAdminCreationForm
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserAdminCreationForm:
|
|
||||||
"""
|
|
||||||
Test class for all tests related to the UserAdminCreationForm
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_username_validation_error_msg(self, user: User):
|
|
||||||
"""
|
|
||||||
Tests UserAdminCreation Form's unique validator functions correctly by testing:
|
|
||||||
1) A new user with an existing username cannot be added.
|
|
||||||
2) Only 1 error is raised by the UserCreation Form
|
|
||||||
3) The desired error message is raised
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The user already exists,
|
|
||||||
# hence cannot be created.
|
|
||||||
form = UserAdminCreationForm(
|
|
||||||
{
|
|
||||||
"username": user.username,
|
|
||||||
"password1": user.password,
|
|
||||||
"password2": user.password,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not form.is_valid()
|
|
||||||
assert len(form.errors) == 1
|
|
||||||
assert "username" in form.errors
|
|
||||||
assert form.errors["username"][0] == _("This username has already been taken.")
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_get_absolute_url(user: User):
|
|
||||||
assert user.get_absolute_url() == f"/users/{user.username}/"
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_swagger_accessible_by_admin(admin_client):
|
|
||||||
url = reverse("api-docs")
|
|
||||||
response = admin_client.get(url)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_swagger_ui_not_accessible_by_normal_user(client):
|
|
||||||
url = reverse("api-docs")
|
|
||||||
response = client.get(url)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.urls import resolve, reverse
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail(user: User):
|
|
||||||
assert (
|
|
||||||
reverse("users:detail", kwargs={"username": user.username})
|
|
||||||
== f"/users/{user.username}/"
|
|
||||||
)
|
|
||||||
assert resolve(f"/users/{user.username}/").view_name == "users:detail"
|
|
||||||
|
|
||||||
|
|
||||||
def test_update():
|
|
||||||
assert reverse("users:update") == "/users/~update/"
|
|
||||||
assert resolve("/users/~update/").view_name == "users:update"
|
|
||||||
|
|
||||||
|
|
||||||
def test_redirect():
|
|
||||||
assert reverse("users:redirect") == "/users/~redirect/"
|
|
||||||
assert resolve("/users/~redirect/").view_name == "users:redirect"
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
|
||||||
from django.contrib.messages.middleware import MessageMiddleware
|
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
|
||||||
from django.http import HttpRequest, HttpResponseRedirect
|
|
||||||
from django.test import RequestFactory
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.forms import UserAdminChangeForm
|
|
||||||
from vbv_lernwelt.users.models import User
|
|
||||||
from vbv_lernwelt.users.tests.factories import UserFactory
|
|
||||||
from vbv_lernwelt.users.views import (
|
|
||||||
UserRedirectView,
|
|
||||||
UserUpdateView,
|
|
||||||
user_detail_view,
|
|
||||||
)
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserUpdateView:
|
|
||||||
"""
|
|
||||||
TODO:
|
|
||||||
extracting view initialization code as class-scoped fixture
|
|
||||||
would be great if only pytest-django supported non-function-scoped
|
|
||||||
fixture db access -- this is a work-in-progress for now:
|
|
||||||
https://github.com/pytest-dev/pytest-django/pull/258
|
|
||||||
"""
|
|
||||||
|
|
||||||
def dummy_get_response(self, request: HttpRequest):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def test_get_success_url(self, user: User, rf: RequestFactory):
|
|
||||||
view = UserUpdateView()
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
view.request = request
|
|
||||||
|
|
||||||
assert view.get_success_url() == f"/users/{user.username}/"
|
|
||||||
|
|
||||||
def test_get_object(self, user: User, rf: RequestFactory):
|
|
||||||
view = UserUpdateView()
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
view.request = request
|
|
||||||
|
|
||||||
assert view.get_object() == user
|
|
||||||
|
|
||||||
def test_form_valid(self, user: User, rf: RequestFactory):
|
|
||||||
view = UserUpdateView()
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
|
|
||||||
# Add the session/message middleware to the request
|
|
||||||
SessionMiddleware(self.dummy_get_response).process_request(request)
|
|
||||||
MessageMiddleware(self.dummy_get_response).process_request(request)
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
view.request = request
|
|
||||||
|
|
||||||
# Initialize the form
|
|
||||||
form = UserAdminChangeForm()
|
|
||||||
form.cleaned_data = []
|
|
||||||
view.form_valid(form)
|
|
||||||
|
|
||||||
messages_sent = [m.message for m in messages.get_messages(request)]
|
|
||||||
assert messages_sent == ["Information successfully updated"]
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserRedirectView:
|
|
||||||
def test_get_redirect_url(self, user: User, rf: RequestFactory):
|
|
||||||
view = UserRedirectView()
|
|
||||||
request = rf.get("/fake-url")
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
view.request = request
|
|
||||||
|
|
||||||
assert view.get_redirect_url() == f"/users/{user.username}/"
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserDetailView:
|
|
||||||
def test_authenticated(self, user: User, rf: RequestFactory):
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
request.user = UserFactory()
|
|
||||||
|
|
||||||
response = user_detail_view(request, username=user.username)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
def test_not_authenticated(self, user: User, rf: RequestFactory):
|
|
||||||
request = rf.get("/fake-url/")
|
|
||||||
request.user = AnonymousUser()
|
|
||||||
|
|
||||||
response = user_detail_view(request, username=user.username)
|
|
||||||
login_url = reverse(settings.LOGIN_URL)
|
|
||||||
|
|
||||||
assert isinstance(response, HttpResponseRedirect)
|
|
||||||
assert response.status_code == 302
|
|
||||||
assert response.url == f"{login_url}?next=/fake-url/"
|
|
||||||
|
|
@ -1,14 +1 @@
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from vbv_lernwelt.users.views import (
|
|
||||||
user_detail_view,
|
|
||||||
user_redirect_view,
|
|
||||||
user_update_view,
|
|
||||||
)
|
|
||||||
|
|
||||||
app_name = "users"
|
|
||||||
urlpatterns = [
|
|
||||||
path("~redirect/", view=user_redirect_view, name="redirect"),
|
|
||||||
path("~update/", view=user_update_view, name="update"),
|
|
||||||
path("<str:username>/", view=user_detail_view, name="detail"),
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,2 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserDetailView(LoginRequiredMixin, DetailView):
|
|
||||||
|
|
||||||
model = User
|
|
||||||
slug_field = "username"
|
|
||||||
slug_url_kwarg = "username"
|
|
||||||
|
|
||||||
|
|
||||||
user_detail_view = UserDetailView.as_view()
|
|
||||||
|
|
||||||
|
|
||||||
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
||||||
|
|
||||||
model = User
|
|
||||||
fields = ["name"]
|
|
||||||
success_message = _("Information successfully updated")
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
assert (
|
|
||||||
self.request.user.is_authenticated
|
|
||||||
) # for mypy to know that the user is authenticated
|
|
||||||
return self.request.user.get_absolute_url()
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return self.request.user
|
|
||||||
|
|
||||||
|
|
||||||
user_update_view = UserUpdateView.as_view()
|
|
||||||
|
|
||||||
|
|
||||||
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
|
||||||
|
|
||||||
permanent = False
|
|
||||||
|
|
||||||
def get_redirect_url(self):
|
|
||||||
return reverse("users:detail", kwargs={"username": self.request.user.username})
|
|
||||||
|
|
||||||
|
|
||||||
user_redirect_view = UserRedirectView.as_view()
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue