diff --git a/demo_project/settings.py b/demo_project/settings.py index cb9c00e7..4d3f3112 100644 --- a/demo_project/settings.py +++ b/demo_project/settings.py @@ -96,12 +96,12 @@ 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.admin', - 'demo_project.profiles', 'easy_thumbnails', 'guardian', 'south', 'userena', 'userena.contrib.umessages', + 'demo_project.profiles', ) # Userena settings @@ -112,6 +112,7 @@ USERENA_DISABLE_PROFILE_LIST = True USERENA_MUGSHOT_SIZE = 140 +USERENA_ACTIVATION_REQUIRED = False # Test settings TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner' diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 00000000..3406ac5f --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,12 @@ +.. _faq: + +F.A.Q +===== + +I get a ``Permission matching query does not exist`` exception +-------------------------------------------------------------- + +Sometimes Django decides not to install the default permissions for a model +and thus the ``change_profile`` permission goes missing. To fix this, run the +``check_permissions`` in :ref:`commands`. This checks all permissions and adds +those that are missing. diff --git a/docs/index.rst b/docs/index.rst index cda4926d..a8997ab1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -52,6 +52,7 @@ Contents settings signals commands + faq api/index Contrib: uMessages diff --git a/docs/installation.rst b/docs/installation.rst index 4795ff77..ecefbba8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -174,4 +174,11 @@ The above should supply you with a fully functional account management app. for your project. You can look into the next chapter to fully customize userena to your likings. +Permission check +~~~~~~~~~~~~~~~~ + +Sometimes Django decides to skip installing the default permissions for a +model. To check if all permissions are there, run the ``check_permissions`` in +the management :ref:`commands`. + .. _Github: https://github.com/lukaszb/django-guardian diff --git a/docs/settings.rst b/docs/settings.rst index 3557cb41..9dc7cfdb 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -17,6 +17,12 @@ Default ``/accounts/%(username)s/'`` (string) A string which defines the URI where the user will be redirected to after signin. +USERENA_ACTIVATION_REQUIRED +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Default: ``True`` (integer) + +Boolean that defines if a activation is required when creating a new user. + USERENA_ACTIVATION_DAYS ~~~~~~~~~~~~~~~~~~~~~~~ Default: ``7`` (integer) diff --git a/userena/forms.py b/userena/forms.py index 318f8b07..8e28b46a 100644 --- a/userena/forms.py +++ b/userena/forms.py @@ -79,7 +79,11 @@ def save(self): self.cleaned_data['email'], self.cleaned_data['password1']) - new_user = UserenaSignup.objects.create_inactive_user(username, email, password) + new_user = UserenaSignup.objects.create_user(username, + email, + password, + not userena_settings.USERENA_ACTIVATION_REQUIRED, + userena_settings.USERENA_ACTIVATION_REQUIRED) return new_user class SignupFormOnlyEmail(SignupForm): diff --git a/userena/management/commands/check_permissions.py b/userena/management/commands/check_permissions.py index 86ffcddc..0379710d 100644 --- a/userena/management/commands/check_permissions.py +++ b/userena/management/commands/check_permissions.py @@ -1,4 +1,5 @@ -from django.core.management.base import NoArgsCommand +from django.core.management.base import NoArgsCommand, BaseCommand +from optparse import make_option from userena.models import UserenaSignup @@ -8,6 +9,36 @@ class Command(NoArgsCommand): This command checks that all permissions are correct. """ + option_list = BaseCommand.option_list + ( + make_option('--no-output', + action='store_false', + dest='output', + default=True, + help='Hide informational output.'), + make_option('--test', + action='store_true', + dest='test', + default=False, + help="Displays that it's testing management command. Don't use it yourself."), + ) + help = 'Check that user permissions are correct.' def handle_noargs(self, **options): - users = UserenaSignup.objects.check_permissions() + permissions, users, warnings = UserenaSignup.objects.check_permissions() + output = options.pop("output") + test = options.pop("test") + if test: + self.stdout.write(40 * ".") + self.stdout.write("\nChecking permission management command. Ignore output..\n\n") + if output: + for p in permissions: + self.stdout.write("Added permission: %s\n" % p) + + for u in users: + self.stdout.write("Changed permissions for user: %s\n" % u) + + for w in warnings: + self.stdout.write("WARNING: %s\n" %w) + + if test: + self.stdout.write("\nFinished testing permissions command.. continuing..\n") diff --git a/userena/managers.py b/userena/managers.py index fcef4775..99a840e8 100644 --- a/userena/managers.py +++ b/userena/managers.py @@ -2,6 +2,7 @@ from django.db.models import Q from django.contrib.auth.models import User, UserManager, Permission, AnonymousUser from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext as _ from userena import settings as userena_settings from userena.utils import generate_sha1, get_profile_model @@ -16,7 +17,8 @@ ASSIGNED_PERMISSIONS = { 'profile': (('view_profile', 'Can view profile'), - ('change_profile', 'Can change profile')), + ('change_profile', 'Can change profile'), + ('delete_profile', 'Can delete profile')), 'user': (('change_user', 'Can change user'), ('delete_user', 'Can delete user')) @@ -25,7 +27,8 @@ class UserenaManager(UserManager): """ Extra functionality for the Userena model. """ - def create_inactive_user(self, username, email, password, send_email=True): + def create_user(self, username, email, password, active=False, + send_email=True): """ A simple wrapper that creates a new :class:`User`. @@ -38,6 +41,10 @@ def create_inactive_user(self, username, email, password, send_email=True): :param password: String containing the password for the new user. + :param active: + Boolean that defines if the user requires activation by clicking + on a link in an e-mail. Defauts to ``True``. + :param send_email: Boolean that defines if the user should be send an email. You could set this to ``False`` when you want to create a user in your own @@ -49,7 +56,7 @@ def create_inactive_user(self, username, email, password, send_email=True): now = datetime.datetime.now() new_user = User.objects.create_user(username, email, password) - new_user.is_active = False + new_user.is_active = active new_user.save() userena_profile = self.create_userena_profile(new_user) @@ -66,7 +73,7 @@ def create_inactive_user(self, username, email, password, send_email=True): for perm in ASSIGNED_PERMISSIONS['profile']: assign(perm[0], new_user, new_profile) - # Give permissinos to view and change itself + # Give permissions to view and change itself for perm in ASSIGNED_PERMISSIONS['user']: assign(perm[0], new_user, new_user) @@ -196,6 +203,11 @@ def check_permissions(self): :return: A set of users whose permissions was wrong. """ + # Variable to supply some feedback + changed_permissions = [] + changed_users = [] + warnings = [] + # Check that all the permissions are available. for model, perms in ASSIGNED_PERMISSIONS.items(): if model == 'profile': @@ -207,20 +219,18 @@ def check_permissions(self): Permission.objects.get(codename=perm[0], content_type=model_content_type) except Permission.DoesNotExist: - print "Creating permission: %s" % perm[1] + changed_permissions.append(perm[1]) Permission.objects.create(name=perm[1], codename=perm[0], content_type=model_content_type) - else: print "Found permission: %s" % perm[1] - # Check permission for every user. - changed_users = set() for user in User.objects.all(): if not user.username == 'AnonymousUser': try: user_profile = user.get_profile() except get_profile_model().DoesNotExist: - print "WARNING: No profile found for %s" % user.username + warnings.append(_("No profile found for %(username)s") \ + % {'username': user.username}) else: all_permissions = get_perms(user, user.get_profile()) + get_perms(user, user) @@ -232,9 +242,9 @@ def check_permissions(self): for perm in perms: if perm[0] not in all_permissions: assign(perm[0], user, perm_object) - changed_users.add(user) + changed_users.append(user) - return changed_users + return (changed_permissions, changed_users, warnings) class UserenaBaseProfileManager(models.Manager): """ Manager for :class:`UserenaProfile` """ diff --git a/userena/models.py b/userena/models.py index 74ccc455..0f2e857d 100644 --- a/userena/models.py +++ b/userena/models.py @@ -21,8 +21,6 @@ PROFILE_PERMISSIONS = ( ('view_profile', 'Can view profile'), - ('change_profile', 'Can change profile'), - ('delete_profile', 'Can delete profile'), ) def upload_to_mugshot(instance, filename): diff --git a/userena/settings.py b/userena/settings.py index 61f9d1ae..c44df063 100644 --- a/userena/settings.py +++ b/userena/settings.py @@ -14,6 +14,10 @@ 'USERENA_SIGNIN_REDIRECT_URL', '/accounts/%(username)s/') +USERENA_ACTIVATION_REQUIRED = getattr(settings, + 'USERENA_ACTIVATION_REQUIRED', + True) + USERENA_ACTIVATION_DAYS = getattr(settings, 'USERENA_ACTIVATION_DAYS', 7) diff --git a/userena/templates/userena/signup_complete.html b/userena/templates/userena/signup_complete.html index 6c1ee2ff..6be2e5cb 100644 --- a/userena/templates/userena/signup_complete.html +++ b/userena/templates/userena/signup_complete.html @@ -7,6 +7,12 @@ {% block content %}

