Browse Source

Wip #38: Delete user from profile as mod

Rafał Pitoń 11 years ago
parent
commit
612e0f8c8b

+ 3 - 0
docs/developers/acls.rst

@@ -77,6 +77,9 @@ Optional. Is called when Misago is trying to make ``target`` aware of its ACLs.
 
 
 Value of ``target`` argument has ``acl`` attribute which is dict with incomplete ACL that function can change and update with new keys.
 Value of ``target`` argument has ``acl`` attribute which is dict with incomplete ACL that function can change and update with new keys.
 
 
+.. note::
+   This will not work for instances of User model, that already reserve ``acl`` attribute for their own acls. Instead add_acl_to_target for User instances will add acl's to `acl_` attribute.
+
 Misago comes with its own debug page titled "Misago User ACL" that is available from Django Debug Toolbar menu. This page display user roles permissions as well as final ACL assigned to current user.
 Misago comes with its own debug page titled "Misago User ACL" that is available from Django Debug Toolbar menu. This page display user roles permissions as well as final ACL assigned to current user.
 
 
 
 

+ 6 - 1
misago/acl/api.py

@@ -1,3 +1,5 @@
+from django.contrib.auth import get_user_model
+
 from misago.core import threadstore
 from misago.core import threadstore
 from misago.core.cache import cache
 from misago.core.cache import cache
 
 
@@ -58,7 +60,10 @@ def _add_acl_to_target(user, target):
     """
     """
     Add valid ACL to single target, helper for add_acl function
     Add valid ACL to single target, helper for add_acl function
     """
     """
-    target.acl = {}
+    if isinstance(target, get_user_model()):
+        target.acl_ = {}
+    else:
+        target.acl = {}
 
 
     for extension, module in providers.list():
     for extension, module in providers.list():
         if hasattr(module, 'add_acl_to_target'):
         if hasattr(module, 'add_acl_to_target'):

+ 24 - 0
misago/acl/decorators.py

@@ -0,0 +1,24 @@
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
+
+
+def require_target_type(supported_type):
+    def wrap(f):
+        def decorator(user, acl, target):
+            if isinstance(target, supported_type):
+                return f(user, acl, target)
+            else:
+                return None
+        return decorator
+    return wrap
+
+
+def return_boolean(f):
+    def decorator(*args, **kwargs):
+        try:
+            f(*args, **kwargs)
+        except (Http404, PermissionDenied):
+            return False
+        else:
+            return True
+    return decorator

+ 1 - 0
misago/acl/forms.py

@@ -21,6 +21,7 @@ def get_permissions_forms(role, data=None):
     role_permissions = role.permissions
     role_permissions = role.permissions
 
 
     forms = []
     forms = []
+    print 'ALC Providers: %s' % len(providers.list())
     for extension, module in providers.list():
     for extension, module in providers.list():
         try:
         try:
             module.change_permissions_form
             module.change_permissions_form

+ 17 - 17
misago/acl/migrations/0003_default_roles.py

@@ -40,10 +40,10 @@ def create_default_roles(apps, schema_editor):
                 'can_see_hidden_users': 0,
                 'can_see_hidden_users': 0,
             },
             },
 
 
-            # destroy users perms
-            'misago.users.permissions.destroying': {
-                'can_destroy_user_newer_than': 0,
-                'can_destroy_users_with_less_posts_than': 0,
+            # delete users perms
+            'misago.users.permissions.delete': {
+                'can_delete_users_newer_than': 0,
+                'can_delete_users_with_less_posts_than': 0,
             },
             },
         })
         })
     role.save()
     role.save()
@@ -69,10 +69,10 @@ def create_default_roles(apps, schema_editor):
                 'can_see_hidden_users': 0,
                 'can_see_hidden_users': 0,
             },
             },
 
 
-            # destroy users perms
-            'misago.users.permissions.destroying': {
-                'can_destroy_user_newer_than': 0,
-                'can_destroy_users_with_less_posts_than': 0,
+            # delete users perms
+            'misago.users.permissions.delete': {
+                'can_delete_users_newer_than': 0,
+                'can_delete_users_with_less_posts_than': 0,
             },
             },
         })
         })
     role.save()
     role.save()
@@ -98,21 +98,21 @@ def create_default_roles(apps, schema_editor):
                 'can_see_hidden_users': 1,
                 'can_see_hidden_users': 1,
             },
             },
 
 
