diff --git a/invitations/admin.py b/invitations/admin.py index 65b8ea4..9a6b953 100644 --- a/invitations/admin.py +++ b/invitations/admin.py @@ -1,7 +1,9 @@ from django.contrib import admin -from .models import Invitation from .forms import InvitationAdminAddForm, InvitationAdminChangeForm +from .utils import get_invitation_model + +Invitation = get_invitation_model() class InvitationAdmin(admin.ModelAdmin): @@ -16,4 +18,5 @@ def get_form(self, request, obj=None, **kwargs): kwargs['form'].request = request return super(InvitationAdmin, self).get_form(request, obj, **kwargs) + admin.site.register(Invitation, InvitationAdmin) diff --git a/invitations/app_settings.py b/invitations/app_settings.py index 42097b5..59f5d02 100644 --- a/invitations/app_settings.py +++ b/invitations/app_settings.py @@ -73,4 +73,12 @@ def EMAIL_SUBJECT_PREFIX(self): """ return self._setting("EMAIL_SUBJECT_PREFIX", None) + @property + def INVITATION_MODEL(self): + """ + Subject-line prefix to use for Invitation model setup + """ + return self._setting("INVITATION_MODEL", "invitations.Invitation") + + app_settings = AppSettings('INVITATIONS_') diff --git a/invitations/base_invitation.py b/invitations/base_invitation.py new file mode 100644 index 0000000..6e1a284 --- /dev/null +++ b/invitations/base_invitation.py @@ -0,0 +1,41 @@ +from django.conf import settings +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ + +from .managers import BaseInvitationManager + + +@python_2_unicode_compatible +class AbstractBaseInvitation(models.Model): + accepted = models.BooleanField(verbose_name=_('accepted'), default=False) + key = models.CharField(verbose_name=_('key'), max_length=64, unique=True) + sent = models.DateTimeField(verbose_name=_('sent'), null=True) + inviter = models.ForeignKey( + settings.AUTH_USER_MODEL, null=True, blank=True) + + objects = BaseInvitationManager() + + class Meta: + abstract = True + + @classmethod + def create(cls, email, inviter=None, **kwargs): + raise NotImplementedError( + 'You should implement the create method class' + ) + + def key_expired(self): + raise NotImplementedError( + 'You should implement the key_expired method' + ) + + def send_invitation(self, request, **kwargs): + raise NotImplementedError( + 'You should implement the send_invitation method' + ) + + def __str__(self): + raise NotImplementedError( + 'You should implement the __str__ method' + ) diff --git a/invitations/forms.py b/invitations/forms.py index 2c25973..d65a5fc 100644 --- a/invitations/forms.py +++ b/invitations/forms.py @@ -2,9 +2,11 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import get_user_model -from .models import Invitation from .adapters import get_invitations_adapter from .exceptions import AlreadyInvited, AlreadyAccepted, UserRegisteredEmail +from .utils import get_invitation_model + +Invitation = get_invitation_model() class CleanEmailMixin(object): diff --git a/invitations/management/commands/clear_expired_invitations.py b/invitations/management/commands/clear_expired_invitations.py index 3f0bb1f..f03927a 100644 --- a/invitations/management/commands/clear_expired_invitations.py +++ b/invitations/management/commands/clear_expired_invitations.py @@ -1,5 +1,7 @@ from django.core.management.base import BaseCommand -from .. .models import Invitation +from .. .utils import get_invitation_model + +Invitation = get_invitation_model() class Command(BaseCommand): diff --git a/invitations/managers.py b/invitations/managers.py index a6ae902..fb8e778 100644 --- a/invitations/managers.py +++ b/invitations/managers.py @@ -7,7 +7,7 @@ from .app_settings import app_settings -class InvitationManager(models.Manager): +class BaseInvitationManager(models.Manager): def all_expired(self): return self.filter(self.expired_q()) diff --git a/invitations/models.py b/invitations/models.py index 64fe2c5..0d56c25 100644 --- a/invitations/models.py +++ b/invitations/models.py @@ -1,43 +1,36 @@ import datetime +from django.conf import settings +from django.contrib.sites.models import Site +from django.core.urlresolvers import reverse from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.template.context import RequestContext from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.encoding import python_2_unicode_compatible -from django.contrib.sites.models import Site -from django.core.urlresolvers import reverse -from django.conf import settings -from django.template.context import RequestContext +from django.utils.translation import ugettext_lazy as _ -from .managers import InvitationManager -from .app_settings import app_settings -from .adapters import get_invitations_adapter from . import signals +from .adapters import get_invitations_adapter +from .app_settings import app_settings +from .base_invitation import AbstractBaseInvitation @python_2_unicode_compatible -class Invitation(models.Model): - +class Invitation(AbstractBaseInvitation): email = models.EmailField(unique=True, verbose_name=_('e-mail address'), max_length=app_settings.EMAIL_MAX_LENGTH) - accepted = models.BooleanField(verbose_name=_('accepted'), default=False) created = models.DateTimeField(verbose_name=_('created'), default=timezone.now) - key = models.CharField(verbose_name=_('key'), max_length=64, unique=True) - sent = models.DateTimeField(verbose_name=_('sent'), null=True) - inviter = models.ForeignKey( - settings.AUTH_USER_MODEL, null=True, blank=True) - - objects = InvitationManager() @classmethod - def create(cls, email, inviter=None): + def create(cls, email, inviter=None, **kwargs): key = get_random_string(64).lower() instance = cls._default_manager.create( email=email, key=key, - inviter=inviter) + inviter=inviter, + **kwargs) return instance def key_expired(self): diff --git a/invitations/tests/allauth/test_allauth.py b/invitations/tests/allauth/test_allauth.py index aa49de6..e51a049 100644 --- a/invitations/tests/allauth/test_allauth.py +++ b/invitations/tests/allauth/test_allauth.py @@ -8,8 +8,11 @@ from nose_parameterized import parameterized from allauth.account.models import EmailAddress -from invitations.models import Invitation, InvitationsAdapter +from invitations.models import InvitationsAdapter from invitations.adapters import get_invitations_adapter +from invitations.utils import get_invitation_model + +Invitation = get_invitation_model() class AllAuthIntegrationTests(TestCase): diff --git a/invitations/tests/basic/tests.py b/invitations/tests/basic/tests.py index 54dff31..04a91af 100644 --- a/invitations/tests/basic/tests.py +++ b/invitations/tests/basic/tests.py @@ -17,10 +17,12 @@ from invitations.adapters import ( get_invitations_adapter, BaseInvitationsAdapter) -from invitations.models import Invitation from invitations.app_settings import app_settings from invitations.views import AcceptInvite, SendJSONInvite from invitations.forms import InviteForm +from invitations.utils import get_invitation_model + +Invitation = get_invitation_model() class InvitationModelTests(TestCase): @@ -538,6 +540,6 @@ def test_admin_form_change(self): self.assertEqual(response.status_code, 200) fields = list(response.context_data['adminform'].form.fields.keys()) - expected_fields = ['email', 'accepted', 'created', - 'key', 'sent', 'inviter'] + expected_fields = ['accepted', + 'key', 'sent', 'inviter', 'email', 'created'] self.assertEqual(fields, expected_fields) diff --git a/invitations/utils.py b/invitations/utils.py index 2510e3c..54fface 100644 --- a/invitations/utils.py +++ b/invitations/utils.py @@ -1,5 +1,9 @@ +from django.apps import apps as django_apps +from django.core.exceptions import ImproperlyConfigured from django.utils import six +from .app_settings import app_settings + try: import importlib except: @@ -11,3 +15,21 @@ def import_attribute(path): pkg, attr = path.rsplit('.', 1) ret = getattr(importlib.import_module(pkg), attr) return ret + + +def get_invitation_model(): + """ + Returns the Invitation model that is active in this project. + """ + path = app_settings.INVITATION_MODEL + try: + return django_apps.get_model(path) + except ValueError: + raise ImproperlyConfigured( + "path must be of the form 'app_label.model_name'" + ) + except LookupError: + raise ImproperlyConfigured( + "path refers to model '%s' that\ + has not been installed" % app_settings.INVITATION_MODEL + ) diff --git a/invitations/views.py b/invitations/views.py index e8a7a5f..9082c65 100644 --- a/invitations/views.py +++ b/invitations/views.py @@ -11,11 +11,13 @@ from braces.views import LoginRequiredMixin from .forms import InviteForm, CleanEmailMixin -from .models import Invitation from .exceptions import AlreadyInvited, AlreadyAccepted, UserRegisteredEmail from .app_settings import app_settings from .adapters import get_invitations_adapter from .signals import invite_accepted +from .utils import get_invitation_model + +Invitation = get_invitation_model() class SendInvite(LoginRequiredMixin, FormView): @@ -182,6 +184,7 @@ def accept_invite_after_signup(sender, request, user, **kwargs): request=request, signal_sender=Invitation) + if app_settings.ACCEPT_INVITE_AFTER_SIGNUP: signed_up_signal = get_invitations_adapter().get_user_signed_up_signal() signed_up_signal.connect(accept_invite_after_signup)