Merge pull request #2371 from matmair/matmair/issue2327

[FR] User sessions
This commit is contained in:
Oliver 2021-11-29 07:32:18 +11:00 committed by GitHub
commit cbc3089525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 123 additions and 51 deletions

View File

@ -257,7 +257,7 @@ INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'user_sessions', # db user sessions
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
@ -299,7 +299,7 @@ INSTALLED_APPS = [
MIDDLEWARE = CONFIG.get('middleware', [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'user_sessions.middleware.SessionMiddleware', # db user sessions
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -626,6 +626,12 @@ if _cache_host:
# as well
Q_CLUSTER["django_redis"] = "worker"
# database user sessions
SESSION_ENGINE = 'user_sessions.backends.db'
LOGOUT_REDIRECT_URL = 'index'
SILENCED_SYSTEM_CHECKS = [
'admin.E410',
]
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators

View File

@ -38,6 +38,7 @@ from rest_framework.documentation import include_docs_urls
from .views import auth_request
from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView
from .views import CustomSessionDeleteView, CustomSessionDeleteOtherView
from .views import CurrencyRefreshView
from .views import AppearanceSelectView, SettingCategorySelectView
from .views import DynamicJsView
@ -156,6 +157,10 @@ urlpatterns = [
url(r'^markdownx/', include('markdownx.urls')),
# DB user sessions
url(r'^accounts/sessions/other/delete/$', view=CustomSessionDeleteOtherView.as_view(), name='session_delete_other', ),
url(r'^accounts/sessions/(?P<pk>\w+)/delete/$', view=CustomSessionDeleteView.as_view(), name='session_delete', ),
# Single Sign On / allauth
# overrides of urlpatterns
url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'),

View File

@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.timezone import now
from django.shortcuts import redirect
from django.conf import settings
@ -29,6 +30,7 @@ from allauth.socialaccount.forms import DisconnectForm
from allauth.account.models import EmailAddress
from allauth.account.views import EmailView, PasswordResetFromKeyView
from allauth.socialaccount.views import ConnectionsView
from user_sessions.views import SessionDeleteView, SessionDeleteOtherView
from common.settings import currency_code_default, currency_codes
@ -733,6 +735,10 @@ class SettingsView(TemplateView):
ctx["request"] = self.request
ctx['social_form'] = DisconnectForm(request=self.request)
# user db sessions
ctx['session_key'] = self.request.session.session_key
ctx['session_list'] = self.request.user.session_set.filter(expire_date__gt=now()).order_by('-last_activity')
return ctx
@ -766,6 +772,20 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView):
success_url = reverse_lazy("account_login")
class UserSessionOverride():
"""overrides sucessurl to lead to settings"""
def get_success_url(self):
return str(reverse_lazy('settings'))
class CustomSessionDeleteView(UserSessionOverride, SessionDeleteView):
pass
class CustomSessionDeleteOtherView(UserSessionOverride, SessionDeleteOtherView):
pass
class CurrencyRefreshView(RedirectView):
"""
POST endpoint to refresh / update exchange rates

View File

@ -4,6 +4,7 @@
{% load inventree_extras %}
{% load socialaccount %}
{% load crispy_forms_tags %}
{% load user_sessions i18n %}
{% block label %}account{% endblock %}
@ -174,58 +175,44 @@
</div>
<div class='panel-heading'>
<h4>{% trans "Language Settings" %}</h4>
<h4>{% trans "Active Sessions" %}</h4>
</div>
<div class="row">
<div class="col">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{% url 'settings' %}">
<label for='language' class=' requiredField'>
{% trans "Select language" %}
</label>
<div class='form-group input-group mb-3'>
<select name="language" class="select form-control w-25">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% if 'alllang' in request.GET %}{% define True as ALL_LANG %}{% endif %}
{% for language in languages %}
{% define language.code as lang_code %}
{% define locale_stats|keyvalue:lang_code as lang_translated %}
{% if lang_translated > 10 or lang_code == 'en' or lang_code == LANGUAGE_CODE %}{% define True as use_lang %}{% else %}{% define False as use_lang %}{% endif %}
{% if ALL_LANG or use_lang %}
<option value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ lang_code }})
{% if lang_translated %}
{% blocktrans %}{{ lang_translated }}% translated{% endblocktrans %}
{% else %}
{% if lang_code == 'en' %}-{% else %}{% trans 'No translations available' %}{% endif %}
{% endif %}
</option>
{% endif %}
{% endfor %}
</select>
<div class='input-group-append'>
<input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary">
</div>
</div>
<p>{% trans "Some languages are not complete" %}
{% if ALL_LANG %}
. <a href="{% url 'settings' %}">{% trans "Show only sufficent" %}</a>
<div>
{% trans "<em>unknown on unknown</em>" as unknown_on_unknown %}
{% trans "<em>unknown</em>" as unknown %}
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{% trans "IP Address" %}</th>
<th>{% trans "Device" %}</th>
<th>{% trans "Last Activity" %}</th>
</tr>
</thead>
{% for object in session_list %}
<tr {% if object.session_key == session_key %}class="active"{% endif %}>
<td>{{ object.ip }}</td>
<td>{{ object.user_agent|device|default_if_none:unknown_on_unknown|safe }}</td>
<td>
{% if object.session_key == session_key %}
{% blocktrans with time=object.last_activity|timesince %}{{ time }} ago (this session){% endblocktrans %}
{% else %}
{% trans "and hidden." %} <a href="?alllang">{% trans "Show them too" %}</a>
{% blocktrans with time=object.last_activity|timesince %}{{ time }} ago{% endblocktrans %}
{% endif %}
</p>
</form>
</div>
<div class="col-sm-6">
<h4>{% trans "Help the translation efforts!" %}</h4>
<p>{% blocktrans with link="https://crowdin.com/project/inventree" %}Native language translation of the InvenTree web application is <a href="{{link}}">community contributed via crowdin</a>. Contributions are welcomed and encouraged.{% endblocktrans %}</p>
</div>
</div>
</td>
</tr>
{% endfor %}
</table>
{% if session_list.count > 1 %}
<form method="post" action="{% url 'session_delete_other' %}">
{% csrf_token %}
<p>{% blocktrans %}You can also end all other sessions but the current.
This will log you out on all other devices.{% endblocktrans %}
<button type="submit" class="btn btn-sm btn-default btn-danger">{% trans "End All Other Sessions" %}</button></p>
</form>
{% endif %}
</div>
{% endblock %}
{% block js_ready %}

View File

@ -50,4 +50,57 @@
</div>
</div>
<div class='panel-heading'>
<h4>{% trans "Language Settings" %}</h4>
</div>
<div class="row">
<div class="col">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{% url 'settings' %}">
<label for='language' class=' requiredField'>
{% trans "Select language" %}
</label>
<div class='form-group input-group mb-3'>
<select name="language" class="select form-control w-25">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% if 'alllang' in request.GET %}{% define True as ALL_LANG %}{% endif %}
{% for language in languages %}
{% define language.code as lang_code %}
{% define locale_stats|keyvalue:lang_code as lang_translated %}
{% if lang_translated > 10 or lang_code == 'en' or lang_code == LANGUAGE_CODE %}{% define True as use_lang %}{% else %}{% define False as use_lang %}{% endif %}
{% if ALL_LANG or use_lang %}
<option value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ lang_code }})
{% if lang_translated %}
{% blocktrans %}{{ lang_translated }}% translated{% endblocktrans %}
{% else %}
{% if lang_code == 'en' %}-{% else %}{% trans 'No translations available' %}{% endif %}
{% endif %}
</option>
{% endif %}
{% endfor %}
</select>
<div class='input-group-append'>
<input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary">
</div>
</div>
<p>{% trans "Some languages are not complete" %}
{% if ALL_LANG %}
. <a href="{% url 'settings' %}">{% trans "Show only sufficent" %}</a>
{% else %}
{% trans "and hidden." %} <a href="?alllang">{% trans "Show them too" %}</a>
{% endif %}
</p>
</form>
</div>
<div class="col-sm-6">
<h4>{% trans "Help the translation efforts!" %}</h4>
<p>{% blocktrans with link="https://crowdin.com/project/inventree" %}Native language translation of the InvenTree web application is <a href="{{link}}">community contributed via crowdin</a>. Contributions are welcomed and encouraged.{% endblocktrans %}</p>
</div>
</div>
{% endblock %}

View File

@ -145,7 +145,6 @@ class RuleSet(models.Model):
# Core django models (not user configurable)
'admin_logentry',
'contenttypes_contenttype',
'sessions_session',
# Models which currently do not require permissions
'common_colortheme',
@ -159,6 +158,7 @@ class RuleSet(models.Model):
'error_report_error',
'exchange_rate',
'exchange_exchangebackend',
'user_sessions_session',
# Django-q
'django_q_ormq',

View File

@ -23,6 +23,7 @@ django-q==1.3.4 # Background task scheduling
django-sql-utils==0.5.0 # Advanced query annotation / aggregation
django-stdimage==5.1.1 # Advanced ImageField management
django-test-migrations==1.1.0 # Unit testing for database migrations
django-user-sessions==1.7.1 # user sessions in DB
django-weasyprint==1.0.1 # django weasyprint integration
djangorestframework==3.12.4 # DRF framework
flake8==3.8.3 # PEP checking

View File

@ -279,7 +279,6 @@ def content_excludes():
excludes = [
"contenttypes",
"sessions.session",
"auth.permission",
"authtoken.token",
"error_report.error",
@ -291,6 +290,7 @@ def content_excludes():
"exchange.rate",
"exchange.exchangebackend",
"common.notificationentry",
"user_sessions.session",
]
output = ""