diff --git a/README.md b/README.md
index 469a4e17b681f4021609071f459bd6e7261527a7..5116df20b6e49d57f343adea6865f2ab56b6fb70 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@ Django app na uživatele, týmy a skupiny, s napojením na LDAP a SSO.
 
 [![code style: Black](https://img.shields.io/badge/code%20style-Black-000000)](https://github.com/psf/black)
 [![license MIT](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
+![Python Version](https://img.shields.io/pypi/pyversions/pirates)
+![Django Version](https://img.shields.io/pypi/djversions/pirates?color=0C4B33)
 
 ## Použití
 
@@ -73,3 +75,13 @@ OIDC_OP_USER_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/userinf
 ```
 
 URL patterns pro OpenID Connect už jsou součástí `pirates.urls` (viz výše).
+
+#### Signál po přihlášení
+
+Po přihlášení uživatele je poslán signál `pirates.signals.post_login` s
+parametry:
+
+* `sender` - `PiratesOIDCAuthenticationBackend`
+* `user` - přihlášený uživatel (instance `AUTH_USER_MODEL`)
+* `created` - `True`/`False` zda-li byl vytvořen nový uživatel
+* `request` - instance `HttpRequest`
diff --git a/pirates/auth.py b/pirates/auth.py
index e45133e9460ed43e1b44cb3826d86e9451180304..eade39779156c662641a3ac056e1216b99cb60aa 100644
--- a/pirates/auth.py
+++ b/pirates/auth.py
@@ -1,5 +1,7 @@
 from mozilla_django_oidc.auth import OIDCAuthenticationBackend
 
+from .signals import post_login
+
 
 class PiratesOIDCAuthenticationBackend(OIDCAuthenticationBackend):
     """
@@ -23,13 +25,25 @@ class PiratesOIDCAuthenticationBackend(OIDCAuthenticationBackend):
         first_name = claims.get("given_name", "")
         last_name = claims.get("family_name", "")
         email = claims.get("email", "")
-        return self.UserModel.objects.create(
+        user = self.UserModel.objects.create(
             sso_id=sso_id, first_name=first_name, last_name=last_name, email=email
         )
+        self.send_post_login_signal(user, True, claims)
+        return user
 
     def update_user(self, user, claims):
         user.first_name = claims.get("given_name", "")
         user.last_name = claims.get("family_name", "")
         user.email = claims.get("email", "")
         user.save()
+        self.send_post_login_signal(user, False, claims)
         return user
+
+    def send_post_login_signal(self, user, created, claims):
+        post_login.send(
+            sender=self.__class__,
+            user=user,
+            created=created,
+            claims=claims,
+            request=self.request,
+        )
diff --git a/pirates/signals.py b/pirates/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..d14a1302bc0db50463e193388a1041ac13f2d1d9
--- /dev/null
+++ b/pirates/signals.py
@@ -0,0 +1,3 @@
+from django.dispatch import Signal
+
+post_login = Signal()
diff --git a/setup.py b/setup.py
index 345874a165722da9521abcafd61e9f09f1e13b74..3e71bf91fcee079b59329d509451d9c372cf9a1f 100755
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@ def read(fname):
 
 setup(
     name="pirates",
-    version="0.3.1",
+    version="0.4.0",
     license="MIT",
     description="Django app for users, teamds and groups.",
     long_description=read("README.md"),
@@ -34,7 +34,12 @@ setup(
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: Implementation :: CPython",
+        "Framework :: Django",
+        "Framework :: Django :: 2.2",
+        "Framework :: Django :: 3.0",
+        "Framework :: Django :: 3.1",
         "Topic :: Utilities",
     ],
     project_urls={
@@ -44,5 +49,5 @@ setup(
     },
     keywords=["django", "openid", "sso"],
     python_requires=">=3.6",
-    install_requires=["mozilla-django-oidc>=1.2.3,<2", "python-ldap>=3.2.0,<4"],
+    install_requires=["mozilla-django-oidc>=1.2.4,<2", "python-ldap>=3.2.0,<4"],
 )
diff --git a/tests/requirements.txt b/tests/requirements.txt
index e2f939e9e9cadcc1d972bda9c53d66ae966033b7..8bb1a4ea82b42a97a73f3c148274b900a259a885 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -2,3 +2,4 @@ pytest
 pytest-cov
 pytest-factoryboy
 pytest-django
+pytest-mock
diff --git a/tests/test_auth.py b/tests/test_auth.py
index b9f69522fad862d9013822aa8e6de53af5775beb..98cf4c5b9ffc764282c781836a177497d810f414 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -10,8 +10,10 @@ fake = Faker()
 
 
 @pytest.fixture
-def backend():
-    return PiratesOIDCAuthenticationBackend()
+def backend(mocker):
+    instance = PiratesOIDCAuthenticationBackend()
+    instance.request = mocker.Mock()
+    return instance
 
 
 def test_auth_backend__get_sso_id(backend):
@@ -69,3 +71,39 @@ def test_auth_backend__update_user(backend, user):
     assert updated_user.last_name == claims["family_name"]
     assert updated_user.email == claims["email"]
     assert get_user_model().objects.get() == updated_user
+
+
+def test_auth_backend__create_user__send_post_login(backend, mocker):
+    m_post_login = mocker.patch("pirates.auth.post_login")
+    claims = {
+        "sub": fake.random_letters(),
+        "given_name": fake.first_name(),
+        "family_name": fake.last_name(),
+        "email": fake.email(),
+    }
+    user = backend.create_user(claims)
+    m_post_login.send.assert_called_once_with(
+        sender=backend.__class__,
+        user=user,
+        created=True,
+        claims=claims,
+        request=backend.request,
+    )
+
+
+def test_auth_backend__update_user__send_post_login(backend, user, mocker):
+    m_post_login = mocker.patch("pirates.auth.post_login")
+    claims = {
+        "sub": fake.random_letters(),
+        "given_name": fake.first_name(),
+        "family_name": fake.last_name(),
+        "email": fake.email(),
+    }
+    updated_user = backend.update_user(user, claims)
+    m_post_login.send.assert_called_once_with(
+        sender=backend.__class__,
+        user=updated_user,
+        created=False,
+        claims=claims,
+        request=backend.request,
+    )
diff --git a/tox.ini b/tox.ini
index bc14a56a63952c6197ce225dcdc85f524d22f9d1..a7bafd63549972248ccb9c94bd0f9da068440203 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,11 @@
 [tox]
 envlist =
-    py{36,37,38}-django{30,22}
+    py{36,37,38,39}-django{31,30,22}
 
 [testenv]
 deps =
     -r{toxinidir}/tests/requirements.txt
+    django31: Django>=3.1,<3.2
     django30: Django>=3.0,<3.1
     django22: Django>=2.2,<3
 setenv =