Revision 9d20fe23 snf-astakos-app/astakos/im/models.py
b/snf-astakos-app/astakos/im/models.py | ||
---|---|---|
76 | 76 |
register_services, register_resources, qh_add_quota, QuotaLimits, |
77 | 77 |
qh_query_serials, qh_ack_serials, |
78 | 78 |
QuotaValues, add_quota_values) |
79 |
from astakos.im import auth_providers |
|
79 |
from astakos.im import auth_providers as auth
|
|
80 | 80 |
|
81 | 81 |
import astakos.im.messages as astakos_messages |
82 | 82 |
from astakos.im.lock import with_lock |
... | ... | |
361 | 361 |
invitations = models.IntegerField( |
362 | 362 |
_('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0)) |
363 | 363 |
|
364 |
auth_token = models.CharField(_('Authentication Token'),
|
|
364 |
auth_token = models.CharField(_('Authentication Token'), |
|
365 | 365 |
max_length=32, |
366 |
null=True,
|
|
367 |
blank=True,
|
|
366 |
null=True, |
|
367 |
blank=True, |
|
368 | 368 |
help_text = _('Renew your authentication ' |
369 | 369 |
'token. Make sure to set the new ' |
370 | 370 |
'token in any client you may be ' |
371 | 371 |
'using, to preserve its ' |
372 | 372 |
'functionality.')) |
373 |
auth_token_created = models.DateTimeField(_('Token creation date'),
|
|
373 |
auth_token_created = models.DateTimeField(_('Token creation date'), |
|
374 | 374 |
null=True) |
375 | 375 |
auth_token_expires = models.DateTimeField( |
376 | 376 |
_('Token expiration date'), null=True) |
... | ... | |
596 | 596 |
self.level = level |
597 | 597 |
self.invitations = INVITATIONS_PER_LEVEL.get(level, 0) |
598 | 598 |
|
599 |
def can_login_with_auth_provider(self, provider): |
|
600 |
if not self.has_auth_provider(provider): |
|
601 |
return False |
|
602 |
else: |
|
603 |
return auth_providers.get_provider(provider).is_available_for_login() |
|
604 |
|
|
605 |
def can_add_auth_provider(self, provider, include_unverified=False, **kwargs): |
|
606 |
provider_settings = auth_providers.get_provider(provider) |
|
607 |
|
|
608 |
if not provider_settings.is_available_for_add(): |
|
609 |
return False |
|
610 |
|
|
611 |
if self.has_auth_provider(provider) and \ |
|
612 |
provider_settings.one_per_user: |
|
613 |
return False |
|
614 |
|
|
615 |
if 'provider_info' in kwargs: |
|
616 |
kwargs.pop('provider_info') |
|
617 |
|
|
618 |
if 'identifier' in kwargs: |
|
619 |
try: |
|
620 |
# provider with specified params already exist |
|
621 |
if not include_unverified: |
|
622 |
kwargs['user__email_verified'] = True |
|
623 |
existing_user = AstakosUser.objects.get_auth_provider_user(provider, |
|
624 |
**kwargs) |
|
625 |
except AstakosUser.DoesNotExist: |
|
626 |
return True |
|
627 |
else: |
|
628 |
return False |
|
629 |
|
|
630 |
return True |
|
599 |
def can_change_password(self): |
|
600 |
return self.has_auth_provider('local', auth_backend='astakos') |
|
631 | 601 |
|
632 |
def can_remove_auth_provider(self, module): |
|
633 |
provider = auth_providers.get_provider(module) |
|
634 |
existing = self.get_active_auth_providers() |
|
635 |
existing_for_provider = self.get_active_auth_providers(module=module) |
|
602 |
def can_change_email(self): |
|
603 |
if not self.has_auth_provider('local'): |
|
604 |
return True |
|
636 | 605 |
|
637 |
if len(existing) <= 1:
|
|
638 |
return False
|
|
606 |
local = self.get_auth_provider('local')._instance
|
|
607 |
return local.auth_backend == 'astakos'
|
|
639 | 608 |
|
640 |
if len(existing_for_provider) == 1 and provider.is_required(): |
|
641 |
return False |
|
609 |
# Auth providers related methods |
|
610 |
def get_auth_provider(self, module=None, identifier=None, **filters): |
|
611 |
if not module: |
|
612 |
return self.auth_providers.active()[0].settings |
|
642 | 613 |
|
643 |
return provider.is_available_for_remove() |
|
614 |
return self.auth_providers.active().get(module=module, |
|
615 |
identifier=identifier, |
|
616 |
**filters).settings |
|
644 | 617 |
|
645 |
def can_change_password(self): |
|
646 |
return self.has_auth_provider('local', auth_backend='astakos') |
|
618 |
def has_auth_provider(self, provider, **kwargs): |
|
619 |
return bool(self.auth_providers.active().filter(module=provider, |
|
620 |
**kwargs).count()) |
|
647 | 621 |
|
648 |
def can_change_email(self): |
|
649 |
non_astakos_local = self.get_auth_providers().filter(module='local') |
|
650 |
non_astakos_local = non_astakos_local.exclude(auth_backend='astakos') |
|
651 |
return non_astakos_local.count() == 0 |
|
622 |
def get_required_providers(self, **kwargs): |
|
623 |
return auth.REQUIRED_PROVIDERS.keys() |
|
652 | 624 |
|
653 |
def has_required_auth_providers(self): |
|
654 |
required = auth_providers.REQUIRED_PROVIDERS |
|
625 |
def missing_required_providers(self): |
|
626 |
required = self.get_required_providers() |
|
627 |
missing = [] |
|
655 | 628 |
for provider in required: |
656 | 629 |
if not self.has_auth_provider(provider): |
657 |
return False |
|
658 |
return True |
|
659 |
|
|
660 |
def has_auth_provider(self, provider, **kwargs): |
|
661 |
return bool(self.get_auth_providers().filter(module=provider, |
|
662 |
**kwargs).count()) |
|
663 |
|
|
664 |
def add_auth_provider(self, provider, **kwargs): |
|
665 |
info_data = '' |
|
666 |
if 'provider_info' in kwargs: |
|
667 |
info_data = kwargs.pop('provider_info') |
|
668 |
if isinstance(info_data, dict): |
|
669 |
info_data = json.dumps(info_data) |
|
670 |
|
|
671 |
if self.can_add_auth_provider(provider, **kwargs): |
|
672 |
if 'identifier' in kwargs: |
|
673 |
# clean up third party pending for activation users of the same |
|
674 |
# identifier |
|
675 |
AstakosUserAuthProvider.objects.remove_unverified_providers(provider, |
|
676 |
**kwargs) |
|
677 |
self.auth_providers.create(module=provider, active=True, |
|
678 |
info_data=info_data, |
|
679 |
**kwargs) |
|
680 |
else: |
|
681 |
raise Exception('Cannot add provider') |
|
630 |
missing.append(auth.get_provider(provider, self)) |
|
631 |
return missing |
|
682 | 632 |
|
683 |
def add_pending_auth_provider(self, pending):
|
|
633 |
def get_available_auth_providers(self, **filters):
|
|
684 | 634 |
""" |
685 |
Convert PendingThirdPartyUser object to AstakosUserAuthProvider entry for |
|
686 |
the current user. |
|
635 |
Returns a list of providers available for add by the user. |
|
687 | 636 |
""" |
688 |
if not isinstance(pending, PendingThirdPartyUser): |
|
689 |
pending = PendingThirdPartyUser.objects.get(token=pending) |
|
637 |
modules = astakos_settings.IM_MODULES |
|
638 |
providers = [] |
|
639 |
for p in modules: |
|
640 |
providers.append(auth.get_provider(p, self)) |
|
641 |
available = [] |
|
642 |
|
|
643 |
for p in providers: |
|
644 |
if p.get_add_policy: |
|
645 |
available.append(p) |
|
646 |
return available |
|
647 |
|
|
648 |
def get_disabled_auth_providers(self, **filters): |
|
649 |
providers = self.get_auth_providers(**filters) |
|
650 |
disabled = [] |
|
651 |
for p in providers: |
|
652 |
if not p.get_login_policy: |
|
653 |
disabled.append(p) |
|
654 |
return disabled |
|
655 |
|
|
656 |
def get_enabled_auth_providers(self, **filters): |
|
657 |
providers = self.get_auth_providers(**filters) |
|
658 |
enabled = [] |
|
659 |
for p in providers: |
|
660 |
if p.get_login_policy: |
|
661 |
enabled.append(p) |
|
662 |
return enabled |
|
663 |
|
|
664 |
def get_auth_providers(self, **filters): |
|
665 |
providers = [] |
|
666 |
for provider in self.auth_providers.active(**filters): |
|
667 |
if provider.settings.module_enabled: |
|
668 |
providers.append(provider.settings) |
|
690 | 669 |
|
691 |
provider = self.add_auth_provider(pending.provider, |
|
692 |
identifier=pending.third_party_identifier, |
|
693 |
affiliation=pending.affiliation, |
|
694 |
provider_info=pending.info) |
|
670 |
modules = astakos_settings.IM_MODULES |
|
671 |
|
|
672 |
def key(p): |
|
673 |
if not p.module in modules: |
|
674 |
return 100 |
|
675 |
return modules.index(p.module) |
|
695 | 676 |
|
696 |
if email_re.match(pending.email or '') and pending.email != self.email:
|
|
697 |
self.additionalmail_set.get_or_create(email=pending.email)
|
|
677 |
providers = sorted(providers, key=key)
|
|
678 |
return providers
|
|
698 | 679 |
|
699 |
pending.delete() |
|
700 |
return provider |
|
680 |
# URL methods |
|
681 |
@property |
|
682 |
def auth_providers_display(self): |
|
683 |
return ",".join(["%s:%s" % (p.module, p.get_username_msg) for p in |
|
684 |
self.get_enabled_auth_providers()]) |
|
701 | 685 |
|
702 |
def remove_auth_provider(self, provider, **kwargs): |
|
703 |
self.get_auth_providers().get(module=provider, **kwargs).delete() |
|
686 |
def add_auth_provider(self, module='local', identifier=None, **params): |
|
687 |
provider = auth.get_provider(module, self, identifier, **params) |
|
688 |
provider.add_to_user() |
|
704 | 689 |
|
705 |
# user urls |
|
706 | 690 |
def get_resend_activation_url(self): |
707 | 691 |
return reverse('send_activation', kwargs={'user_id': self.pk}) |
708 | 692 |
|
709 |
def get_provider_remove_url(self, module, **kwargs): |
|
710 |
return reverse('remove_auth_provider', kwargs={ |
|
711 |
'pk': self.get_auth_providers().get(module=module, **kwargs).pk}) |
|
712 |
|
|
713 | 693 |
def get_activation_url(self, nxt=False): |
714 | 694 |
url = "%s?auth=%s" % (reverse('astakos.im.views.activate'), |
715 | 695 |
quote(self.auth_token)) |
... | ... | |
722 | 702 |
kwargs={'uidb36':int_to_base36(self.id), |
723 | 703 |
'token':token_generator.make_token(self)}) |
724 | 704 |
|
725 |
def get_primary_auth_provider(self): |
|
726 |
return self.get_auth_providers().filter()[0] |
|
727 |
|
|
728 |
def get_auth_providers(self): |
|
729 |
return self.auth_providers |
|
730 |
|
|
731 |
def get_available_auth_providers(self): |
|
732 |
""" |
|
733 |
Returns a list of providers available for user to connect to. |
|
734 |
""" |
|
735 |
providers = [] |
|
736 |
for module, provider_settings in auth_providers.PROVIDERS.iteritems(): |
|
737 |
if self.can_add_auth_provider(module): |
|
738 |
providers.append(provider_settings(self)) |
|
739 |
|
|
740 |
modules = astakos_settings.IM_MODULES |
|
741 |
def key(p): |
|
742 |
if not p.module in modules: |
|
743 |
return 100 |
|
744 |
return modules.index(p.module) |
|
745 |
providers = sorted(providers, key=key) |
|
746 |
return providers |
|
747 |
|
|
748 |
def get_active_auth_providers(self, **filters): |
|
749 |
providers = [] |
|
750 |
for provider in self.get_auth_providers().active(**filters): |
|
751 |
if auth_providers.get_provider(provider.module).is_available_for_login(): |
|
752 |
providers.append(provider) |
|
753 |
|
|
754 |
modules = astakos_settings.IM_MODULES |
|
755 |
def key(p): |
|
756 |
if not p.module in modules: |
|
757 |
return 100 |
|
758 |
return modules.index(p.module) |
|
759 |
providers = sorted(providers, key=key) |
|
760 |
return providers |
|
761 |
|
|
762 |
@property |
|
763 |
def auth_providers_display(self): |
|
764 |
return ",".join(map(lambda x:unicode(x), self.get_auth_providers().active())) |
|
705 |
def get_inactive_message(self, provider_module, identifier=None): |
|
706 |
provider = self.get_auth_provider(provider_module, identifier) |
|
765 | 707 |
|
766 |
def get_inactive_message(self): |
|
767 | 708 |
msg_extra = '' |
768 | 709 |
message = '' |
710 |
|
|
711 |
msg_inactive = provider.get_account_inactive_msg |
|
712 |
msg_pending = provider.get_pending_activation_msg |
|
713 |
msg_pending_help = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP) |
|
714 |
#msg_resend_prompt = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION) |
|
715 |
msg_pending_mod = provider.get_pending_moderation_msg |
|
716 |
msg_resend = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION) |
|
717 |
|
|
769 | 718 |
if self.activation_sent: |
770 | 719 |
if self.email_verified: |
771 |
message = _(astakos_messages.ACCOUNT_INACTIVE)
|
|
720 |
message = msg_inactive
|
|
772 | 721 |
else: |
773 |
message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION) |
|
774 |
if astakos_settings.MODERATION_ENABLED: |
|
775 |
msg_extra = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP) |
|
776 |
else: |
|
777 |
url = self.get_resend_activation_url() |
|
778 |
msg_extra = mark_safe(_(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP) + \ |
|
779 |
u' ' + \ |
|
780 |
_('<a href="%s">%s?</a>') % (url, |
|
781 |
_(astakos_messages.ACCOUNT_RESEND_ACTIVATION_PROMPT))) |
|
722 |
message = msg_pending |
|
723 |
url = self.get_resend_activation_url() |
|
724 |
msg_extra = msg_pending_help + \ |
|
725 |
u' ' + \ |
|
726 |
'<a href="%s">%s?</a>' % (url, msg_resend) |
|
782 | 727 |
else: |
783 | 728 |
if astakos_settings.MODERATION_ENABLED: |
784 |
message = _(astakos_messages.ACCOUNT_PENDING_MODERATION)
|
|
729 |
message = msg_pending_mod
|
|
785 | 730 |
else: |
786 |
message = astakos_messages.ACCOUNT_PENDING_ACTIVATION
|
|
731 |
message = msg_pending
|
|
787 | 732 |
url = self.get_resend_activation_url() |
788 |
msg_extra = mark_safe(_('<a href="%s">%s?</a>') % (url,
|
|
789 |
_(astakos_messages.ACCOUNT_RESEND_ACTIVATION_PROMPT)))
|
|
733 |
msg_extra = '<a href="%s">%s?</a>' % (url, \
|
|
734 |
msg_resend)
|
|
790 | 735 |
|
791 | 736 |
return mark_safe(message + u' '+ msg_extra) |
792 | 737 |
|
... | ... | |
903 | 848 |
|
904 | 849 |
def remove_unverified_providers(self, provider, **filters): |
905 | 850 |
try: |
906 |
existing = self.filter(module=provider, user__email_verified=False, **filters) |
|
851 |
existing = self.filter(module=provider, user__email_verified=False, |
|
852 |
**filters) |
|
907 | 853 |
for p in existing: |
908 | 854 |
p.user.delete() |
909 | 855 |
except: |
910 | 856 |
pass |
911 | 857 |
|
858 |
def unverified(self, provider, **filters): |
|
859 |
try: |
|
860 |
return self.get(module=provider, user__email_verified=False, |
|
861 |
**filters).settings |
|
862 |
except AstakosUserAuthProvider.DoesNotExist: |
|
863 |
return None |
|
864 |
|
|
865 |
def verified(self, provider, **filters): |
|
866 |
try: |
|
867 |
return self.get(module=provider, user__email_verified=True, |
|
868 |
**filters).settings |
|
869 |
except AstakosUserAuthProvider.DoesNotExist: |
|
870 |
return None |
|
871 |
|
|
872 |
|
|
873 |
class AuthProviderPolicyProfileManager(models.Manager): |
|
874 |
|
|
875 |
def active(self): |
|
876 |
return self.filter(active=True) |
|
877 |
|
|
878 |
def for_user(self, user, provider): |
|
879 |
policies = {} |
|
880 |
exclusive_q1 = Q(provider=provider) & Q(is_exclusive=False) |
|
881 |
exclusive_q2 = ~Q(provider=provider) & Q(is_exclusive=True) |
|
882 |
exclusive_q = exclusive_q1 | exclusive_q2 |
|
883 |
|
|
884 |
for profile in user.authpolicy_profiles.active().filter(exclusive_q): |
|
885 |
policies.update(profile.policies) |
|
886 |
|
|
887 |
user_groups = user.groups.all().values('pk') |
|
888 |
for profile in self.active().filter(groups__in=user_groups).filter( |
|
889 |
exclusive_q): |
|
890 |
policies.update(profile.policies) |
|
891 |
return policies |
|
892 |
|
|
893 |
def add_policy(self, name, provider, group_or_user, exclusive=False, |
|
894 |
**policies): |
|
895 |
is_group = isinstance(group_or_user, Group) |
|
896 |
profile, created = self.get_or_create(name=name, provider=provider, |
|
897 |
is_exclusive=exclusive) |
|
898 |
profile.is_exclusive = exclusive |
|
899 |
profile.save() |
|
900 |
if is_group: |
|
901 |
profile.groups.add(group_or_user) |
|
902 |
else: |
|
903 |
profile.users.add(group_or_user) |
|
904 |
profile.set_policies(policies) |
|
905 |
profile.save() |
|
906 |
return profile |
|
907 |
|
|
908 |
|
|
909 |
class AuthProviderPolicyProfile(models.Model): |
|
910 |
name = models.CharField(_('Name'), max_length=255, blank=False, |
|
911 |
null=False, db_index=True) |
|
912 |
provider = models.CharField(_('Provider'), max_length=255, blank=False, |
|
913 |
null=False) |
|
914 |
|
|
915 |
# apply policies to all providers excluding the one set in provider field |
|
916 |
is_exclusive = models.BooleanField(default=False) |
|
917 |
|
|
918 |
policy_add = models.NullBooleanField(null=True, default=None) |
|
919 |
policy_remove = models.NullBooleanField(null=True, default=None) |
|
920 |
policy_create = models.NullBooleanField(null=True, default=None) |
|
921 |
policy_login = models.NullBooleanField(null=True, default=None) |
|
922 |
policy_limit = models.IntegerField(null=True, default=None) |
|
923 |
policy_required = models.NullBooleanField(null=True, default=None) |
|
924 |
policy_automoderate = models.NullBooleanField(null=True, default=None) |
|
925 |
policy_switch = models.NullBooleanField(null=True, default=None) |
|
926 |
|
|
927 |
POLICY_FIELDS = ('add', 'remove', 'create', 'login', 'limit', 'required', |
|
928 |
'automoderate') |
|
929 |
|
|
930 |
priority = models.IntegerField(null=False, default=1) |
|
931 |
groups = models.ManyToManyField(Group, related_name='authpolicy_profiles') |
|
932 |
users = models.ManyToManyField(AstakosUser, |
|
933 |
related_name='authpolicy_profiles') |
|
934 |
active = models.BooleanField(default=True) |
|
935 |
|
|
936 |
objects = AuthProviderPolicyProfileManager() |
|
937 |
|
|
938 |
class Meta: |
|
939 |
ordering = ['priority'] |
|
940 |
|
|
941 |
@property |
|
942 |
def policies(self): |
|
943 |
policies = {} |
|
944 |
for pkey in self.POLICY_FIELDS: |
|
945 |
value = getattr(self, 'policy_%s' % pkey, None) |
|
946 |
if value is None: |
|
947 |
continue |
|
948 |
policies[pkey] = value |
|
949 |
return policies |
|
950 |
|
|
951 |
def set_policies(self, policies_dict): |
|
952 |
for key, value in policies_dict.iteritems(): |
|
953 |
if key in self.POLICY_FIELDS: |
|
954 |
setattr(self, 'policy_%s' % key, value) |
|
955 |
return self.policies |
|
912 | 956 |
|
913 | 957 |
|
914 | 958 |
class AstakosUserAuthProvider(models.Model): |
... | ... | |
947 | 991 |
for key,value in self.info.iteritems(): |
948 | 992 |
setattr(self, 'info_%s' % key, value) |
949 | 993 |
|
950 |
|
|
951 | 994 |
@property |
952 | 995 |
def settings(self): |
953 |
return auth_providers.get_provider(self.module)
|
|
996 |
extra_data = {}
|
|
954 | 997 |
|
955 |
@property |
|
956 |
def details_display(self): |
|
957 |
try: |
|
958 |
params = self.user.__dict__ |
|
959 |
params.update(self.__dict__) |
|
960 |
return self.settings.get_details_tpl_display % params |
|
961 |
except: |
|
962 |
return '' |
|
998 |
info_data = {} |
|
999 |
if self.info_data: |
|
1000 |
info_data = json.loads(self.info_data) |
|
963 | 1001 |
|
964 |
@property |
|
965 |
def title_display(self): |
|
966 |
title_tpl = self.settings.get_title_display |
|
967 |
try: |
|
968 |
if self.settings.get_user_title_display: |
|
969 |
title_tpl = self.settings.get_user_title_display |
|
970 |
except Exception, e: |
|
971 |
pass |
|
972 |
try: |
|
973 |
return title_tpl % self.__dict__ |
|
974 |
except: |
|
975 |
return self.settings.get_title_display % self.__dict__ |
|
1002 |
extra_data['info'] = info_data |
|
976 | 1003 |
|
977 |
def can_remove(self):
|
|
978 |
return self.user.can_remove_auth_provider(self.module)
|
|
1004 |
for key in ['active', 'auth_backend', 'created', 'pk', 'affiliation']:
|
|
1005 |
extra_data[key] = getattr(self, key)
|
|
979 | 1006 |
|
980 |
def delete(self, *args, **kwargs): |
|
981 |
ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs) |
|
982 |
if self.module == 'local': |
|
983 |
self.user.set_unusable_password() |
|
984 |
self.user.save() |
|
985 |
return ret |
|
1007 |
extra_data['instance'] = self |
|
1008 |
return auth.get_provider(self.module, self.user, |
|
1009 |
self.identifier, **extra_data) |
|
986 | 1010 |
|
987 | 1011 |
def __repr__(self): |
988 | 1012 |
return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier) |
... | ... | |
1182 | 1206 |
pass |
1183 | 1207 |
return None |
1184 | 1208 |
|
1209 |
|
|
1185 | 1210 |
class PendingThirdPartyUser(models.Model): |
1186 | 1211 |
""" |
1187 | 1212 |
Model for registring successful third party user authentications |
... | ... | |
1195 | 1220 |
null=True) |
1196 | 1221 |
affiliation = models.CharField('Affiliation', max_length=255, blank=True, |
1197 | 1222 |
null=True) |
1198 |
username = models.CharField(_('username'), max_length=30, unique=True,
|
|
1223 |
username = models.CharField(_('username'), max_length=30, unique=True, |
|
1199 | 1224 |
help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters")) |
1200 | 1225 |
token = models.CharField(_('Token'), max_length=255, null=True, blank=True) |
1201 | 1226 |
created = models.DateTimeField(auto_now_add=True, null=True, blank=True) |
... | ... | |
1248 | 1273 |
return AstakosUser.objects.filter(auth_providers__module=self.provider, |
1249 | 1274 |
auth_providers__identifier=self.third_party_identifier) |
1250 | 1275 |
|
1276 |
def get_provider(self, user): |
|
1277 |
params = { |
|
1278 |
'info_data': self.info, |
|
1279 |
'affiliation': self.affiliation |
|
1280 |
} |
|
1281 |
return auth.get_provider(self.provider, user, |
|
1282 |
self.third_party_identifier, **params) |
|
1283 |
|
|
1251 | 1284 |
class SessionCatalog(models.Model): |
1252 | 1285 |
session_key = models.CharField(_('session key'), max_length=40) |
1253 | 1286 |
user = models.ForeignKey(AstakosUser, related_name='sessions', null=True) |
Also available in: Unified diff