-            # destroy users perms
-            'misago.users.permissions.destroying': {
-                'can_destroy_user_newer_than': 0,
-                'can_destroy_users_with_less_posts_than': 0,
+            # delete users perms
+            'misago.users.permissions.delete': {
+                'can_delete_users_newer_than': 0,
+                'can_delete_users_with_less_posts_than': 0,
             },
             },
         })
         })
     role.save()
     role.save()
 
 
-    role = Role(name=_("Spammers Deleter"))
+    role = Role(name=_("Delete users"))
     pickle_permissions(role,
     pickle_permissions(role,
         {
         {
-            # destroy users perms
-            'misago.users.permissions.destroying': {
-                'can_destroy_user_newer_than': 2,
-                'can_destroy_users_with_less_posts_than': 20,
+            # delete users perms
+            'misago.users.permissions.delete': {
+                'can_delete_users_newer_than': 3,
+                'can_delete_users_with_less_posts_than': 7,
             },
             },
         })
         })
     role.save()
     role.save()

+ 1 - 1
misago/conf/defaults.py

@@ -154,7 +154,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
 MISAGO_ACL_EXTENSIONS = (
 MISAGO_ACL_EXTENSIONS = (
     'misago.users.permissions.account',
     'misago.users.permissions.account',
     'misago.users.permissions.profiles',
     'misago.users.permissions.profiles',
-    'misago.users.permissions.destroying',
+    'misago.users.permissions.delete',
     'misago.forums.permissions',
     'misago.forums.permissions',
 )
 )
 
 

+ 3 - 3
misago/faker/management/commands/createfakeusers.py

@@ -31,7 +31,7 @@ class Command(BaseCommand):
         message = 'Successfully created %s fake user accounts!'
         message = 'Successfully created %s fake user accounts!'
 
 
         created_count = 0
         created_count = 0
-        for i in xrange(fake_users_to_create):
+        for i in xrange(fake_users_to_create + 1):
             try:
             try:
                 kwargs = {
                 kwargs = {
                     'rank': random.choice(ranks),
                     'rank': random.choice(ranks),
@@ -44,7 +44,7 @@ class Command(BaseCommand):
                 pass
                 pass
             else:
             else:
                 created_count += 1
                 created_count += 1
-                if created_count % 100 == 0:
+                if (created_count * 100 / fake_users_to_create) % 10 == 0:
                     self.stdout.write(message % created_count)
                     self.stdout.write(message % created_count)
 
 
-        self.stdout.write(message % User.objects.all().count())
+        self.stdout.write(message % created_count)

+ 53 - 8
misago/static/misago/css/misago/buttons.less

@@ -2,6 +2,18 @@
 // Forum Buttons
 // Forum Buttons
 // --------------------------------------------------
 // --------------------------------------------------
 
 
+
+.misago-button-active-flavour(@btn-color, @btn-bg, @btn-border) {
+  background: darken(@btn-bg, 15%);
+  border-color: darken(@btn-bg, 15%);
+  box-shadow: none;
+  position: relative;
+  top: 2px;
+
+  color: darken(@btn-color, 10%);
+}
+
+
 .misago-button-flavour(@btn-color, @btn-bg, @btn-border) {
 .misago-button-flavour(@btn-color, @btn-bg, @btn-border) {
   background: @btn-bg;
   background: @btn-bg;
   border-color: @btn-bg;
   border-color: @btn-bg;
@@ -17,14 +29,8 @@
     color: @btn-color;
     color: @btn-color;
   }
   }
 
 
-  &.active, &:active, &:focus {
-    background: darken(@btn-bg, 15%);
-    border-color: darken(@btn-bg, 15%);
-    box-shadow: none;
-    position: relative;
-    top: 2px;
-
-    color: darken(@btn-color, 10%);
+  &.active, &:active {
+    .misago-button-active-flavour(@btn-color, @btn-bg, @btn-border);
   }
   }
 
 
   .glyphicon-chevron-down {
   .glyphicon-chevron-down {
@@ -35,6 +41,7 @@
   }
   }
 }
 }
 
 
+
 .btn {
 .btn {
   &.btn-default {
   &.btn-default {
     .misago-button-flavour(@btn-default-color, @btn-default-bg, @btn-default-border);
     .misago-button-flavour(@btn-default-color, @btn-default-bg, @btn-default-border);
@@ -60,3 +67,41 @@
     .misago-button-flavour(@btn-danger-color, @btn-danger-bg, @btn-danger-border);
     .misago-button-flavour(@btn-danger-color, @btn-danger-bg, @btn-danger-border);
   }
   }
 }
 }
