New longterm_install options #24
5 changed files with 171 additions and 12 deletions
22
README.rst
22
README.rst
|
@ -216,11 +216,23 @@ Customize
|
||||||
behaviour, and for instance attribute a default entrance year.
|
behaviour, and for instance attribute a default entrance year.
|
||||||
|
|
||||||
Initial migration
|
Initial migration
|
||||||
If you used allauth without LongTermClipperAccountAdapter, or another CAS
|
Description
|
||||||
interface to log in, you need to update the Users to the new username policy,
|
If you used allauth without LongTermClipperAccountAdapter, or another CAS
|
||||||
and (in the second case) to create the SocialAccount instances to link CAS and
|
interface to log in, you need to update the Users to the new username policy,
|
||||||
Users. This can be done easily with ``$ python manage.py install_longterm``.
|
and (in the second case) to create the SocialAccount instances to link CAS and
|
||||||
|
Users. This can be done easily with ``$ python manage.py install_longterm``.
|
||||||
|
|
||||||
|
Install_longterm options
|
||||||
|
- ``--use-socialaccounts``: Use the existing SocialAccounts rather than all the Users. Useful if you are already using Allauth and don't want ``install_longterm`` to mess with the non-clipper authentications.
|
||||||
|
- ``--keep-usernames``: Do not apply the username template (e.g. ``clipper@promo``) to the existing accounts, only populate the SocialAccounts with LDAP informations. Useful if you don't want to change the usernames of previous users, but do want such a template for future accounts.
|
||||||
|
- ``--clipper-field <field_name>``: Use a special field rather than the username to get the clipper username (for LDAP lookup and SocialAccount creation/update). This parameter is compatible with ForeignKeys (e.g. ``profile.clipper``). Note: ``--use-socialaccounts`` will ignore the ``--clipper-field`` parameter.
|
||||||
|
- ``--fake``: Do not modify the database. Use it to test there is no conflict, and be sure the changes are the ones expected. This command does not check for uniqueness errors, so there it may succeed and the actual command fail eventually.
|
||||||
|
|
||||||
|
Typical use cases
|
||||||
|
- *Django-cas-ng -> Longterm*: Use ``install_longterm`` without parameters, or maybe ``--keep-usernames``. If you had a custom username handling, ``--clipper_field`` may be useful.
|
||||||
|
- *Allauth -> Longterm*: Use ``install_longterm`` with ``--use-socialaccounts``, and maybe ``--keep-usernames``.
|
||||||
|
|
||||||
|
|
||||||
*********
|
*********
|
||||||
Demo Site
|
Demo Site
|
||||||
*********
|
*********
|
||||||
|
|
|
@ -147,14 +147,20 @@ def deprecate_clippers():
|
||||||
clippers.update(provider='clipper_inactive')
|
clippers.update(provider='clipper_inactive')
|
||||||
|
|
||||||
|
|
||||||
def install_longterm_adapter(fake=False):
|
def install_longterm_adapter(fake=False, accounts=None, keep_usernames=False):
|
||||||
"""
|
"""
|
||||||
Manages the transition from an older django_cas or an allauth_ens
|
Manages the transition from an older django_cas or an allauth_ens
|
||||||
installation without LongTermClipperAccountAdapter
|
installation without LongTermClipperAccountAdapter
|
||||||
|
|
||||||
|
accounts is an optional dictionary containing the association between
|
||||||
|
clipper usernames and django's User accounts. If not provided, the
|
||||||
|
function will assumer Users' usernames are their clipper uid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
accounts = {u.username: u for u in User.objects.all()
|
if accounts is None:
|
||||||
if u.username.isalnum()}
|
accounts = {u.username: u for u in User.objects.all()
|
||||||
|
if u.username.isalnum()}
|
||||||
|
|
||||||
ldap_connection = init_ldap()
|
ldap_connection = init_ldap()
|
||||||
ltc_adapter = get_adapter()
|
ltc_adapter = get_adapter()
|
||||||
|
|
||||||
|
@ -178,7 +184,10 @@ def install_longterm_adapter(fake=False):
|
||||||
user = accounts.get(clipper_uid, None)
|
user = accounts.get(clipper_uid, None)
|
||||||
if user is None:
|
if user is None:
|
||||||
continue
|
continue
|
||||||
user.username = ltc_adapter.get_username(clipper_uid, data)
|
|
||||||
|
if not keep_usernames:
|
||||||
|
user.username = ltc_adapter.get_username(clipper_uid, data)
|
||||||
|
|
||||||
if fake:
|
if fake:
|
||||||
cases.append(clipper_uid)
|
cases.append(clipper_uid)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
|
||||||
from allauth_ens.adapter import install_longterm_adapter
|
from allauth_ens.adapter import install_longterm_adapter
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,10 +20,52 @@ class Command(BaseCommand):
|
||||||
help=('Does not save the models created/updated,'
|
help=('Does not save the models created/updated,'
|
||||||
'only shows the list'),
|
'only shows the list'),
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--use-socialaccounts',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=('Use the existing SocialAccounts rather than all the Users'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--keep-usernames',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=('Do not apply the username template (e.g. clipper@promo) to'
|
||||||
|
'the existing account, only populate the SocialAccounts with'
|
||||||
|
'ldap informations'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--clipper-field',
|
||||||
|
default=None,
|
||||||
|
type=str
|
||||||
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
logs = install_longterm_adapter(options.get("fake", False))
|
fake = options.get("fake", False)
|
||||||
|
keep_usernames = options.get("keep_usernames", False)
|
||||||
|
|
||||||
|
if options.get('use_socialaccounts', False):
|
||||||
|
accounts = {account.uid: account.user for account in
|
||||||
|
(SocialAccount.objects.filter(provider="clipper")
|
||||||
|
.prefetch_related("user"))}
|
||||||
|
elif options.get('clipper_field', None):
|
||||||
|
fields = options['clipper_field'].split('.')
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
def get_subattr(obj, fields):
|
||||||
|
# Allows to follow OneToOne relationships
|
||||||
|
if len(fields) == 1:
|
||||||
|
return getattr(obj, fields[0])
|
||||||
|
return get_subattr(getattr(obj, fields[0]), fields[1:])
|
||||||
|
|
||||||
|
accounts = {get_subattr(account, fields): account for account in
|
||||||
|
User.objects.all()}
|
||||||
|
else:
|
||||||
|
accounts = None
|
||||||
|
|
||||||
|
logs = install_longterm_adapter(fake, accounts, keep_usernames)
|
||||||
|
|
||||||
self.stdout.write("Social accounts created : %d"
|
self.stdout.write("Social accounts created : %d"
|
||||||
% len(logs["created"]))
|
% len(logs["created"]))
|
||||||
self.stdout.write(" ".join(("%s -> %s" % s) for s in logs["created"]))
|
self.stdout.write(" ".join(("%s -> %s" % s) for s in logs["created"]))
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.contrib.auth import HASH_SESSION_KEY, get_user_model
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
from django.test.utils import captured_stdout
|
||||||
|
|
||||||
from allauth.socialaccount.models import SocialAccount
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ from mock import patch
|
||||||
from allauth_ens.utils import get_ldap_infos
|
from allauth_ens.utils import get_ldap_infos
|
||||||
|
|
||||||
from .adapter import deprecate_clippers, install_longterm_adapter
|
from .adapter import deprecate_clippers, install_longterm_adapter
|
||||||
|
from .management.commands.install_longterm import Command as InstallLongterm
|
||||||
|
|
||||||
_mock_ldap = MockLDAP()
|
_mock_ldap = MockLDAP()
|
||||||
ldap_patcher = patch('allauth_ens.utils.ldap.initialize',
|
ldap_patcher = patch('allauth_ens.utils.ldap.initialize',
|
||||||
|
@ -331,6 +333,98 @@ class LongTermClipperTests(CASTestCase):
|
||||||
self.assertEqual(user1.username, "test@12")
|
self.assertEqual(user1.username, "test@12")
|
||||||
self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12')
|
self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12')
|
||||||
|
|
||||||
|
def test_longterm_installer_from_allauth_command_using_username(self):
|
||||||
|
self._setup_ldap(12)
|
||||||
|
with self.settings(
|
||||||
|
SOCIALACCOUNT_ADAPTER='allauth.socialaccount.'
|
||||||
|
'adapter.DefaultSocialAccountAdapter'):
|
||||||
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
|
username='test')
|
||||||
|
user0 = r.context["user"]
|
||||||
|
nsa0 = SocialAccount.objects.count()
|
||||||
|
self.assertEqual(user0.username, "test")
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
command = InstallLongterm()
|
||||||
|
command.handle(clipper_field='username')
|
||||||
|
|
||||||
|
output = stdout.getvalue()
|
||||||
|
self.assertIn('test -> test@12', output)
|
||||||
|
|
||||||
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
|
username='test')
|
||||||
|
user1 = r.context["user"]
|
||||||
|
nsa1 = SocialAccount.objects.count()
|
||||||
|
conn = user1.socialaccount_set.get(provider='clipper')
|
||||||
|
self.assertEqual(user1.id, user0.id)
|
||||||
|
self.assertEqual(nsa1, nsa0)
|
||||||
|
self.assertEqual(user1.username, "test@12")
|
||||||
|
self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12')
|
||||||
|
|
||||||
|
def test_longterm_installer_from_allauth_command_keeping_username(self):
|
||||||
|
self._setup_ldap(12)
|
||||||
|
with self.settings(
|
||||||
|
SOCIALACCOUNT_ADAPTER='allauth.socialaccount.'
|
||||||
|
'adapter.DefaultSocialAccountAdapter'):
|
||||||
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
|
username='test')
|
||||||
|
user0 = r.context["user"]
|
||||||
|
nsa0 = SocialAccount.objects.count()
|
||||||
|
self.assertEqual(user0.username, "test")
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
command = InstallLongterm()
|
||||||
|
command.handle(keep_usernames=True)
|
||||||
|
|
||||||
|
output = stdout.getvalue()
|
||||||
|
self.assertIn('test -> test', output)
|
||||||
|
|
||||||
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
|
username='test')
|
||||||
|
user1 = r.context["user"]
|
||||||
|
nsa1 = SocialAccount.objects.count()
|
||||||
|
conn = user1.socialaccount_set.get(provider='clipper')
|
||||||
|
self.assertEqual(user1.id, user0.id)
|
||||||
|
self.assertEqual(nsa1, nsa0)
|
||||||
|
self.assertEqual(user1.username, "test")
|
||||||
|
self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12')
|
||||||
|
|
||||||
|
def test_longterm_installer_from_allauth_command_socialaccounts(self):
|
||||||
|
self._setup_ldap(12)
|
||||||
|
with self.settings(
|
||||||
|
SOCIALACCOUNT_ADAPTER='allauth.socialaccount.'
|
||||||
|
'adapter.DefaultSocialAccountAdapter'):
|
||||||
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
|
username='test')
|
||||||
|
user0 = r.context["user"]
|
||||||
|
self.assertEqual(user0.username, "test")
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
user1 = User.objects.create_user('bidule', 'bidule@clipper.ens.fr',
|
||||||
|
'bidule')
|
||||||
|
nsa0 = SocialAccount.objects.count()
|
||||||
|
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
command = InstallLongterm()
|
||||||
|
command.handle(use_socialaccounts=True)
|
||||||
|
|
||||||
|
output = stdout.getvalue()
|
||||||
|
self.assertIn('test -> test@12', output)
|
||||||
|
self.assertNotIn('bidule ->', output)
|
||||||
|
|
||||||
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
|
username='test')
|
||||||
|
user1 = r.context["user"]
|
||||||
|
nsa1 = SocialAccount.objects.count()
|
||||||
|
conn = user1.socialaccount_set.get(provider='clipper')
|
||||||
|
self.assertEqual(user1.id, user0.id)
|
||||||
|
self.assertEqual(nsa1, nsa0)
|
||||||
|
self.assertEqual(nsa1, nsa0)
|
||||||
|
self.assertEqual(user1.username, "test@12")
|
||||||
|
self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12')
|
||||||
|
|
||||||
def test_longterm_installer_from_djangocas(self):
|
def test_longterm_installer_from_djangocas(self):
|
||||||
with self.settings(
|
with self.settings(
|
||||||
SOCIALACCOUNT_ADAPTER='allauth.socialaccount.'
|
SOCIALACCOUNT_ADAPTER='allauth.socialaccount.'
|
||||||
|
|
|
@ -116,8 +116,7 @@ def remove_email(user, email):
|
||||||
|
|
||||||
# Prefer a verified mail.
|
# Prefer a verified mail.
|
||||||
new_primary = (
|
new_primary = (
|
||||||
others.filter(verified=True).last() or
|
others.filter(verified=True).last() or others.last()
|
||||||
others.last()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if new_primary:
|
if new_primary:
|
||||||
|
|
Loading…
Reference in a new issue