Просмотр исходного кода

Users app was split into separate smaller apps.

Ralfp 12 лет назад
Родитель
Сommit
ae2a12d034
61 измененных файлов с 761 добавлено и 709 удалено
  1. 0 0
      misago/activation/__init__.py
  2. 3 3
      misago/activation/forms.py
  3. 6 0
      misago/activation/urls.py
  4. 2 2
      misago/activation/views.py
  5. 14 13
      misago/admin/layout/users.py
  6. 0 0
      misago/auth/__init__.py
  7. 0 0
      misago/auth/forms.py
  8. 0 0
      misago/auth/models.py
  9. 0 0
      misago/auth/urls.py
  10. 0 0
      misago/auth/views.py
  11. 20 0
      misago/banning/views.py
  12. 0 16
      misago/banning/views/__init__.py
  13. 0 0
      misago/profiles/__init__.py
  14. 0 0
      misago/profiles/forms.py
  15. 11 0
      misago/profiles/urls.py
  16. 61 4
      misago/profiles/views.py
  17. 0 0
      misago/prune/__init__.py
  18. 1 1
      misago/prune/forms.py
  19. 12 0
      misago/prune/models.py
  20. 24 23
      misago/prune/views.py
  21. 0 1
      misago/ranks/views.py
  22. 0 0
      misago/register/__init__.py
  23. 134 0
      misago/register/fixtures.py
  24. 1 1
      misago/register/forms.py
  25. 5 0
      misago/register/urls.py
  26. 2 2
      misago/register/views.py
  27. 0 0
      misago/resetpswd/__init__.py
  28. 33 0
      misago/resetpswd/forms.py
  29. 6 0
      misago/resetpswd/urls.py
  30. 3 3
      misago/resetpswd/views.py
  31. 7 2
      misago/settings_base.py
  32. 5 1
      misago/urls.py
  33. 0 0
      misago/usercp/__init__.py
  34. 50 0
      misago/usercp/fixtures.py
  35. 0 0
      misago/usercp/forms.py
  36. 10 0
      misago/usercp/urls.py
  37. 9 9
      misago/usercp/views.py
  38. 1 167
      misago/users/fixtures.py
  39. 0 0
      misago/users/forms.py
  40. 0 11
      misago/users/models.py
  41. 0 22
      misago/users/urls.py
  42. 332 24
      misago/users/views.py
  43. 0 333
      misago/users/views/admin.py
  44. 0 62
      misago/users/views/profiles.py
  45. 0 0
      templates/admin/policies/list.html
  46. 0 0
      templates/admin/users/list.html
  47. 1 1
      templates/sora/profiles/details.html
  48. 0 0
      templates/sora/profiles/list.html
  49. 0 0
      templates/sora/profiles/profile.html
  50. 0 0
      templates/sora/register.html
  51. 0 0
      templates/sora/resend_activation.html
  52. 0 0
      templates/sora/reset_password.html
  53. 1 1
      templates/sora/usercp/avatar.html
  54. 1 1
      templates/sora/usercp/avatar_banned.html
  55. 1 1
      templates/sora/usercp/credentials.html
  56. 1 1
      templates/sora/usercp/ignored.html
  57. 0 0
      templates/sora/usercp/layout.html
  58. 1 1
      templates/sora/usercp/options.html
  59. 1 1
      templates/sora/usercp/signature.html
  60. 1 1
      templates/sora/usercp/signature_banned.html
  61. 1 1
      templates/sora/usercp/username.html

+ 0 - 0
misago/users/forms/__init__.py → misago/activation/__init__.py


+ 3 - 3
misago/users/forms/special.py → misago/activation/forms.py

@@ -2,11 +2,11 @@ from django import forms
 from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 from misago.forms import Form
-import misago.captcha
+from misago import captcha
 from misago.users.models import User
     
     
-class UserSendSpecialMailForm(Form):
+class UserSendActivationMailForm(Form):
     email = forms.EmailField(max_length=255)
     captcha_qa = captcha.QACaptchaField()
     recaptcha = captcha.ReCaptchaField()