+
+.open .dropdown-toggle {
+  &.btn-default {
+    &, &:link, &:visited, &:hover , &:active {
+      .misago-button-active-flavour(@btn-default-color, @btn-default-bg, @btn-default-border);
+    }
+  }
+
+  &.btn-primary {
+    &, &:link, &:visited, &:hover , &:active {
+      .misago-button-active-flavour(@btn-primary-color, @btn-primary-bg, @btn-primary-border);
+    }
+  }
+
+  &.btn-info {
+    &, &:link, &:visited, &:hover , &:active {
+      .misago-button-active-flavour(@btn-info-color, @btn-info-bg, @btn-info-border);
+    }
+  }
+
+  &.btn-success {
+    &, &:link, &:visited, &:hover , &:active {
+      .misago-button-active-flavour(@btn-success-color, @btn-success-bg, @btn-success-border);
+    }
+  }
+
+  &.btn-warning {
+    &, &:link, &:visited, &:hover , &:active {
+      .misago-button-active-flavour(@btn-warning-color, @btn-warning-bg, @btn-warning-border);
+    }
+  }
+
+  &.btn-danger {
+    &, &:link, &:visited, &:hover , &:active {
+      .misago-button-active-flavour(@btn-danger-color, @btn-danger-bg, @btn-danger-border);
+    }
+  }
+}

+ 10 - 1
misago/static/misago/css/misago/dropdowns.less