{% trans "Thank you for signing up with us!" %}

+ +{% if userena_activation_required %}

{% blocktrans %}You have been send an e-mail with an activation link to the supplied email.{% endblocktrans %}

-

{% blocktrans %}We will store your signup information for {{ userena_activation_days }} days on our server. {% endblocktrans %}

+

{% blocktrans %}We will store your signup information for {{ + userena_activation_days }} days on our server. {% endblocktrans %}

+{% else %} +

{% blocktrans %}You can now use the supplied credentials to signin.{% endblocktrans %}

+{% endif %} {% endblock %} diff --git a/userena/tests/commands.py b/userena/tests/commands.py index bb921829..d8abfae0 100644 --- a/userena/tests/commands.py +++ b/userena/tests/commands.py @@ -25,7 +25,7 @@ def test_clean_expired(self): """ # Create an account which is expired. - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) user.date_joined -= datetime.timedelta(days=userena_settings.USERENA_ACTIVATION_DAYS + 1) user.save() @@ -44,7 +44,7 @@ class CheckPermissionTests(TestCase): def test_check_permissions(self): # Create a new account. - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) user.save() # Remove all permissions @@ -64,7 +64,7 @@ def test_check_permissions(self): self.fail() # Check it again should do nothing - call_command('check_permissions') + call_command('check_permissions', test=True) def test_incomplete_permissions(self): # Delete the neccesary permissions @@ -92,7 +92,7 @@ def test_incomplete_permissions(self): else: self.fail("Found %s: " % perm) # Repair them - call_command('check_permissions') + call_command('check_permissions', test=True) # Check if they are they are back for model, perms in ASSIGNED_PERMISSIONS.items(): @@ -105,3 +105,16 @@ def test_incomplete_permissions(self): content_type=content_type) except Permission.DoesNotExist: self.fail() + + def test_no_profile(self): + """ Check for warning when there is no profile """ + # TODO: Dirty! Currently we check for the warning by getting a 100% + # test coverage, meaning that it dit output some warning. + user = UserenaSignup.objects.create_user(**self.user_info) + + # remove the profile of this user + get_profile_model().objects.get(user=user).delete() + + # run the command to check for the warning. + call_command('check_permissions', test=True) + diff --git a/userena/tests/managers.py b/userena/tests/managers.py index fc30319e..e78771c7 100644 --- a/userena/tests/managers.py +++ b/userena/tests/managers.py @@ -30,7 +30,7 @@ def test_create_inactive_user(self): """ # Check that the fields are set. - new_user = UserenaSignup.objects.create_inactive_user(**self.user_info) + new_user = UserenaSignup.objects.create_user(**self.user_info) self.assertEqual(new_user.username, self.user_info['username']) self.assertEqual(new_user.email, self.user_info['email']) self.failUnless(new_user.check_password(self.user_info['password'])) @@ -56,7 +56,7 @@ def test_activation_valid(self): the setting ``USERENA_ACTIVATED``. """ - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) active_user = UserenaSignup.objects.activate_user(user.username, user.userena_signup.activation_key) @@ -93,7 +93,7 @@ def test_activation_expired(self): ``UserenaSignup.objects.activation_user`` return ``False``. """ - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) # Set the date that the key is created a day further away than allowed user.date_joined -= datetime.timedelta(days=userena_settings.USERENA_ACTIVATION_DAYS + 1) @@ -153,7 +153,7 @@ def test_delete_expired_users(self): Test if expired users are deleted from the database. """ - expired_user = UserenaSignup.objects.create_inactive_user(**self.user_info) + expired_user = UserenaSignup.objects.create_user(**self.user_info) expired_user.date_joined -= datetime.timedelta(days=userena_settings.USERENA_ACTIVATION_DAYS + 1) expired_user.save() diff --git a/userena/tests/models.py b/userena/tests/models.py index a899c741..088106c5 100644 --- a/userena/tests/models.py +++ b/userena/tests/models.py @@ -61,7 +61,7 @@ def test_activation_expired_account(self): ``USERENA_ACTIVATION_DAYS``. """ - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) user.date_joined -= datetime.timedelta(days=userena_settings.USERENA_ACTIVATION_DAYS + 1) user.save() @@ -74,7 +74,7 @@ def test_activation_used_account(self): already used. """ - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) activated_user = UserenaSignup.objects.activate_user(user.username, user.userena_signup.activation_key) self.failUnless(activated_user.userena_signup.activation_key_expired()) @@ -85,7 +85,7 @@ def test_activation_unexpired_account(self): ``activation_key_created`` is within the defined timeframe.`` """ - user = UserenaSignup.objects.create_inactive_user(**self.user_info) + user = UserenaSignup.objects.create_user(**self.user_info) self.failIf(user.userena_signup.activation_key_expired()) def test_activation_email(self): @@ -94,7 +94,7 @@ def test_activation_email(self): by ``UserenaSignup.send_activation_email``. """ - new_user = UserenaSignup.objects.create_inactive_user(**self.user_info) + new_user = UserenaSignup.objects.create_user(**self.user_info) self.failUnlessEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, [self.user_info['email']]) diff --git a/userena/tests/views.py b/userena/tests/views.py index ce3e8bae..a5ec3b99 100644 --- a/userena/tests/views.py +++ b/userena/tests/views.py @@ -97,6 +97,21 @@ def test_signup_view(self): # Back to default userena_settings.USERENA_WITHOUT_USERNAMES = False + def test_signup_view_signout(self): + """ Check that a newly signed user shouldn't be signed in. """ + # User should be signed in + self.failUnless(self.client.login(username='john', password='blowfish')) + # Post a new, valid signup + response = self.client.post(reverse('userena_signup'), + data={'username': 'alice', + 'email': 'alice@example.com', + 'password1': 'blueberry', + 'password2': 'blueberry', + 'tos': 'on'}) + + # And should now be signed out + self.failIf(len(self.client.session.keys()) > 0) + def test_signup_view_success(self): """ After a ``POST`` to the ``signup`` view a new user should be created, diff --git a/userena/urls.py b/userena/urls.py index 092b0f62..191bba85 100644 --- a/userena/urls.py +++ b/userena/urls.py @@ -42,7 +42,8 @@ url(r'^(?P[\.\w]+)/signup/complete/$', userena_views.direct_to_user_template, {'template_name': 'userena/signup_complete.html', - 'extra_context': {'userena_activation_days': userena_settings.USERENA_ACTIVATION_DAYS}}, + 'extra_context': {'userena_activation_required': userena_settings.USERENA_ACTIVATION_REQUIRED, + 'userena_activation_days': userena_settings.USERENA_ACTIVATION_DAYS}}, name='userena_signup_complete'), # Activate