@@ -15,7 +15,7 @@ class UserSendSpecialMailForm(Form):
     layout = [
               (
                None,
-               [('email', {'label': _("Your E-mail Address"), 'help_text': _("Enter email address you use to sign in to forums."), 'attrs': {'placeholder': _("Enter your e-mail address.")}})]
+               [('email', {'label': _("Your E-mail Address"), 'help_text': _("Enter email address send activation e-mail to. It must be valid e-mail you used to register on forums."), 'attrs': {'placeholder': _("Enter your e-mail address.")}})]
                ),
               (
                None,

+ 6 - 0
misago/activation/urls.py

@@ -0,0 +1,6 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.activation.views',
+    url(r'^request/$', 'form', name="send_activation"),
+    url(r'^(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'activate', name="activate"),
+)

+ 2 - 2
misago/users/views/activation.py → misago/activation/views.py

@@ -7,7 +7,7 @@ from misago.forms.layouts import FormLayout
 from misago.messages import Message
 from misago.security.auth import sign_user_in
 from misago.security.decorators import *
-from misago.users.forms.special import UserSendSpecialMailForm
+from misago.activation.forms import UserSendActivationMailForm
 from misago.users.models import User
 from misago.views import redirect_message, error404
 
@@ -44,7 +44,7 @@ def form(request):
             message = Message(form.non_field_errors()[0], 'error')
     else:
         form = UserSendSpecialMailForm(request=request)
-    return request.theme.render_to_response('users/resend_activation.html',
+    return request.theme.render_to_response('resend_activation.html',
                                             {
                                              'message': message,
                                              'form': FormLayout(form),

+ 14 - 13
misago/admin/layout/users.py

@@ -3,9 +3,10 @@ from django.utils.translation import ugettext_lazy as _
 from misago.admin import AdminAction
 from misago.banning.models import Ban
 from misago.newsletters.models import Newsletter
+from misago.prune.models import Policy
 from misago.ranks.models import Rank
 from misago.roles.models import Role
-from misago.users.models import User, Pruning
+from misago.users.models import User
 
 ADMIN_ACTIONS=(
    AdminAction(
@@ -30,7 +31,7 @@ ADMIN_ACTIONS=(
                          },
                         ],
                route='admin_users',
-               urlpatterns=patterns('misago.users.views.admin',
+               urlpatterns=patterns('misago.users.views',
                         url(r'^$', 'List', name='admin_users'),
                         url(r'^(?P<page>\d+)/$', 'List', name='admin_users'),
                         url(r'^inactive/$', 'inactive', name='admin_users_inactive'),
@@ -120,7 +121,7 @@ ADMIN_ACTIONS=(
                          },
                         ],
                route='admin_bans',
-               urlpatterns=patterns('misago.banning.views.admin',
+               urlpatterns=patterns('misago.banning.views',
                         url(r'^$', 'List', name='admin_bans'),
                         url(r'^(?P<page>\d+)/$', 'List', name='admin_bans'),
                         url(r'^new/$', 'New', name='admin_bans_new'),
@@ -130,31 +131,31 @@ ADMIN_ACTIONS=(
                ),
    AdminAction(
                section='users',
-               id='pruning',
+               id='prune_users',
                name=_("Prune Users"),
                help=_("Delete multiple Users"),
                icon='remove',
-               model=Pruning,
+               model=Policy,
                actions=[
                         {
                          'id': 'list',
                          'name': _("Pruning Policies"),
                          'help': _("Browse all existing pruning policies"),
-                         'route': 'admin_users_pruning'
+                         'route': 'admin_prune_users'
                          },
                         {
                          'id': 'new',
                          'name': _("Set New Policy"),
                          'help': _("Set new pruning policy"),
-                         'route': 'admin_users_pruning_new'
+                         'route': 'admin_prune_users_new'
                          },
                         ],
-               route='admin_users_pruning',
-               urlpatterns=patterns('misago.users.views.pruning',
-                        url(r'^$', 'List', name='admin_users_pruning'),
-                        url(r'^new/$', 'New', name='admin_users_pruning_new'),
-                        url(r'^edit/(?P<target>\d+)/$', 'Edit', name='admin_users_pruning_edit'),
-                        url(r'^delete/(?P<target>\d+)/$', 'Delete', name='admin_users_pruning_delete'),
+               route='admin_prune_users',
+               urlpatterns=patterns('misago.prune.views',
+                        url(r'^$', 'List', name='admin_prune_users'),
+                        url(r'^new/$', 'New', name='admin_prune_users_new'),
+                        url(r'^edit/(?P<target>\d+)/$', 'Edit', name='admin_prune_users_edit'),
+                        url(r'^delete/(?P<target>\d+)/$', 'Delete', name='admin_prune_users_delete'),
                     ),
                ),
    AdminAction(

+ 0 - 0
misago/users/views/__init__.py → misago/auth/__init__.py


+ 0 - 0
misago/auth/forms.py


+ 0 - 0
misago/auth/models.py


+ 0 - 0
misago/auth/urls.py


+ 0 - 0
misago/auth/views.py


+ 20 - 0
misago/banning/views/admin.py → misago/banning/views.py

@@ -1,11 +1,16 @@
 from django.core.urlresolvers import reverse as django_reverse
 from django.db.models import Q
+from django.template import RequestContext
 from django.utils.translation import ugettext as _
 from misago.admin import site
 from misago.admin.widgets import *
 from misago.banning.forms import BanForm, SearchBansForm
 from misago.banning.models import Ban
+from misago.messages import Message
 
+"""
+Admin mixin
+"""
 def reverse(route, target=None):
     if target:
         return django_reverse(route, kwargs={'target': target.pk})
@@ -14,6 +19,21 @@ def reverse(route, target=None):
 """
 Views
 """
+def error_banned(request, user=None, ban=None):
+    if not ban:
+        ban = request.ban
+    response = request.theme.render_to_response('error403_banned.html',
+                                                {
+                                                 'banned_user': user,
+                                                 'ban': ban,
+                                                 'hide_signin': True,
+                                                 'exception_response': True,
+                                                 },
+                                                context_instance=RequestContext(request));
+    response.status_code = 403
+    return response
+
+
 class List(ListWidget):
     """
     List Bans

+ 0 - 16
misago/banning/views/__init__.py

@@ -1,16 +0,0 @@
-from django.template import RequestContext
-from misago.messages import Message
-
-def error_banned(request, user=None, ban=None):
-    if not ban:
-        ban = request.ban
-    response = request.theme.render_to_response('error403_banned.html',
-                                                {
-                                                 'banned_user': user,
-                                                 'ban': ban,
-                                                 'hide_signin': True,
-                                                 'exception_response': True,
-                                                 },
-                                                context_instance=RequestContext(request));
-    response.status_code = 403
-    return response

+ 0 - 0
misago/profiles/__init__.py


+ 0 - 0
misago/users/forms/list.py → misago/profiles/forms.py


+ 11 - 0
misago/profiles/urls.py

@@ -0,0 +1,11 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.profiles.views',
+    url(r'^$', 'list', name="users"),
+    url(r'^(?P<username>\w+)-(?P<user>\d+)/$', 'profile', name="user"),
+    url(r'^(?P<username>\w+)-(?P<user>\d+)/threads/$', 'profile', name="user_threads", kwargs={'tab': 'threads'}),
+    url(r'^(?P<username>\w+)-(?P<user>\d+)/following/$', 'profile', name="user_following", kwargs={'tab': 'following'}),
+    url(r'^(?P<username>\w+)-(?P<user>\d+)/followiers/$', 'profile', name="user_followers", kwargs={'tab': 'followers'}),
+    url(r'^(?P<username>\w+)-(?P<user>\d+)/details/$', 'profile', name="user_details", kwargs={'tab': 'details'}),
+    url(r'^(?P<rank_slug>(\w|-)+)/$', 'list', name="users"),
+)

+ 61 - 4
misago/users/views/list.py → misago/profiles/views.py

@@ -3,11 +3,11 @@ from django.shortcuts import redirect
 from django.template import RequestContext
 from misago.forms import FormFields
 from misago.messages import Message
+from misago.profiles.forms import QuickFindUserForm
 from misago.ranks.models import Rank
-from misago.users.forms.list import QuickFindUserForm
 from misago.users.models import User
-from misago.views import error404
 from misago.utils import slugify
+from misago.views import error404
 
 
 def list(request, rank_slug=None):
@@ -65,7 +65,7 @@ def list(request, rank_slug=None):
         if active_rank:
             users = User.objects.filter(rank=active_rank).order_by('username_slug')
     
-    return request.theme.render_to_response('users/list.html',
+    return request.theme.render_to_response('profiles/list.html',
                                         {
                                          'message': message,
                                          'search_form': FormFields(search_form).fields,
@@ -74,4 +74,61 @@ def list(request, rank_slug=None):
                                          'ranks': ranks,
                                          'users': users,
                                         },
-                                        context_instance=RequestContext(request));
+                                        context_instance=RequestContext(request));
+
+
+def profile(request, user, username, tab='posts'):
+    user = int(user)
+    try:
+        user = User.objects.get(pk=user)
+        if user.username_slug != username:
+            # Force crawlers to take notice of updated username
+            return redirect(reverse('user', args=(user.username_slug, user.pk)), permanent=True)
+        return globals()['profile_%s' % tab](request, user)
+    except User.DoesNotExist:
+        return error404(request)
+    
+
+def profile_posts(request, user):
+    return request.theme.render_to_response('profiles/profile.html',
+                                            {
+                                             'profile': user,
+                                             'tab': 'posts',
+                                            },
+                                            context_instance=RequestContext(request));
+    
+
+def profile_threads(request, user):
+    return request.theme.render_to_response('profiles/profile.html',
+                                            {
+                                             'profile': user,
+                                             'tab': 'threads',
+                                            },
+                                            context_instance=RequestContext(request));
+    
+
+def profile_following(request, user):
+    return request.theme.render_to_response('profiles/profile.html',
+                                            {
+                                             'profile': user,
+                                             'tab': 'following',
+                                            },
+                                            context_instance=RequestContext(request));
+    
+
+def profile_followers(request, user):
+    return request.theme.render_to_response('profiles/profile.html',
+                                            {
+                                             'profile': user,
+                                             'tab': 'followers',
+                                            },
+                                            context_instance=RequestContext(request));
+    
+
+def profile_details(request, user):
+    return request.theme.render_to_response('profiles/details.html',
+                                            {
+                                             'profile': user,
+                                             'tab': 'details',
+                                            },
+                                            context_instance=RequestContext(request));

+ 0 - 0
misago/prune/__init__.py


+ 1 - 1
misago/users/forms/pruning.py → misago/prune/forms.py

@@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _
 from django import forms
 from misago.forms import Form
 
-class PruningForm(Form):
+class PolicyForm(Form):
     name = forms.CharField(max_length=255)
     email = forms.CharField(max_length=255,required=False)
     posts = forms.IntegerField(min_value=0,initial=0)

+ 12 - 0
misago/prune/models.py

@@ -0,0 +1,12 @@
+from django.db import models
+
+class Policy(models.Model):
+    """
+    Pruning policy
+    """
+    name = models.CharField(max_length=255)
+    email = models.CharField(max_length=255,null=True,blank=True)
+    posts = models.PositiveIntegerField(default=0)
+    registered = models.PositiveIntegerField(default=0)
+    last_visit = models.PositiveIntegerField(default=0)
+    

+ 24 - 23
misago/users/views/pruning.py → misago/prune/views.py

@@ -4,8 +4,8 @@ from django.utils.translation import ugettext as _
 from misago.admin import site
 from misago.admin.widgets import *
 from misago.forms import Form
-from misago.users.forms.pruning import PruningForm
-from misago.users.models import Pruning
+from misago.prune.forms import PolicyForm
+from misago.prune.models import Policy
 
 def reverse(route, target=None):
     if target:
@@ -16,7 +16,7 @@ def reverse(route, target=None):
 Views
 """
 class List(ListWidget):
-    admin = site.get_action('pruning')
+    admin = site.get_action('prune_users')
     id = 'list'
     columns=(
              ('name', _("Pruning Policy")),
@@ -31,33 +31,33 @@ class List(ListWidget):
     
     def get_item_actions(self, request, item):
         return (
-                self.action('pencil', _("Edit Pruning Policy"), reverse('admin_users_pruning_edit', item)),
-                self.action('remove', _("Delete Pruning Policy"), reverse('admin_users_pruning_delete', item), post=True, prompt=_("Are you sure you want to delete this rank?")),
+                self.action('pencil', _("Edit Pruning Policy"), reverse('admin_pruning_edit', item)),
+                self.action('remove', _("Delete Pruning Policy"), reverse('admin_pruning_delete', item), post=True, prompt=_("Are you sure you want to delete this rank?")),
                 )
 
     def action_delete(self, request, items, checked):
         if not request.user.is_god():
-            return Message(_('Only system administrators can delete pruning policies.'), 'error'), reverse('admin_users_pruning')
+            return Message(_('Only system administrators can delete pruning policies.'), 'error'), reverse('admin_prune_users')
         
-        Pruning.objects.filter(id__in=checked).delete()
-        return Message(_('Selected pruning policies have been deleted successfully.'), 'success'), reverse('admin_users_pruning')
+        Policy.objects.filter(id__in=checked).delete()
+        return Message(_('Selected pruning policies have been deleted successfully.'), 'success'), reverse('admin_prune_users')
 
 
 class New(FormWidget):
-    admin = site.get_action('pruning')
+    admin = site.get_action('prune_users')
     id = 'new'
-    fallback = 'admin_users_pruning' 
-    form = PruningForm
+    fallback = 'admin_prune_users' 
+    form = PolicyForm
     submit_button = _("Save Policy")
         
     def get_new_url(self, request, model):
-        return reverse('admin_users_pruning')
+        return reverse('admin_prune_users')
     
     def get_edit_url(self, request, model):
-        return reverse('admin_users_pruning_edit', model)
+        return reverse('admin_pruning_edit', model)
     
     def submit_form(self, request, form, target):
-        new_policy = Pruning(
+        new_policy = Policy(
                       name = form.cleaned_data['name'],
                       email = form.cleaned_data['email'],
                       posts = form.cleaned_data['posts'],
@@ -71,22 +71,23 @@ class New(FormWidget):
     def __call__(self, request, *args, **kwargs):
         if not request.user.is_god():
             request.messages.set_flash(Message(_('Only system administrators can set new pruning policies.')), 'error', self.admin.id)
-            return redirect(reverse('admin_users_pruning'))
+            return redirect(reverse('admin_prune_users'))
         
         return super(New, self).__call__(request, *args, **kwargs)
-   
+
+  
 class Edit(FormWidget):
-    admin = site.get_action('pruning')
+    admin = site.get_action('prune_users')
     id = 'edit'
     name = _("Edit Pruning Policy")
-    fallback = 'admin_users_pruning'
-    form = PruningForm
+    fallback = 'admin_prune_users'
+    form = PolicyForm
     target_name = 'name'
     notfound_message = _('Requested pruning policy could not be found.')
     submit_fallback = True
     
     def get_url(self, request, model):
-        return reverse('admin_users_pruning_edit', model)
+        return reverse('admin_pruning_edit', model)
     
     def get_edit_url(self, request, model):
         return self.get_url(request, model)
@@ -113,15 +114,15 @@ class Edit(FormWidget):
     def __call__(self, request, *args, **kwargs):
         if not request.user.is_god():
             request.messages.set_flash(Message(_('Only system administrators can edit pruning policies.')), 'error', self.admin.id)
-            return redirect(reverse('admin_users_pruning'))
+            return redirect(reverse('admin_prune_users'))
         
         return super(Edit, self).__call__(request, *args, **kwargs)
 
 
 class Delete(ButtonWidget):
-    admin = site.get_action('pruning')
+    admin = site.get_action('prune_users')
     id = 'delete'
-    fallback = 'admin_users_pruning'
+    fallback = 'admin_prune_users'
     notfound_message = _('Requested pruning policy could not be found.')
     
     def action(self, request, target):

+ 0 - 1
misago/ranks/views.py

@@ -28,7 +28,6 @@ class List(ListWidget):
              ('delete', _("Delete selected ranks"), _("Are you sure you want to delete selected ranks?")),
              )
     
-    
     def get_table_form(self, request, page_items):
         order_form = {}
         

+ 0 - 0
misago/register/__init__.py


+ 134 - 0
misago/register/fixtures.py

@@ -0,0 +1,134 @@
+from misago.settings.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils import ugettext_lazy as _
+from misago.utils import get_msgid
+
+settings_fixtures = (
+    # Register and Sign-In Settings
+    ('register-and-signin', {
+        'name': _("Register and Sign-In Settings"),
+        'description': _("Those settings allow you to increase security of your members accounts."),
+        'settings': (
+            ('account_activation', {
+                'type':         "string",
+                'input':        "choice",
+                'extra':        {'choices': [('', _("No validation required")), ('user', _("Activation Token sent to User")), ('admin', _("Activation by Administrator")), ('block', _("Dont allow new registrations"))]},
+                'separator':    _("Users Registrations"),
+                'name':         _("New accounts validation"),
+                'position':     0,
+            }),
+            ('default_timezone', {
+                'value':        "utc",
+                'type':         "string",
+                'input':        "select",
+                'extra':        {'choices': '#TZ#'},
+                'name':         _("Default Timezone"),
+                'description':  _("Used by guests, crawlers and newly registered users."),
+                'position':     1,
+            }),
+            ('password_length', {
+                'value':        4,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 1},
+                'separator':    _("Users Passwords"),
+                'name':         _("Minimum user password length"),
+                'position':     2,
+            }),
+            ('password_complexity', {
+                'value':        [],
+                'type':         "array",
+                'input':        "mlist",
+                'extra':        {'choices': [('case', _("Require mixed Case")), ('digits', _("Require digits")), ('special', _("Require special characters"))]},
+                'name':         _("Password Complexity"),
+                'position':     3,
+            }),
+            ('password_lifetime', {
+                'value':        0,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _("Password Lifetime"),
+                'description':  _("Enter number of days since password was set to force member to change it with new one, or 0 to dont force your members to change their passwords."),
+                'position':     4,
+            }),
+            ('password_in_email', {
+                'value':        False,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Include User Password in Welcoming E-mail"),
+                'description':  _("If you want to, Misago can include new user password in welcoming e-mail that is sent to new users after successful account creation."),
+                'position':     5,
+            }),
+            ('sessions_validate_ip', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Check IP on session authorization"),
+                'description':  _("Makes sessions more secure, but can cause problems with proxies and VPN's."),
+                'position':     6,
+            }),
+            ('remember_me_allow', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'separator':    _("Sign-In Settings"),
+                'name':         _('Enable "Remember Me" functionality'),
+                'description':  _("Turning this option on allows users to sign in on to your board using cookie-based tokens. This may result in account compromisation when user fails to sign out on shared computer."),
+                'position':     7,
+            }),
+            ('remember_me_lifetime', {
+                'value':        90,
+                'type':         "integer",
+                'input':        "text",
+                'name':         _('"Remember Me" token lifetime'),
+                'description':  _('Number of days since either last use or creation of "Remember Me" token to its expiration.'),
+                'position':     8,
+            }),
+            ('remember_me_extensible', {
+                'value':        1,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _('Allow "Remember Me" tokens refreshing'),
+                'description':  _('Set this setting to off if you want to force your users to periodically update their "Remember Me" tokens by signing in. If this option is on, Tokens are updated when they are used to open new session.'),
+                'position':     9,
+            }),
+            ('login_attempts_limit', {
+                'value':        3,
+                'default':      3,
+                'type':         "integer",
+                'input':        "text",
+                'separator':    _("Brute-Force Countermeasures"),
+                'name':         _("Limit Sign In attempts"),
+                'description':  _('Enter maximal number of allowed Sign In attempts before IP address "jams".'),
+                'position':     10,
+            }),
+            ('registrations_jams', {
+                'value':        1,
+                'default':      1,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Count failed register attempts too"),
+                'description':  _("Set this setting to yes if you want failed register attempts to count into limit."),
+                'position':     11,
+            }),
+            ('jams_lifetime', {
+                'value':        15,
+                'default':      15,
+                'type':         "integer",
+                'input':        "text",
+                'name':         _("Automaticaly unlock jammed IPs"),
+                'description':  _('Enter number of minutes since IP address "jams" to automatically unlock it, or 0 to never unlock jammed IP adresses. Jams dont count as bans.'),
+                'position':     12,
+            }),
+        ),
+    }),
+)
+
+
+def load_fixtures():
+    load_monitor_fixture(monitor_fixtures)
+    load_settings_fixture(settings_fixtures)
+    
+    
+def update_fixtures():
+    update_settings_fixture(settings_fixtures)

+ 1 - 1
misago/users/forms/register.py → misago/register/forms.py

@@ -2,7 +2,7 @@ from django import forms
 from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 from misago.forms import Form
-import misago.captcha
+from misago import captcha
 from misago.timezones import tzlist
 from misago.users.models import User
 from misago.users.validators import validate_password, validate_email

+ 5 - 0
misago/register/urls.py

@@ -0,0 +1,5 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.register.views',
+    url(r'^$', 'form', name="register"),
+)

+ 2 - 2
misago/users/views/register.py → misago/register/views.py

@@ -8,7 +8,7 @@ from misago.forms.layouts import FormLayout
 from misago.messages import Message
 from misago.security.auth import sign_user_in
 from misago.security.decorators import *
-from misago.users.forms.register import UserRegisterForm
+from misago.register.forms import UserRegisterForm
 from misago.users.models import User
 from misago.views import redirect_message
 
@@ -72,7 +72,7 @@ def form(request):
                 return redirect(reverse('register'))
     else:
         form = UserRegisterForm(request=request)
-    return request.theme.render_to_response('users/register.html',
+    return request.theme.render_to_response('register.html',
                                             {
                                              'message': message,
                                              'form': FormLayout(form),

+ 0 - 0
misago/resetpswd/__init__.py


+ 33 - 0
misago/resetpswd/forms.py

@@ -0,0 +1,33 @@
+from django import forms
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext_lazy as _
+from misago.forms import Form
+from misago import captcha
+from misago.users.models import User
+    
+    
+class UserResetPasswordForm(Form):
+    email = forms.EmailField(max_length=255)
+    captcha_qa = captcha.QACaptchaField()
+    recaptcha = captcha.ReCaptchaField()
+    error_source = 'email'
+    
+    layout = [
+              (
+               None,
+               [('email', {'label': _("Your E-mail Address"), 'help_text': _("Enter email address password reset confirmation e-mail will be sent to. It must be valid e-mail you used to register on forums."), 'attrs': {'placeholder': _("Enter your e-mail address.")}})]
+               ),
+              (
+               None,
+               ['captcha_qa', 'recaptcha']
+               ),
+              ]
+    
+    def clean_email(self):
+        try:
+            email = self.cleaned_data['email'].lower()
+            email_hash = hashlib.md5(email).hexdigest()
+            self.found_user = User.objects.get(email_hash=email_hash)
+        except User.DoesNotExist:
+            raise ValidationError(_("There is no user with such e-mail address."))
+        return email

+ 6 - 0
misago/resetpswd/urls.py

@@ -0,0 +1,6 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.resetpswd.views',
+    url(r'^$', 'form', name="forgot_password"),
+    url(r'^(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-z0-9]+)/$', 'reset', name="reset_password"),
+)

+ 3 - 3
misago/users/views/password.py → misago/resetpswd/views.py

@@ -7,7 +7,7 @@ from misago.forms.layouts import FormLayout
 from misago.messages import Message
 from misago.security import get_random_string
 from misago.security.decorators import *
-from misago.users.forms.special import UserSendSpecialMailForm
+from misago.resetpswd.forms import UserResetPasswordForm
 from misago.users.models import User
 from misago.views import redirect_message, error404
 
@@ -19,7 +19,7 @@ def form(request):
     message = None
     
     if request.method == 'POST':
-        form = UserSendSpecialMailForm(request.POST, request=request)
+        form = UserResetPasswordForm(request.POST, request=request)
         
         if form.is_valid():
             user = form.found_user
@@ -43,7 +43,7 @@ def form(request):
             message = Message(form.non_field_errors()[0], 'error')
     else:
         form = UserSendSpecialMailForm(request=request)
-    return request.theme.render_to_response('users/forgot_password.html',
+    return request.theme.render_to_response('reset_password.html',
                                             {
                                              'message': message,
                                              'form': FormLayout(form),

+ 7 - 2
misago/settings_base.py

@@ -109,12 +109,17 @@ INSTALLED_APPS = (
     'misago.security', # Security: CSRF, Firewall, etc ect
     'misago.sessions', # Sessions
     'misago.setup', # Installation/update tool
-    'misago.stopwatch', # Simple stopwatch to measure time spent on request
     'misago.template', # Templates extensions
     'misago.themes', # Themes
-    'misago.users', # Users
+    'misago.users', # Users foundation
+    'misago.prune', # Prune Users
     'misago.ranks', # User Ranks
     'misago.roles', # User Roles
+    'misago.usercp', # User Control Panel
+    'misago.profiles', # User Profiles
+    'misago.register', # Register New Users
+    'misago.activation', # Activate inactive User or resend activation e-mail
+    'misago.resetpswd', # Reset User Password
 )
 
 # IP's that can see debug toolbar

+ 5 - 1
misago/urls.py

@@ -6,7 +6,11 @@ from misago.admin import ADMIN_PATH, site
 # Include frontend patterns
 urlpatterns = patterns('',
     (r'^', include('misago.security.urls')),
-    (r'^', include('misago.users.urls')),
+    (r'^users/', include('misago.profiles.urls')),
+    (r'^usercp/', include('misago.usercp.urls')),
+    (r'^register/', include('misago.register.urls')),
+    (r'^activate/', include('misago.activation.urls')),
+    (r'^reset-password/', include('misago.resetpswd.urls')),
     url(r'^$', 'misago.views.home', name="index"),
 )
 

+ 0 - 0
misago/usercp/__init__.py


+ 50 - 0
misago/usercp/fixtures.py

@@ -0,0 +1,50 @@
+from misago.settings.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils import ugettext_lazy as _
+from misago.utils import get_msgid
+
+settings_fixtures = (
+    # Avatars Settings
+    ('avatars', {
+         'name': _("Users Avatars Settings"),
+         'description': _("Those settings allow you to control your users avatars."),
+         'settings': (
+            ('avatars_types', {
+                'value':        ['gravatar', 'gallery'],
+                'type':         "array",
+                'input':        "mlist",
+                'extra':        {'choices': [('gravatar', _("Gravatar")), ('upload', _("Uploaded Avatar")), ('gallery', _("Avatars Gallery"))]},
+                'separator':    _("General Settings"),
+                'name':         _("Allowed Avatars"),
+                'description':  _("Select Avatar types allowed on your forum."),
+                'position':     0,
+            }),
+            ('default_avatar', {
+                'value':        "gravatar",
+                'type':         "string",
+                'input':        "select",
+                'extra':        {'choices': [('gravatar', _("Gravatar")), ('gallery', _("Random Avatar from Gallery"))]},
+                'name':         _("Default Avatar"),
+                'description':  _("Default Avatar assigned to new members. If you creade directory and name it \"_default\", forum will select random avatar from that directory instead of regular gallery. If no avatar can be picked from gallery, Gravatar will be used."),
+                'position':     1,
+            }),
+            ('upload_limit', {
+                'value':        128,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'separator':    _("Avatar Upload Settings"),
+                'name':         _("Maxmimum size of uploaded file"),
+                'description':  _("Select maximum allowed file size (in KB) for Avatar uploads."),
+                'position':     2,
+            }),
+       ),
+    }),
+)
+
+
+def load_fixtures():
+    load_settings_fixture(settings_fixtures)
+    
+    
+def update_fixtures():
+    update_settings_fixture(settings_fixtures)

+ 0 - 0
misago/users/forms/usercp.py → misago/usercp/forms.py


+ 10 - 0
misago/usercp/urls.py

@@ -0,0 +1,10 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.usercp.views',
+    url(r'^$', 'options', name="usercp"),
+    url(r'^credentials/$', 'credentials', name="usercp_credentials"),
+    url(r'^username/$', 'username', name="usercp_username"),
+    url(r'^avatar/$', 'avatar', name="usercp_avatar"),
+    url(r'^signature/$', 'signature', name="usercp_signature"),
+    url(r'^ignored/$', 'ignored', name="usercp_ignored"),
+)

+ 9 - 9
misago/users/views/usercp.py → misago/usercp/views.py

@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
 from misago.forms import FormLayout
 from misago.messages import Message
 from misago.security.decorators import *
-from misago.users.forms.usercp import UserForumOptionsForm
+from misago.usercp.forms import UserForumOptionsForm
 
 
 @block_guest   
@@ -28,7 +28,7 @@ def options(request):
                                                              'timezone': request.user.timezone,
                                                              })
     
-    return request.theme.render_to_response('users/usercp/options.html',
+    return request.theme.render_to_response('usercp/options.html',
                                             {
                                              'message': message,
                                              'tab': 'options',
@@ -39,7 +39,7 @@ def options(request):
  
 @block_guest
 def credentials(request):
-    return request.theme.render_to_response('users/usercp/credentials.html',
+    return request.theme.render_to_response('usercp/credentials.html',
                                             {
                                              'tab': 'credentials',
                                              },
@@ -48,7 +48,7 @@ def credentials(request):
  
 @block_guest
 def username(request):
-    return request.theme.render_to_response('users/usercp/username.html',
+    return request.theme.render_to_response('usercp/username.html',
                                             {
                                              'tab': 'username',
                                              },
@@ -59,11 +59,11 @@ def username(request):
 def avatar(request):
     # Intercept all requests if we cant use avatar
     if request.user.avatar_ban:
-        return request.theme.render_to_response('users/usercp/avatar_banned.html',
+        return request.theme.render_to_response('usercp/avatar_banned.html',
                                                 {'tab': 'avatar'},
                                                 context_instance=RequestContext(request));
                                                    
-    return request.theme.render_to_response('users/usercp/avatar.html',
+    return request.theme.render_to_response('usercp/avatar.html',
                                             {
                                              'tab': 'avatar',
                                              },
@@ -74,11 +74,11 @@ def avatar(request):
 def signature(request):
     # Intercept all requests if we cant use signature
     if request.user.avatar_ban:
-        return request.theme.render_to_response('users/usercp/signature_banned.html',
+        return request.theme.render_to_response('usercp/signature_banned.html',
                                                 {'tab': 'signature'},
                                                 context_instance=RequestContext(request));
                                                 
-    return request.theme.render_to_response('users/usercp/signature.html',
+    return request.theme.render_to_response('usercp/signature.html',
                                             {
                                              'tab': 'signature',
                                              },
@@ -87,7 +87,7 @@ def signature(request):
  
 @block_guest
 def ignored(request):
-    return request.theme.render_to_response('users/usercp/ignored.html',
+    return request.theme.render_to_response('usercp/ignored.html',
                                             {
                                              'tab': 'ignored',
                                              },

+ 1 - 167
misago/users/fixtures.py

@@ -1,7 +1,4 @@
 from misago.monitor.fixtures import load_monitor_fixture
-from misago.settings.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils import ugettext_lazy as _
-from misago.utils import get_msgid
 
 monitor_fixtures = {
                   'users': 0,
@@ -12,169 +9,6 @@ monitor_fixtures = {
                   'last_user_slug': None,
                   }
 
-settings_fixtures = (
-    # Avatars Settings
-    ('avatars', {
-         'name': _("Users Avatars Settings"),
-         'description': _("Those settings allow you to control your users avatars."),
-         'settings': (
-            ('avatars_types', {
-                'value':        ['gravatar', 'gallery'],
-                'type':         "array",
-                'input':        "mlist",
-                'extra':        {'choices': [('gravatar', _("Gravatar")), ('upload', _("Uploaded Avatar")), ('gallery', _("Avatars Gallery"))]},
-                'separator':    _("General Settings"),
-                'name':         _("Allowed Avatars"),
-                'description':  _("Select Avatar types allowed on your forum."),
-                'position':     0,
-            }),
-            ('default_avatar', {
-                'value':        "gravatar",
-                'type':         "string",
-                'input':        "select",
-                'extra':        {'choices': [('gravatar', _("Gravatar")), ('gallery', _("Random Avatar from Gallery"))]},
-                'name':         _("Default Avatar"),
-                'description':  _("Default Avatar assigned to new members. If you creade directory and name it \"_default\", forum will select random avatar from that directory instead of regular gallery. If no avatar can be picked from gallery, Gravatar will be used."),
-                'position':     1,
-            }),
-            ('upload_limit', {
-                'value':        128,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0},
-                'separator':    _("Avatar Upload Settings"),
-                'name':         _("Maxmimum size of uploaded file"),
-                'description':  _("Select maximum allowed file size (in KB) for Avatar uploads."),
-                'position':     2,
-            }),
-       ),
-    }),
-    # Register and Sign-In Settings
-    ('register-and-signin', {
-        'name': _("Register and Sign-In Settings"),
-        'description': _("Those settings allow you to increase security of your members accounts."),
-        'settings': (
-            ('account_activation', {
-                'type':         "string",
-                'input':        "choice",
-                'extra':        {'choices': [('', _("No validation required")), ('user', _("Activation Token sent to User")), ('admin', _("Activation by Administrator")), ('block', _("Dont allow new registrations"))]},
-                'separator':    _("Users Registrations"),
-                'name':         _("New accounts validation"),
-                'position':     0,
-            }),
-            ('default_timezone', {
-                'value':        "utc",
-                'type':         "string",
-                'input':        "select",
-                'extra':        {'choices': '#TZ#'},
-                'name':         _("Default Timezone"),
-                'description':  _("Used by guests, crawlers and newly registered users."),
-                'position':     1,
-            }),
-            ('password_length', {
-                'value':        4,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 1},
-                'separator':    _("Users Passwords"),
-                'name':         _("Minimum user password length"),
-                'position':     2,
-            }),
-            ('password_complexity', {
-                'value':        [],
-                'type':         "array",
-                'input':        "mlist",
-                'extra':        {'choices': [('case', _("Require mixed Case")), ('digits', _("Require digits")), ('special', _("Require special characters"))]},
-                'name':         _("Password Complexity"),
-                'position':     3,
-            }),
-            ('password_lifetime', {
-                'value':        0,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0},
-                'name':         _("Password Lifetime"),
-                'description':  _("Enter number of days since password was set to force member to change it with new one, or 0 to dont force your members to change their passwords."),
-                'position':     4,
-            }),
-            ('password_in_email', {
-                'value':        False,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Include User Password in Welcoming E-mail"),
-                'description':  _("If you want to, Misago can include new user password in welcoming e-mail that is sent to new users after successful account creation."),
-                'position':     5,
-            }),
-            ('sessions_validate_ip', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Check IP on session authorization"),
-                'description':  _("Makes sessions more secure, but can cause problems with proxies and VPN's."),
-                'position':     6,
-            }),
-            ('remember_me_allow', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'separator':    _("Sign-In Settings"),
-                'name':         _('Enable "Remember Me" functionality'),
-                'description':  _("Turning this option on allows users to sign in on to your board using cookie-based tokens. This may result in account compromisation when user fails to sign out on shared computer."),
-                'position':     7,
-            }),
-            ('remember_me_lifetime', {
-                'value':        90,
-                'type':         "integer",
-                'input':        "text",
-                'name':         _('"Remember Me" token lifetime'),
-                'description':  _('Number of days since either last use or creation of "Remember Me" token to its expiration.'),
-                'position':     8,
-            }),
-            ('remember_me_extensible', {
-                'value':        1,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _('Allow "Remember Me" tokens refreshing'),
-                'description':  _('Set this setting to off if you want to force your users to periodically update their "Remember Me" tokens by signing in. If this option is on, Tokens are updated when they are used to open new session.'),
-                'position':     9,
-            }),
-            ('login_attempts_limit', {
-                'value':        3,
-                'default':      3,
-                'type':         "integer",
-                'input':        "text",
-                'separator':    _("Brute-Force Countermeasures"),
-                'name':         _("Limit Sign In attempts"),
-                'description':  _('Enter maximal number of allowed Sign In attempts before IP address "jams".'),
-                'position':     10,
-            }),
-            ('registrations_jams', {
-                'value':        1,
-                'default':      1,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Count failed register attempts too"),
-                'description':  _("Set this setting to yes if you want failed register attempts to count into limit."),
-                'position':     11,
-            }),
-            ('jams_lifetime', {
-                'value':        15,
-                'default':      15,
-                'type':         "integer",
-                'input':        "text",
-                'name':         _("Automaticaly unlock jammed IPs"),
-                'description':  _('Enter number of minutes since IP address "jams" to automatically unlock it, or 0 to never unlock jammed IP adresses. Jams dont count as bans.'),
-                'position':     12,
-            }),
-        ),
-    }),
-)
-
 
 def load_fixtures():
-    load_monitor_fixture(monitor_fixtures)
-    load_settings_fixture(settings_fixtures)
-    
-    
-def update_fixtures():
-    update_settings_fixture(settings_fixtures)
+    load_monitor_fixture(monitor_fixtures)

+ 0 - 0
misago/users/forms/admin.py → misago/users/forms.py


+ 0 - 11
misago/users/models.py

@@ -434,14 +434,3 @@ class Crawler(object):
     def is_crawler(self):
         return True
 
-
-class Pruning(models.Model):
-    """
-    Pruning policy
-    """
-    name = models.CharField(max_length=255)
-    email = models.CharField(max_length=255,null=True,blank=True)
-    posts = models.PositiveIntegerField(default=0)
-    registered = models.PositiveIntegerField(default=0)
-    last_visit = models.PositiveIntegerField(default=0)
-    

+ 0 - 22
misago/users/urls.py

@@ -1,22 +0,0 @@
-from django.conf.urls import patterns, url, include
-
-urlpatterns = patterns('misago.users.views',
-    url(r'^register/$', 'register.form', name="register"),
-    url(r'^activate/(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'activation.activate', name="activate"),
-    url(r'^resend-activation/$', 'activation.form', name="send_activation"),
-    url(r'^reset-pass/$', 'password.form', name="forgot_password"),
-    url(r'^reset-pass/(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-z0-9]+)/$', 'password.reset', name="reset_password"),
-    url(r'^users/$', 'list.list', name="users"),
-    url(r'^users/(?P<username>\w+)-(?P<user>\d+)/$', 'profiles.profile', name="user"),
-    url(r'^users/(?P<username>\w+)-(?P<user>\d+)/threads/$', 'profiles.profile', name="user_threads", kwargs={'tab': 'threads'}),
-    url(r'^users/(?P<username>\w+)-(?P<user>\d+)/following/$', 'profiles.profile', name="user_following", kwargs={'tab': 'following'}),
-    url(r'^users/(?P<username>\w+)-(?P<user>\d+)/followiers/$', 'profiles.profile', name="user_followers", kwargs={'tab': 'followers'}),
-    url(r'^users/(?P<username>\w+)-(?P<user>\d+)/details/$', 'profiles.profile', name="user_details", kwargs={'tab': 'details'}),
-    url(r'^users/(?P<rank_slug>(\w|-)+)/$', 'list.list', name="users"),
-    url(r'^usercp/$', 'usercp.options', name="usercp"),
-    url(r'^usercp/credentials$', 'usercp.credentials', name="usercp_credentials"),
-    url(r'^usercp/username$', 'usercp.username', name="usercp_username"),
-    url(r'^usercp/avatar$', 'usercp.avatar', name="usercp_avatar"),
-    url(r'^usercp/signature$', 'usercp.signature', name="usercp_signature"),
-    url(r'^usercp/ignored$', 'usercp.ignored', name="usercp_ignored"),
-)

+ 332 - 24
misago/users/views.py

@@ -1,25 +1,333 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
+from django.core.urlresolvers import reverse as django_reverse
+from django.db.models import Q
+from django.utils.translation import ugettext as _
+from misago.admin import site
+from misago.admin.widgets import *
+from misago.security import get_random_string
+from misago.users.forms import UserForm, NewUserForm, SearchUsersForm
 from misago.users.models import User
-from misago.views import error403, error404
-
-
-def users(request):
-    pass
-
-
-def user_profile(request, user, username):
-    user = int(user)
-    try:
-        user = User.objects.get(pk=user)
-        if user.username_slug != username:
-            # Force crawlers to take notice of updated username
-            return redirect(reverse('user', args=(user.username_slug, user.pk)), permanent=True)
-        return request.theme.render_to_response('users/profile.html',
-                                            {
-                                             'profile': user,
-                                            },
-                                            context_instance=RequestContext(request));
-    except User.DoesNotExist:
-        return error404(request)
+
+def reverse(route, target=None):
+    if target:
+        return django_reverse(route, kwargs={'target': target.pk, 'slug': target.username_slug})
+    return django_reverse(route)
+
+"""
+Views
+"""
+class List(ListWidget):
+    admin = site.get_action('users')
+    id = 'list'
+    columns=(
+             ('username_slug', _("User Name"), 35),
+             ('join_date', _("Join Date")),
+             )
+    default_sorting = 'username'
+    sortables={
+               'username_slug': 1,
+               'join_date': 0,
+              }
+    pagination = 25
+    search_form = SearchUsersForm
+    nothing_checked_message = _('You have to check at least one user.')
+    actions=(
+             ('activate', _("Activate users"), _("Are you sure you want to activate selected members?")),
+             ('deactivate', _("Request e-mail validation"), _("Are you sure you want to deactivate selected members and request them to revalidate their e-mail addresses?")),
+             ('remove_av', _("Remove and lock avatars"), _("Are you sure you want to remove selected members avatars and their ability to change them?")),
+             ('remove_sig', _("Remove and lock signatures"), _("Are you sure you want to remove selected members signatures and their ability to edit them?")),
+             ('remove_locks', _("Remove locks from avatars and signatures"), _("Are you sure you want to remove locks from selected members avatars and signatures?")),
+             ('reset', _("Reset passwords"), _("Are you sure you want to reset selected members passwords?")),
+             ('delete', _("Delete users"), _("Are you sure you want to delete selected users?")),
+             )
+    
+    def set_filters(self, model, filters):
+        if 'role' in filters:
+            model = model.filter(roles__in=filters['role']).distinct()
+        if 'rank' in filters:
+            model = model.filter(rank__in=filters['rank'])
+        if 'username' in filters:
+            if ',' in filters['username']:
+                qs = None
+                for name in filters['username'].split(','):
+                    name = name.strip().lower()
+                    if name:
+                        if qs:
+                            qs = qs | Q(username_slug__contains=name)
+                        else:
+                            qs = Q(username_slug__contains=name)
+                if qs:
+                    model = model.filter(qs)
+            else:
+                model = model.filter(username_slug__contains=filters['username'])
+        if 'email' in filters:
+            if ',' in filters['email']:
+                qs = None
+                for name in filters['email'].split(','):
+                    name = name.strip().lower()
+                    if name:
+                        if qs:
+                            qs = qs | Q(email__contains=name)
+                        else:
+                            qs = Q(email__contains=name)
+                if qs:
+                    model = model.filter(qs)
+            else:
+                model = model.filter(email__contains=filters['email'])
+        if 'activation' in filters:
+            model = model.filter(activation__in=filters['activation'])
+        return model
+    
+    def prefetch_related(self, items):
+        return items.prefetch_related('roles')
+    
+    def get_item_actions(self, request, item):
+        return (
+                self.action('pencil', _("Edit User Details"), reverse('admin_users_edit', item)),
+                self.action('remove', _("Delete User"), reverse('admin_users_delete', item), post=True, prompt=_("Are you sure you want to delete this user account?")),
+                )
+    
+    def action_activate(self, request, items, checked):
+        for user in items:
+            if unicode(user.pk) in checked and user.activation > 0:
+                request.monitor['users_inactive'] = int(request.monitor['users_inactive']) - 1
+                user.activation = user.ACTIVATION_NONE
+                user.save(force_update=True)
+                user.email_user(
+                                request,
+                                'users/activation/admin_done',
+                                _("Your Account has been activated"),
+                                )
+                
+        return Message(_('Selected users accounts have been activated.'), 'success'), reverse('admin_users')
+    
+    def action_deactivate(self, request, items, checked):
+        # First loop - check for errors
+        for user in items:
+            if unicode(user.pk) in checked:
+                if user.is_protected() and not request.user.is_god():
+                    return Message(_('You cannot force validation of protected members e-mails.'), 'error'), reverse('admin_users')
+                
+        # Second loop - reset passwords
+        for user in items:
+            if unicode(user.pk) in checked:
+                user.activation = user.ACTIVATION_USER
+                user.token = token = get_random_string(12)
+                user.save(force_update=True)
+                user.email_user(
+                                request,
+                                'users/activation/invalidated',
+                                _("Account Activation"),
+                                )
+                
+        return Message(_('Selected users accounts have been deactivated and new activation links have been sent to them.'), 'success'), reverse('admin_users')
+
+    def action_remove_av(self, request, items, checked):
+        # First loop - check for errors
+        for user in items:
+            if unicode(user.pk) in checked:
+                if user.is_protected() and not request.user.is_god():
+                    return Message(_('You cannot remove and block protected members avatars.'), 'error'), reverse('admin_users')
+                
+        # Second loop - reset passwords
+        for user in items:
+            if unicode(user.pk) in checked:
+                user.lock_avatar()
+                user.save(force_update=True)
+                
+        return Message(_('Selected users avatars were deleted and locked.'), 'success'), reverse('admin_users')
+
+    def action_remove_sig(self, request, items, checked):
+        # First loop - check for errors
+        for user in items:
+            if unicode(user.pk) in checked:
+                if user.is_protected() and not request.user.is_god():
+                    return Message(_('You cannot remove and block protected members signatures.'), 'error'), reverse('admin_users')
+                
+        # Second loop - reset passwords
+        for user in items:
+            if unicode(user.pk) in checked:
+                user.signature_ban = True
+                user.signature = ''
+                user.signature_preparsed = ''
+                user.save(force_update=True)
+                
+        return Message(_('Selected users signatures were deleted and locked.'), 'success'), reverse('admin_users')
+   
+    def action_remove_locks(self, request, items, checked):
+        for user in items:
+            if unicode(user.pk) in checked:
+                user.default_avatar(request.settings)
+                user.avatar_ban = False
+                user.signature_ban = False
+                user.save(force_update=True)
+                
+        return Message(_('Selected users can now edit their avatars and signatures.'), 'success'), reverse('admin_users')
+    
+    def action_reset(self, request, items, checked):
+        # First loop - check for errors
+        for user in items:
+            if unicode(user.pk) in checked:
+                if user.is_protected() and not request.user.is_god():
+                    return Message(_('You cannot reset protected members passwords.'), 'error'), reverse('admin_users')
+                
+        # Second loop - reset passwords
+        for user in items:
+            if unicode(user.pk) in checked:
+                new_password = get_random_string(8)
+                user.set_password(new_password)
+                user.save(force_update=True)
+                user.email_user(
+                                request,
+                                'users/password/new_admin',
+                                _("Your New Password"),
+                                {
+                                 'password': new_password,
+                                 },
+                                )
+                
+        return Message(_('Selected users passwords have been reset successfully.'), 'success'), reverse('admin_users')
+
+    def action_delete(self, request, items, checked):
+        for user in items:
+            if unicode(user.pk) in checked:
+                if user.pk == request.user.id:
+                    return Message(_('You cannot delete yourself.'), 'error'), reverse('admin_users')
+                if user.is_protected():
+                    return Message(_('You cannot delete protected members.'), 'error'), reverse('admin_users')
+                
+        User.objects.filter(id__in=checked).delete()
+        User.objects.resync_monitor(request.monitor)
+        return Message(_('Selected users have been deleted successfully.'), 'success'), reverse('admin_users')
+    
+
+class New(FormWidget):
+    admin = site.get_action('users')
+    id = 'new'
+    fallback = 'admin_users' 
+    form = NewUserForm
+    submit_button = _("Save User")
+        
+    def get_new_url(self, request, model):
+        return reverse('admin_users')
+    
+    def get_edit_url(self, request, model):
+        return reverse('admin_users_edit', model)
+    
+    def submit_form(self, request, form, target):
+        new_user = User.objects.create_user(
+                                            form.cleaned_data['username'],
+                                            form.cleaned_data['email'],
+                                            form.cleaned_data['password'],
+                                            request.settings['default_timezone'],
+                                            request.META['REMOTE_ADDR'],
+                                            no_roles=True,
+                                            request=request,
+                                            )
+        new_user.title = form.cleaned_data['title']
+        new_user.rank = form.cleaned_data['rank']
+        
+        for role in form.cleaned_data['roles']:
+            new_user.roles.add(role)
+        new_user.save(force_update=True)
+        
+        return new_user, Message(_('New User has been created.'), 'success')
+    
+    
+class Edit(FormWidget):
+    admin = site.get_action('users')
+    id = 'edit'
+    name = _("Edit User")
+    fallback = 'admin_users'
+    form = UserForm
+    tabbed = True
+    target_name = 'username'
+    notfound_message = _('Requested User could not be found.')
+    submit_fallback = True
+    
+    def get_form_instance(self, form, request, model, initial, post=False):
+        if post:
+            return form(model, request.POST, request=request, initial=self.get_initial_data(request, model))
+        return form(model, request=request, initial=self.get_initial_data(request, model))
+        
+    def get_url(self, request, model):
+        return reverse('admin_users_edit', model)
+    
+    def get_edit_url(self, request, model):
+        return self.get_url(request, model)
+    
+    def get_initial_data(self, request, model):
+        return {
+                'username': model.username,
+                'title': model.title,
+                'email': model.email,
+                'rank': model.rank,
+                'roles': model.roles.all(),
+                'avatar_ban': model.avatar_ban,
+                'avatar_ban_reason_user': model.avatar_ban_reason_user,
+                'avatar_ban_reason_admin': model.avatar_ban_reason_admin,
+                'signature': model.signature,
+                'signature_ban': model.signature_ban,
+                'signature_ban_reason_user': model.signature_ban_reason_user,
+                'signature_ban_reason_admin': model.signature_ban_reason_admin,
+                }
+    
+    def submit_form(self, request, form, target):
+        target.title = form.cleaned_data['title']
+        target.rank = form.cleaned_data['rank']
+        target.avatar_ban_reason_user = form.cleaned_data['avatar_ban_reason_user']
+        target.avatar_ban_reason_admin = form.cleaned_data['avatar_ban_reason_admin']
+        target.set_signature(form.cleaned_data['signature'])
+        target.signature_ban = form.cleaned_data['signature_ban']
+        target.signature_ban_reason_user = form.cleaned_data['signature_ban_reason_user']
+        target.signature_ban_reason_admin = form.cleaned_data['signature_ban_reason_admin']
+        
+        # Do avatar ban mumbo-jumbo
+        if target.avatar_ban != form.cleaned_data['avatar_ban']:
+            if form.cleaned_data['avatar_ban']:
+                target.lock_avatar()
+            else:
+                target.default_avatar(request.settings)
+        target.avatar_ban = form.cleaned_data['avatar_ban']
+               
+        # Set custom avatar
+        if form.cleaned_data['avatar_custom']:
+            target.delete_avatar()
+            target.avatar_image = form.cleaned_data['avatar_custom']
+            target.avatar_type = 'gallery'
+        
+        # Update user roles
+        if request.user.is_god():
+            target.roles.clear()
+            for role in form.cleaned_data['roles']:
+                target.roles.add(role)
+        else:
+            for role in target.roles.all():
+                if not role.protected:
+                    target.roles.remove(role)
+            for role in form.cleaned_data['roles']:
+                target.roles.add(role)
+        
+        target.save(force_update=True)
+        return target, Message(_('Changes in user\'s "%(name)s" account have been saved.') % {'name': self.original_name}, 'success')
+
+
+class Delete(ButtonWidget):
+    admin = site.get_action('users')
+    id = 'delete'
+    fallback = 'admin_users'
+    notfound_message = _('Requested User account could not be found.')
+    
+    def action(self, request, target):
+        if target.pk == request.user.id:
+            return Message(_('You cannot delete yourself.'), 'error'), False
+        if target.is_protected():
+            return Message(_('You cannot delete protected member.'), 'error'), False
+        target.delete()
+        User.objects.resync_monitor(request.monitor)
+        return Message(_('User "%(name)s" has been deleted.') % {'name': target.username}, 'success'), False
+    
+
+def inactive(request):
+    token = 'list_filter_misago.users.models.User'
+    request.session[token] = {'activation': ['1', '2', '3']}
+    return redirect(reverse('admin_users'))

+ 0 - 333
misago/users/views/admin.py

@@ -1,333 +0,0 @@
-from django.core.urlresolvers import reverse as django_reverse
-from django.db.models import Q
-from django.utils.translation import ugettext as _
-from misago.admin import site
-from misago.admin.widgets import *
-from misago.security import get_random_string
-from misago.users.forms.admin import UserForm, NewUserForm, SearchUsersForm
-from misago.users.models import User
-
-def reverse(route, target=None):
-    if target:
-        return django_reverse(route, kwargs={'target': target.pk, 'slug': target.username_slug})
-    return django_reverse(route)
-
-"""
-Views
-"""
-class List(ListWidget):
-    admin = site.get_action('users')
-    id = 'list'
-    columns=(
-             ('username_slug', _("User Name"), 35),
-             ('join_date', _("Join Date")),
-             )
-    default_sorting = 'username'
-    sortables={
-               'username_slug': 1,
-               'join_date': 0,
-              }
-    pagination = 25
-    search_form = SearchUsersForm
-    nothing_checked_message = _('You have to check at least one user.')
-    actions=(
-             ('activate', _("Activate users"), _("Are you sure you want to activate selected members?")),
-             ('deactivate', _("Request e-mail validation"), _("Are you sure you want to deactivate selected members and request them to revalidate their e-mail addresses?")),
-             ('remove_av', _("Remove and lock avatars"), _("Are you sure you want to remove selected members avatars and their ability to change them?")),
-             ('remove_sig', _("Remove and lock signatures"), _("Are you sure you want to remove selected members signatures and their ability to edit them?")),
-             ('remove_locks', _("Remove locks from avatars and signatures"), _("Are you sure you want to remove locks from selected members avatars and signatures?")),
-             ('reset', _("Reset passwords"), _("Are you sure you want to reset selected members passwords?")),
-             ('delete', _("Delete users"), _("Are you sure you want to delete selected users?")),
-             )
-    
-    def set_filters(self, model, filters):
-        if 'role' in filters:
-            model = model.filter(roles__in=filters['role']).distinct()
-        if 'rank' in filters:
-            model = model.filter(rank__in=filters['rank'])
-        if 'username' in filters:
-            if ',' in filters['username']:
-                qs = None
-                for name in filters['username'].split(','):
-                    name = name.strip().lower()
-                    if name:
-                        if qs:
-                            qs = qs | Q(username_slug__contains=name)
-                        else:
-                            qs = Q(username_slug__contains=name)
-                if qs:
-                    model = model.filter(qs)
-            else:
-                model = model.filter(username_slug__contains=filters['username'])
-        if 'email' in filters:
-            if ',' in filters['email']:
-                qs = None
-                for name in filters['email'].split(','):
-                    name = name.strip().lower()
-                    if name:
-                        if qs:
-                            qs = qs | Q(email__contains=name)
-                        else:
-                            qs = Q(email__contains=name)
-                if qs:
-                    model = model.filter(qs)
-            else:
-                model = model.filter(email__contains=filters['email'])
-        if 'activation' in filters:
-            model = model.filter(activation__in=filters['activation'])
-        return model
-    
-    def prefetch_related(self, items):
-        return items.prefetch_related('roles')
-    
-    def get_item_actions(self, request, item):
-        return (
-                self.action('pencil', _("Edit User Details"), reverse('admin_users_edit', item)),
-                self.action('remove', _("Delete User"), reverse('admin_users_delete', item), post=True, prompt=_("Are you sure you want to delete this user account?")),
-                )
-    
-    def action_activate(self, request, items, checked):
-        for user in items:
-            if unicode(user.pk) in checked and user.activation > 0:
-                request.monitor['users_inactive'] = int(request.monitor['users_inactive']) - 1
-                user.activation = user.ACTIVATION_NONE
-                user.save(force_update=True)
-                user.email_user(
-                                request,
-                                'users/activation/admin_done',
-                                _("Your Account has been activated"),
-                                )
-                
-        return Message(_('Selected users accounts have been activated.'), 'success'), reverse('admin_users')
-    
-    def action_deactivate(self, request, items, checked):
-        # First loop - check for errors
-        for user in items:
-            if unicode(user.pk) in checked:
-                if user.is_protected() and not request.user.is_god():
-                    return Message(_('You cannot force validation of protected members e-mails.'), 'error'), reverse('admin_users')
-                
-        # Second loop - reset passwords
-        for user in items:
-            if unicode(user.pk) in checked:
-                user.activation = user.ACTIVATION_USER
-                user.token = token = get_random_string(12)
-                user.save(force_update=True)
-                user.email_user(
-                                request,
-                                'users/activation/invalidated',
-                                _("Account Activation"),
-                                )
-                
-        return Message(_('Selected users accounts have been deactivated and new activation links have been sent to them.'), 'success'), reverse('admin_users')
-
-    def action_remove_av(self, request, items, checked):
-        # First loop - check for errors
-        for user in items:
-            if unicode(user.pk) in checked:
-                if user.is_protected() and not request.user.is_god():
-                    return Message(_('You cannot remove and block protected members avatars.'), 'error'), reverse('admin_users')
-                
-        # Second loop - reset passwords
-        for user in items:
-            if unicode(user.pk) in checked:
-                user.lock_avatar()
-                user.save(force_update=True)
-                
-        return Message(_('Selected users avatars were deleted and locked.'), 'success'), reverse('admin_users')
-
-    def action_remove_sig(self, request, items, checked):
-        # First loop - check for errors
-        for user in items:
-            if unicode(user.pk) in checked:
-                if user.is_protected() and not request.user.is_god():
-                    return Message(_('You cannot remove and block protected members signatures.'), 'error'), reverse('admin_users')
-                
-        # Second loop - reset passwords
-        for user in items:
-            if unicode(user.pk) in checked:
-                user.signature_ban = True
-                user.signature = ''
-                user.signature_preparsed = ''
-                user.save(force_update=True)
-                
-        return Message(_('Selected users signatures were deleted and locked.'), 'success'), reverse('admin_users')
-   
-    def action_remove_locks(self, request, items, checked):
-        for user in items:
-            if unicode(user.pk) in checked:
-                user.default_avatar(request.settings)
-                user.avatar_ban = False
-                user.signature_ban = False
-                user.save(force_update=True)
-                
-        return Message(_('Selected users can now edit their avatars and signatures.'), 'success'), reverse('admin_users')
-    
-    def action_reset(self, request, items, checked):
-        # First loop - check for errors
-        for user in items:
-            if unicode(user.pk) in checked:
-                if user.is_protected() and not request.user.is_god():
-                    return Message(_('You cannot reset protected members passwords.'), 'error'), reverse('admin_users')
-                
-        # Second loop - reset passwords
-        for user in items:
-            if unicode(user.pk) in checked:
-                new_password = get_random_string(8)
-                user.set_password(new_password)
-                user.save(force_update=True)
-                user.email_user(
-                                request,
-                                'users/password/new_admin',
-                                _("Your New Password"),
-                                {
-                                 'password': new_password,
-                                 },
-                                )
-                
-        return Message(_('Selected users passwords have been reset successfully.'), 'success'), reverse('admin_users')
-
-    def action_delete(self, request, items, checked):
-        for user in items:
-            if unicode(user.pk) in checked:
-                if user.pk == request.user.id:
-                    return Message(_('You cannot delete yourself.'), 'error'), reverse('admin_users')
-                if user.is_protected():
-                    return Message(_('You cannot delete protected members.'), 'error'), reverse('admin_users')
-                
-        User.objects.filter(id__in=checked).delete()
-        User.objects.resync_monitor(request.monitor)
-        return Message(_('Selected users have been deleted successfully.'), 'success'), reverse('admin_users')
-    
-
-class New(FormWidget):
-    admin = site.get_action('users')
-    id = 'new'
-    fallback = 'admin_users' 
-    form = NewUserForm
-    submit_button = _("Save User")
-        
-    def get_new_url(self, request, model):
-        return reverse('admin_users')
-    
-    def get_edit_url(self, request, model):
-        return reverse('admin_users_edit', model)
-    
-    def submit_form(self, request, form, target):
-        new_user = User.objects.create_user(
-                                            form.cleaned_data['username'],
-                                            form.cleaned_data['email'],
-                                            form.cleaned_data['password'],
-                                            request.settings['default_timezone'],
-                                            request.META['REMOTE_ADDR'],
-                                            no_roles=True,
-                                            request=request,
-                                            )
-        new_user.title = form.cleaned_data['title']
-        new_user.rank = form.cleaned_data['rank']
-        
-        for role in form.cleaned_data['roles']:
-            new_user.roles.add(role)
-        new_user.save(force_update=True)
-        
-        return new_user, Message(_('New User has been created.'), 'success')
-    
-    
-class Edit(FormWidget):
-    admin = site.get_action('users')
-    id = 'edit'
-    name = _("Edit User")
-    fallback = 'admin_users'
-    form = UserForm
-    tabbed = True
-    target_name = 'username'
-    notfound_message = _('Requested User could not be found.')
-    submit_fallback = True
-    
-    def get_form_instance(self, form, request, model, initial, post=False):
-        if post:
-            return form(model, request.POST, request=request, initial=self.get_initial_data(request, model))
-        return form(model, request=request, initial=self.get_initial_data(request, model))
-        
-    def get_url(self, request, model):
-        return reverse('admin_users_edit', model)
-    
-    def get_edit_url(self, request, model):
-        return self.get_url(request, model)
-    
-    def get_initial_data(self, request, model):
-        return {
-                'username': model.username,
-                'title': model.title,
-                'email': model.email,
-                'rank': model.rank,
-                'roles': model.roles.all(),
-                'avatar_ban': model.avatar_ban,
-                'avatar_ban_reason_user': model.avatar_ban_reason_user,
-                'avatar_ban_reason_admin': model.avatar_ban_reason_admin,
-                'signature': model.signature,
-                'signature_ban': model.signature_ban,
-                'signature_ban_reason_user': model.signature_ban_reason_user,
-                'signature_ban_reason_admin': model.signature_ban_reason_admin,
-                }
-    
-    def submit_form(self, request, form, target):
-        target.title = form.cleaned_data['title']
-        target.rank = form.cleaned_data['rank']
-        target.avatar_ban_reason_user = form.cleaned_data['avatar_ban_reason_user']
-        target.avatar_ban_reason_admin = form.cleaned_data['avatar_ban_reason_admin']
-        target.set_signature(form.cleaned_data['signature'])
-        target.signature_ban = form.cleaned_data['signature_ban']
-        target.signature_ban_reason_user = form.cleaned_data['signature_ban_reason_user']
-        target.signature_ban_reason_admin = form.cleaned_data['signature_ban_reason_admin']
-        
-        # Do avatar ban mumbo-jumbo
-        if target.avatar_ban != form.cleaned_data['avatar_ban']:
-            if form.cleaned_data['avatar_ban']:
-                target.lock_avatar()
-            else:
-                target.default_avatar(request.settings)
-        target.avatar_ban = form.cleaned_data['avatar_ban']
-               
-        # Set custom avatar
-        if form.cleaned_data['avatar_custom']:
-            target.delete_avatar()
-            target.avatar_image = form.cleaned_data['avatar_custom']
-            target.avatar_type = 'gallery'
-        
-        # Update user roles
-        if request.user.is_god():
-            target.roles.clear()
-            for role in form.cleaned_data['roles']:
-                target.roles.add(role)
-        else:
-            for role in target.roles.all():
-                if not role.protected:
-                    target.roles.remove(role)
-            for role in form.cleaned_data['roles']:
-                target.roles.add(role)
-        
-        target.save(force_update=True)
-        return target, Message(_('Changes in user\'s "%(name)s" account have been saved.') % {'name': self.original_name}, 'success')
-
-
-class Delete(ButtonWidget):
-    admin = site.get_action('users')
-    id = 'delete'
-    fallback = 'admin_users'
-    notfound_message = _('Requested User account could not be found.')
-    
-    def action(self, request, target):
-        if target.pk == request.user.id:
-            return Message(_('You cannot delete yourself.'), 'error'), False
-        if target.is_protected():
-            return Message(_('You cannot delete protected member.'), 'error'), False
-        target.delete()
-        User.objects.resync_monitor(request.monitor)
-        return Message(_('User "%(name)s" has been deleted.') % {'name': target.username}, 'success'), False
-    
-
-def inactive(request):
-    token = 'list_filter_misago.users.models.User'
-    request.session[token] = {'activation': ['1', '2', '3']}
-    return redirect(reverse('admin_users'))

+ 0 - 62
misago/users/views/profiles.py

@@ -1,62 +0,0 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from misago.users.models import User
-from misago.views import error404
-
-
-def profile(request, user, username, tab='posts'):
-    user = int(user)
-    try:
-        user = User.objects.get(pk=user)
-        if user.username_slug != username:
-            # Force crawlers to take notice of updated username
-            return redirect(reverse('user', args=(user.username_slug, user.pk)), permanent=True)
-        return globals()['profile_%s' % tab](request, user)
-    except User.DoesNotExist:
-        return error404(request)
-    
-
-def profile_posts(request, user):
-    return request.theme.render_to_response('users/profile/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'posts',
-                                            },
-                                            context_instance=RequestContext(request));
-    
-
-def profile_threads(request, user):
-    return request.theme.render_to_response('users/profile/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'threads',
-                                            },
-                                            context_instance=RequestContext(request));
-    
-
-def profile_following(request, user):
-    return request.theme.render_to_response('users/profile/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'following',
-                                            },
-                                            context_instance=RequestContext(request));
-    
-
-def profile_followers(request, user):
-    return request.theme.render_to_response('users/profile/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'followers',
-                                            },
-                                            context_instance=RequestContext(request));
-    
-
-def profile_details(request, user):
-    return request.theme.render_to_response('users/profile/details.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'details',
-                                            },
-                                            context_instance=RequestContext(request));

+ 0 - 0
templates/admin/users/prunings/list.html → templates/admin/policies/list.html


+ 0 - 0
templates/admin/users/users/list.html → templates/admin/users/list.html


+ 1 - 1
templates/sora/users/profile/details.html → templates/sora/profiles/details.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/profile/profile.html" %}
+{% extends "sora/profiles/profile.html" %}
 {% load i18n %}
 {% load humanize %}
 {% load url from future %}

+ 0 - 0
templates/sora/users/list.html → templates/sora/profiles/list.html


+ 0 - 0
templates/sora/users/profile/profile.html → templates/sora/profiles/profile.html


+ 0 - 0
templates/sora/users/register.html → templates/sora/register.html


+ 0 - 0
templates/sora/users/resend_activation.html → templates/sora/resend_activation.html


+ 0 - 0
templates/sora/users/forgot_password.html → templates/sora/reset_password.html


+ 1 - 1
templates/sora/users/usercp/avatar.html → templates/sora/usercp/avatar.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}

+ 1 - 1
templates/sora/users/usercp/avatar_banned.html → templates/sora/usercp/avatar_banned.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}

+ 1 - 1
templates/sora/users/usercp/credentials.html → templates/sora/usercp/credentials.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}

+ 1 - 1
templates/sora/users/usercp/ignored.html → templates/sora/usercp/ignored.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}

+ 0 - 0
templates/sora/users/usercp/usercp.html → templates/sora/usercp/layout.html


+ 1 - 1
templates/sora/users/usercp/options.html → templates/sora/usercp/options.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "_forms.html" as form_theme with context %}

+ 1 - 1
templates/sora/users/usercp/signature.html → templates/sora/usercp/signature.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}

+ 1 - 1
templates/sora/users/usercp/signature_banned.html → templates/sora/usercp/signature_banned.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}

+ 1 - 1
templates/sora/users/usercp/username.html → templates/sora/usercp/username.html

@@ -1,4 +1,4 @@
-{% extends "sora/users/usercp/usercp.html" %}
+{% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load url from future %}
 {% import "sora/macros.html" as macros with context %}