@@ -28,6 +28,7 @@
     button {
     button {
       background: none;
       background: none;
       border: none;
       border: none;
+      border-radius: 0px;
       padding: 3px 20px;
       padding: 3px 20px;
       width: 100%;
       width: 100%;
 
 
@@ -56,6 +57,8 @@
     a, button {
     a, button {
       .glyphicon, .fa {
       .glyphicon, .fa {
         display: inline-block;
         display: inline-block;
+        margin-left: -10px;
+        margin-right: 4px;
         width: 16px;
         width: 16px;
 
 
         text-align: center;
         text-align: center;
@@ -109,7 +112,13 @@
 /* Big displays */
 /* Big displays */
 @media (min-width: @screen-sm-min) {
 @media (min-width: @screen-sm-min) {
   .dropdown-menu {
   .dropdown-menu {
-    min-width: 300px;
+    &.width-medium {
+      max-width: 200px;
+    }
+
+    &.width-large {
+      max-width: 300px;
+    }
 
 
     .dropdown-title {
     .dropdown-title {
       background-color: #ecf0f1;
       background-color: #ecf0f1;

+ 1 - 7
misago/templates/misago/admin/users/list.html

@@ -57,13 +57,7 @@
   <a href="mailto:{{ item.email }}">{{ item.email }}</a>
   <a href="mailto:{{ item.email }}">{{ item.email }}</a>
 </td>
 </td>
 <td>
 <td>
-  {% if user.rank %}
-    {% if user.rank.title %}
-      {{ user.rank.title }}
-    {% else %}
-      {{ user.rank.name }}
-    {% endif %}
-  {% endif %}
+  {{ item.rank.name }}
 </td>
 </td>
 <td>
 <td>
   <abbr class="tooltip-top dynamic time-ago" title="{{ item.joined_on }}" data-timestamp="{{ item.joined_on|date:"c" }}">
   <abbr class="tooltip-top dynamic time-ago" title="{{ item.joined_on }}" data-timestamp="{{ item.joined_on|date:"c" }}">

+ 0 - 106
misago/templates/misago/profile/base.html

@@ -1,106 +0,0 @@
-{% extends "misago/base.html" %}
-{% load humanize i18n misago_avatars %}
-
-
-{% block title %}
-{{ profile.username }}: {{ active_page.name }} {% if page_number > 1 %}({{ number }}) {% endif %}| {{ block.super }}
-{% endblock title %}
-
-
-{% block content %}
-<div class="page-header user-profile-header">
-  <div class="container">
-    <div class="row">
-      <div class="col-md-9 col-md-offset-3">
-
-        <ul class="nav nav-tabs">
-          {% for page in pages %}
-          <li{% if page.is_active %} class="active"{% endif %}>
-            <a href="{% url page.link user_slug=profile.slug user_id=profile.pk %}">
-              {{ page.name }}
-              {% if page.badge != None %}
-              <span class="label label-default">{{ page.badge }}</span>
-              {% endif %}
-            </a>
-          </li>
-          {% endfor %}
-        </ul>
-
-        {% comment %}
-        <div class="page-actions">
-
-          <a href="#" class="btn btn-primary">Primary</a>
-          <a href="#" class="btn btn-success">Success</a>
-          <a href="#" class="btn btn-warning">Warning</a>
-          <a href="#" class="btn btn-danger">Danger</a>
-
-          <div class="btn-group">
-            <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
-              Action <span class="glyphicon glyphicon-chevron-down"></span>
-            </button>
-            <ul class="dropdown-menu" role="menu">
-              <li><a href="#">Action</a></li>
-              <li><a href="#">Another action</a></li>
-              <li><a href="#">Something else here</a></li>
-              <li class="divider"></li>
-              <li><a href="#">Separated link</a></li>
-            </ul>
-          </div>
-
-        </div>
-        {% endcomment %}
-
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="container user-profile {% if profile.rank.css_class %}profile-{{ profile.rank.css_class }}{% endif %}">
-  <div class="row">
-    <div class="col-md-3 profile-side">
-      <div class="user-avatar">
-        {% if authenticateds_profile %}
-        <a href="{% url 'misago:usercp_change_avatar' %}" class="tooltip-top" title="{% trans "Change your avatar" %}">
-          <img src="{{ profile|avatar:400 }}" class="img-rounded img-responsive" alt="{% trans "Avatar" %}">
-        </a>
-        {% else %}
-        <img src="{{ profile|avatar:400 }}" class="img-rounded img-responsive" alt="{% trans "Avatar" %}">
-        {% endif %}
-      </div>
-
-      <h1 class="user-name">
-        {{ profile.username }}
-        {% if profile.full_title %}
-        <small class="user-title">{{ profile.full_title }}</small>
-        {% endif %}
-      </h1>
-
-      <div class="user-details">
-        <ul class="list-unstyled">
-          <li class="user-active">
-            {% include "misago/profile/state.html" %}
-          </li>
-          <li class="user-joined-on">
-            <span class="tooltip-top" title="{% trans "Joined on" %}">
-              <span class="fa fa-clock-o fa-fw"></span>
-              {{ profile.joined_on|date }}
-            </span>
-          </li>
-          {% if show_email %}
-          <li class="user-email">
-            <a href="mailto:{{ profile.email }}" class="tooltip-top"title="{% trans "E-mail address" %}">
-              <span class="fa fa-envelope-o fa-fw"></span>
-              {{ profile.email }}
-            </a>
-          </li>
-          {% endif %}
-        </ul>
-      </div>
-
-    </div>
-    <div class="col-md-9">
-      {% block page %}{% endblock page %}
-    </div>
-  </div>
-</div>
-{% endblock content %}

+ 40 - 0
misago/templates/misago/profile/side.html

@@ -0,0 +1,40 @@
+{% load i18n misago_avatars %}
+
+<div class="user-avatar">
+  {% if authenticateds_profile %}
+  <a href="{% url 'misago:usercp_change_avatar' %}" class="tooltip-top" title="{% trans "Change your avatar" %}">
+    <img src="{{ profile|avatar:400 }}" class="img-rounded img-responsive" alt="{% trans "Avatar" %}">
+  </a>
+  {% else %}
+  <img src="{{ profile|avatar:400 }}" class="img-rounded img-responsive" alt="{% trans "Avatar" %}">
+  {% endif %}
+</div>
+
+<h1 class="user-name">
+  {{ profile.username }}
+  {% if profile.full_title %}
+  <small class="user-title">{{ profile.full_title }}</small>
+  {% endif %}
+</h1>
+
+<div class="user-details">
+  <ul class="list-unstyled">
+    <li class="user-active">
+      {% include "misago/profile/state.html" %}
+    </li>
+    <li class="user-joined-on">
+      <span class="tooltip-top" title="{% trans "Joined on" %}">
+        <span class="fa fa-clock-o fa-fw"></span>
+        {{ profile.joined_on|date }}
+      </span>
+    </li>
+    {% if show_email %}
+    <li class="user-email">
+      <a href="mailto:{{ profile.email }}" class="tooltip-top"title="{% trans "E-mail address" %}">
+        <span class="fa fa-envelope-o fa-fw"></span>
+        {{ profile.email }}
+      </a>
+    </li>
+    {% endif %}
+  </ul>
+</div>

+ 95 - 0
misago/users/permissions/delete.py

@@ -0,0 +1,95 @@
+from datetime import timedelta
+
+from django.contrib.auth import get_user_model
+from django.core.exceptions import PermissionDenied
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _, ungettext
+
+from misago.acl import algebra
+from misago.acl.decorators import require_target_type, return_boolean
+from misago.acl.models import Role
+from misago.core import forms
+
+
+"""
+Admin Permissions Form
+"""
+class PermissionsForm(forms.Form):
+    legend = _("Deleting users")
+
+    can_delete_users_newer_than = forms.IntegerField(
+        label=_("Maximum age of deleted account (in days)"),
+        help_text=_("Enter zero to disable this check."),
+        min_value=0,
+        initial=0)
+    can_delete_users_with_less_posts_than = forms.IntegerField(
+        label=_("Maximum number of posts on deleted account"),
+        help_text=_("Enter zero to disable this check."),
+        min_value=0,
+        initial=0)
+
+
+def change_permissions_form(role):
+    if isinstance(role, Role) and role.special_role != 'anonymous':
+        return PermissionsForm
+    else:
+        return None
+
+
+"""
+ACL Builder
+"""
+def build_acl(acl, roles, key_name):
+    new_acl = {
+        'can_delete_users_newer_than': 0,
+        'can_delete_users_with_less_posts_than': 0,
+    }
+    new_acl.update(acl)
+
+    return algebra.sum_acls(
+            new_acl, roles=roles, key=key_name,
+            can_delete_users_newer_than=algebra.greater,
+            can_delete_users_with_less_posts_than=algebra.greater
+            )
+
+
+"""
+ACL's for targets
+"""
+@require_target_type(get_user_model())
+def add_acl_to_target(user, acl, target):
+    target.acl_['can_delete'] = can_delete_user(user, target)
+    if target.acl_['can_delete']:
+        target.acl_['can_moderate'] = True
+
+
+"""
+ACL tests
+"""
+def allow_delete_user(user, target):
+    newer_than = user.acl['can_delete_users_newer_than']
+    less_posts_than = user.acl['can_delete_users_with_less_posts_than']
+    if not (newer_than or less_posts_than):
+        raise PermissionDenied(_("You can't delete users."))
+
+    if user.pk == target.pk:
+        raise PermissionDenied(_("You can't delete yourself."))
+    if target.is_staff or target.is_superuser:
+        raise PermissionDenied(_("You can't delete administrators."))
+
+    if newer_than:
+        if target.joined_on < timezone.now() - timedelta(days=newer_than):
+            message = ungettext("You can't delete users that are "
+                                "members for more than %(days)s day.",
+                                "You can't delete users that are "
+                                "members for more than %(days)s days.",
+                                newer_than) % {'days': newer_than}
+            raise PermissionDenied(message)
+    if less_posts_than:
+        if target.posts > less_posts_than:
+            message = ungettext(
+                "You can't delete users that made more than %(posts)s post.",
+                "You can't delete users that made more than %(posts)s posts.",
+                less_posts_than) % {'posts': less_posts_than}
+            raise PermissionDenied(message)
+can_delete_user = return_boolean(allow_delete_user)

+ 0 - 47
misago/users/permissions/destroying.py

@@ -1,47 +0,0 @@
-from django.utils.translation import ugettext_lazy as _
-
-from misago.acl import algebra
-from misago.acl.models import Role
-from misago.core import forms
-
-
-"""
-Admin Permissions Form
-"""
-class PermissionsForm(forms.Form):
-    legend = _("Deleting spammer accounts")
-
-    can_destroy_user_newer_than = forms.IntegerField(
-        label=_("Maximum age of deleted account (in days)"),
-        help_text=_("Enter zero to disable this check."),
-        min_value=0,
-        initial=0)
-    can_destroy_users_with_less_posts_than = forms.IntegerField(
-        label=_("Maximum number of posts on deleted account"),
-        help_text=_("Enter zero to disable this check."),
-        min_value=0,
-        initial=0)
-
-
-def change_permissions_form(role):
-    if isinstance(role, Role) and role.special_role != 'anonymous':
-        return PermissionsForm
-    else:
-        return None
-
-
-"""
-ACL Builder
-"""
-def build_acl(acl, roles, key_name):
-    new_acl = {
-        'can_destroy_user_newer_than': 0,
-        'can_destroy_users_with_less_posts_than': 0,
-    }
-    new_acl.update(acl)
-
-    return algebra.sum_acls(
-            new_acl, roles=roles, key=key_name,
-            can_destroy_user_newer_than=algebra.greater,
-            can_destroy_users_with_less_posts_than=algebra.greater
-            )

+ 7 - 0
misago/users/urls.py

@@ -63,6 +63,13 @@ urlpatterns += patterns('',
 
 
 
 
 urlpatterns += patterns('',
 urlpatterns += patterns('',
+    url(r'^mod-user/(?P<user_id>\d+)/', include(patterns('misago.users.views.moderation',
+        url(r'^delete/$', 'delete', name='delete_user'),
+    ))),
+)
+
+
+urlpatterns += patterns('',
     url(r'^user-avatar/', include(patterns('misago.users.views.avatarserver',
     url(r'^user-avatar/', include(patterns('misago.users.views.avatarserver',
         url(r'(?P<size>\d+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar', name="user_avatar"),
         url(r'(?P<size>\d+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar', name="user_avatar"),
         url(r'tmp:(?P<token>[a-zA-Z0-9]+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar_source', name="user_avatar_tmp", kwargs={'type': 'tmp'}),
         url(r'tmp:(?P<token>[a-zA-Z0-9]+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar_source', name="user_avatar_tmp", kwargs={'type': 'tmp'}),

+ 38 - 0
misago/users/views/moderation.py

@@ -0,0 +1,38 @@
+from django.contrib import messages
+from django.contrib.auth import get_user_model
+from django.db import transaction
+from django.shortcuts import get_object_or_404, redirect, render
+from django.utils.translation import ugettext as _
+
+from misago.acl import add_acl
+from misago.core.decorators import require_POST
+
+from misago.users.permissions.delete import allow_delete_user
+
+
+def user_moderation_view(required_permission=None):
+    def wrap(f):
+        @transaction.atomic
+        def decorator(request, *args, **kwargs):
+            queryset = get_user_model().objects
+            user_id = kwargs.pop('user_id')
+
+            kwargs['user'] = get_object_or_404(queryset, id=user_id)
+            add_acl(request.user, kwargs['user'])
+
+            if required_permission:
+                required_permission(request.user, kwargs['user'])
+
+            return f(request, *args, **kwargs)
+        return decorator
+    return wrap
+
+
+@require_POST
+@user_moderation_view(allow_delete_user)
+def delete(request, user):
+    user.delete(delete_content=True)
+
+    message = _("User %(username)s account has been deleted with all content.")
+    messages.success(request, message % {'username': user.username})
+    return redirect('misago:index')

+ 5 - 2
misago/users/views/profile.py

@@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model
 from django.http import Http404
 from django.http import Http404
 from django.shortcuts import redirect, render as django_render
 from django.shortcuts import redirect, render as django_render
 
 
+from misago.acl import add_acl
 from misago.core.shortcuts import get_object_or_404, paginate, validate_slug
 from misago.core.shortcuts import get_object_or_404, paginate, validate_slug
 
 
 from misago.users import online
 from misago.users import online
@@ -9,7 +10,7 @@ from misago.users.sites import user_profile
 
 
 
 
 def profile_view(f):
 def profile_view(f):
-    def decorator(*args, **kwargs):
+    def decorator(request, *args, **kwargs):
         relations = ('rank', 'online_tracker', 'ban_cache')
         relations = ('rank', 'online_tracker', 'ban_cache')
         queryset = get_user_model().objects.select_related(*relations)
         queryset = get_user_model().objects.select_related(*relations)
         profile = get_object_or_404(queryset, id=kwargs.pop('user_id'))
         profile = get_object_or_404(queryset, id=kwargs.pop('user_id'))
@@ -17,7 +18,9 @@ def profile_view(f):
         validate_slug(profile, kwargs.pop('user_slug'))
         validate_slug(profile, kwargs.pop('user_slug'))
         kwargs['profile'] = profile
         kwargs['profile'] = profile
 
 
-        return f(*args, **kwargs)
+        add_acl(request.user, profile)
+
+        return f(request, *args, **kwargs)
     return decorator
     return decorator