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

WIP UserCP: change forum options

Rafał Pitoń 11 лет назад
Родитель
Сommit
d960f645fc

+ 8 - 0
docs/developers/user_sites.rst

@@ -35,3 +35,11 @@ Default links for Misago sites are available in templates trough following varia
 * **USERCP_URL** - Default link to user control panel site.
 * **USERS_LIST_URL** - Default link to users lists site.
 * **USER_PROFILE_URL** - Default link to user profile site.
+
+
+Adding pages to User Control Panel
+==================================
+
+To add custom page to UserCP, register it on :py:object:`misago.users.sites.usercp` object using ``add_page`` method described above.
+
+After this, write your view(s), making sure they are using :py:object:`misago.users.views.usercp.render` function instead of :py:object:`django.shortcuts.render` to render response, and that your templates are extending ``misago/usercp/base.html`` template, defining custom html in ``page`` block instead of usual ``content``.

+ 3 - 1
misago/admin/testutils.py

@@ -11,5 +11,7 @@ def admin_login(client, username, password):
 class AdminTestCase(TestCase):
     def setUp(self):
         User = get_user_model()
-        User.objects.create_superuser('TestAdmin', 'admin@test.com', 'Pass.123')
+        self.test_admin = User.objects.create_superuser('TestAdmin',
+                                                        'admin@test.com',
+                                                        'Pass.123')
         admin_login(self.client, 'TestAdmin', 'Pass.123')

+ 30 - 2
misago/static/misago/css/misago/header.less

@@ -7,10 +7,38 @@
 //
 //##
 .page-header {
+  border-bottom-width: 1px;
+  margin: 0px;
   margin-top: @line-height-computed * -1;
+  margin-bottom: @line-height-computed;
+  padding: @line-height-computed 0px;
 
-  h1 {
-    font-size: @font-size-base * 2.3;
+  &>.container {
+    h1 {
+      margin: 0px;
+      padding: 0px;
+      overflow: visible;
+
+      font-size: @font-size-large * 1.5;
+
+      .main, .sub {
+        float: left;
+
+        font-weight: normal;
+      }
+
+      .main {
+        a {
+          color: @text-color;
+        }
+      }
+
+      .sub {
+        margin-left: @line-height-computed / 2;
+
+        color: lighten(@text-color, 25%);
+      }
+    }
   }
 }
 

+ 18 - 7
misago/static/misago/css/misago/navs.less

@@ -85,19 +85,30 @@
 /* Sidenav */
 .nav-side {
   li {
-    a {
-      &, &:link, &:visited {
+    .fa, .glyphicon {
+      margin-left: -8px;
+      width: 30px;
+
+      text-align: center;
+    }
+
+    &.title {
+      h3 {
         border-bottom: 1px solid @nav-side-border;
+        margin: 0px;
         padding: @nav-side-padding;
 
         color: @nav-side-color;
+        font-size: @font-size-large;
+      }
+    }
 
-        .fa, .glyphicon {
-          margin-left: -8px;
-          width: 30px;
+    a {
+      &, &:link, &:visited {
+        border-bottom: 1px solid @nav-side-border;
+        padding: @nav-side-padding;
 
-          text-align: center;
-        }
+        color: @nav-side-color;
       }
 
       &:hover {

+ 1 - 1
misago/templates/misago/navbar.html

@@ -15,7 +15,7 @@
     <!-- Collect the nav links, forms, and other content for toggling -->
     <div class="collapse navbar-collapse">
       {% if user.is_authenticated %}
-      {% include 'misago/user_nav.html' %}
+      {% include "misago/user_nav.html" %}
       {% else %}
       <div class="navbar-nav-guest navbar-right">
         <a href="{% url LOGIN_URL %}" class="btn btn-sign-in navbar-btn">{% trans "Sign in" %}</a>

+ 38 - 0
misago/templates/misago/usercp/base.html

@@ -0,0 +1,38 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}
+{{ active_page.name }} | {% trans "Change options" %} | {{ block.super }}
+{% endblock title %}
+
+
+{% block content %}
+<div class="container">
+  <div class="row">
+    <div class="col-md-3">
+
+      <ul class="nav nav-side">
+        <li class="title">
+          <h3>
+            <span class="fa fa-cog"></span>
+            {% trans "Change options" %}
+          </h3>
+        </li>
+        {% for page in pages %}
+        <li {% if page.is_active %}class="active"{% endif %}>
+          <a href="{% url page.link %}">
+            <span class="{{ page.icon }}"></span>
+            {{ page.name }}
+          </a>
+        </li>
+        {% endfor %}
+      </ul>
+
+    </div>
+    <div class="col-md-9">
+      {% block page %}{% endblock page %}
+    </div>
+  </div>
+</div>
+{% endblock content %}

+ 40 - 0
misago/templates/misago/usercp/change_email_password.html

@@ -0,0 +1,40 @@
+{% extends "misago/usercp/base.html" %}
+{% load i18n %}
+
+
+{% block page %}
+<div class="form-panel">
+  <form method="POST" role="form">
+    {% csrf_token %}
+
+    <div class="form-header">
+      <h2>
+        <span class="{{ active_page.icon }}"></span>
+        {{ active_page.name }}
+      </h2>
+    </div>
+
+    {% include "misago/form_errors.html" %}
+
+    <div class="form-body no-fieldsets">
+
+      <div class="form-group">
+        <div class="control-input">
+          <input type="text" name="username" class="form-control input-lg" placeholder="{% trans "Username or e-mail" %}" {% if form.username.value %}value="{{ form.username.value }}"{% endif %}>
+        </div>
+      </div>
+      <div class="form-group">
+        <div class="control-input">
+          <input type="password" name="password" class="form-control input-lg" placeholder="{% trans "Password" %}">
+        </div>
+      </div>
+
+    </div>
+
+    <div class="form-footer">
+      <button class="btn btn-primary btn-block">{% trans "Sign in" %}</button>
+    </div>
+
+  </form>
+</div>
+{% endblock page %}

+ 50 - 0
misago/templates/misago/usercp/change_forum_options.html

@@ -0,0 +1,50 @@
+{% extends "misago/usercp/base.html" %}
+{% load i18n misago_forms %}
+
+
+{% block page %}
+<div class="form-panel">
+  <form method="POST" role="form" class="form-horizontal">
+    {% csrf_token %}
+
+    <div class="form-header">
+      <h2>
+        <span class="{{ active_page.icon }}"></span>
+        {{ active_page.name }}
+      </h2>
+    </div>
+
+    {% include "misago/form_errors.html" %}
+
+    <div class="form-body">
+      {% with label_class="col-md-3" field_class="col-md-9" %}
+      <fieldset>
+        <legend>{% trans "Forum options" %}</legend>
+
+        {% form_row form.presence_visibility label_class field_class %}
+        {% form_row form.timezone label_class field_class %}
+
+      </fieldset>
+      <fieldset>
+        <legend>{% trans "Automatic subscriptions" %}</legend>
+
+        {% form_row form.subscribe_to_started_threads label_class field_class %}
+        {% form_row form.subscribe_to_replied_threads label_class field_class %}
+
+      </fieldset>
+      {% endwith %}
+    </div>
+
+    <div class="form-footer">
+      <div class="row">
+        <div class="col-md-9 col-md-offset-3">
+
+          <button class="btn btn-primary">{% trans "Save changes" %}</button>
+
+        </div>
+      </div>
+    </div>
+
+  </form>
+</div>
+{% endblock page %}

+ 40 - 0
misago/templates/misago/usercp/change_username.html

@@ -0,0 +1,40 @@
+{% extends "misago/usercp/base.html" %}
+{% load i18n %}
+
+
+{% block page %}
+<div class="form-panel">
+  <form method="POST" role="form">
+    {% csrf_token %}
+
+    <div class="form-header">
+      <h2>
+        <span class="{{ active_page.icon }}"></span>
+        {{ active_page.name }}
+      </h2>
+    </div>
+
+    {% include "misago/form_errors.html" %}
+
+    <div class="form-body no-fieldsets">
+
+      <div class="form-group">
+        <div class="control-input">
+          <input type="text" name="username" class="form-control input-lg" placeholder="{% trans "Username or e-mail" %}" {% if form.username.value %}value="{{ form.username.value }}"{% endif %}>
+        </div>
+      </div>
+      <div class="form-group">
+        <div class="control-input">
+          <input type="password" name="password" class="form-control input-lg" placeholder="{% trans "Password" %}">
+        </div>
+      </div>
+
+    </div>
+
+    <div class="form-footer">
+      <button class="btn btn-primary btn-block">{% trans "Sign in" %}</button>
+    </div>
+
+  </form>
+</div>
+{% endblock page %}

+ 9 - 3
misago/users/apps.py

@@ -14,9 +14,15 @@ class MisagoUsersConfig(AppConfig):
         self.register_default_user_profile_pages()
 
     def register_default_usercp_pages(self):
-        usercp.add_page(link='misago:index',
-                        name='Not',
-                        icon='fa fa-check')
+        usercp.add_page(link='misago:usercp_change_forum_options',
+                        name=_('Change forum options'),
+                        icon='fa fa-check-square-o')
+        usercp.add_page(link='misago:usercp_change_username',
+                        name=_('Change username'),
+                        icon='fa fa-credit-card')
+        usercp.add_page(link='misago:usercp_change_email_password',
+                        name=_('Change email or password'),
+                        icon='fa fa-ticket')
 
     def register_default_users_list_pages(self):
         users_list.add_page(link='misago:index',

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

@@ -0,0 +1,56 @@
+from django.contrib.auth import get_user_model
+from django.utils.translation import ugettext_lazy as _
+
+from misago.core import forms, timezones
+from misago.users.models import (PRESENCE_VISIBILITY_CHOICES,
+                                 AUTO_SUBSCRIBE_CHOICES)
+
+
+class ChangeForumOptionsBaseForm(forms.ModelForm):
+    timezone = forms.ChoiceField(
+        label=_("Your current timezone"), choices=[],
+        help_text=_("If dates and hours displayed by forums are inaccurate, "
+                    "you can fix it by adjusting timezone setting."))
+
+    presence_visibility = forms.TypedChoiceField(
+        label=_("Show my presence to"),
+        choices=PRESENCE_VISIBILITY_CHOICES, coerce=int,
+        help_text=_("If you want to, you can limit other members ability to "
+                    "track your presence on forums."))
+
+    subscribe_to_started_threads = forms.TypedChoiceField(
+        label=_("Threads I start"), coerce=int, choices=AUTO_SUBSCRIBE_CHOICES)
+
+    subscribe_to_replied_threads = forms.TypedChoiceField(
+        label=_("Threads I reply to"), coerce=int,
+        choices=AUTO_SUBSCRIBE_CHOICES)
+
+    class Meta:
+        model = get_user_model()
+        fields = ['timezone', 'presence_visibility',
+                  'subscribe_to_started_threads',
+                  'subscribe_to_replied_threads']
+
+
+def ChangeForumOptionsForm(*args, **kwargs):
+    timezone = forms.ChoiceField(
+        label=_("Your current timezone"), choices=timezones.choices(),
+        help_text=_("If dates and hours displayed by forums are inaccurate, "
+                    "you can fix it by adjusting timezone setting."))
+
+    FinalFormType = type('FinalChangeForumOptionsForm',
+                         (ChangeForumOptionsBaseForm,),
+                         {'timezone': timezone})
+    return FinalFormType(*args, **kwargs)
+
+
+class ChangeUsernameForm(forms.ModelForm):
+    class Meta:
+        model = get_user_model()
+        fields = ['username', 'email', 'title']
+
+
+class ChangeEmailPasswordForm(forms.ModelForm):
+    class Meta:
+        model = get_user_model()
+        fields = ['username', 'email', 'title']

+ 8 - 3
misago/users/models/user.py

@@ -14,6 +14,7 @@ from misago.conf import settings
 from misago.core.utils import slugify
 
 from misago.users.models.rank import Rank
+from misago.users.signals import username_changed
 from misago.users.utils import hash_email
 
 
@@ -39,7 +40,7 @@ PRESENCE_VISIBILITY_ALLOWED = 2
 PRESENCE_VISIBILITY_CHOICES = (
     (PRESENCE_VISIBILITY_ALL, _("Everyone")),
     (PRESENCE_VISIBILITY_FOLLOWED, _("Users I follow")),
-    (PRESENCE_VISIBILITY_ALLOWED, _("Users with permission"))
+    (PRESENCE_VISIBILITY_ALLOWED, _("Noone"))
 )
 
 
@@ -275,8 +276,12 @@ class User(AbstractBaseUser, PermissionsMixin):
         return self.username
 
     def set_username(self, new_username):
-        self.username = new_username
-        self.username_slug = slugify(new_username)
+        if new_username != self.username:
+            self.username = new_username
+            self.username_slug = slugify(new_username)
+
+            if self.pk:
+                username_changed.send(sender=self)
 
     def set_email(self, new_email):
         self.email = UserManager.normalize_email(new_email)

+ 4 - 0
misago/users/signals.py

@@ -0,0 +1,4 @@
+import django.dispatch
+
+
+username_changed = django.dispatch.Signal()

+ 1 - 1
misago/users/sites.py

@@ -87,7 +87,7 @@ class Site(object):
         url_name = request.resolver_match.url_name
 
         if namespace:
-            active_link = '%s:%s' (namespace, url_name)
+            active_link = '%s:%s' % (namespace, url_name)
         else:
             active_link = url_name
         return active_link

+ 1 - 1
misago/users/tests/test_sites.py

@@ -23,4 +23,4 @@ class SiteTests(TestCase):
         self.assertEqual(sorted_pages[1]['name'], 'Follows')
         self.assertEqual(sorted_pages[2]['name'], 'Posts')
 
-        self.assertEqual(self.site.get_default_link, 'misago:user_threads')
+        self.assertEqual(self.site.get_default_link(), 'misago:user_threads')

+ 33 - 0
misago/users/tests/test_usercp_views.py

@@ -0,0 +1,33 @@
+from django.contrib.auth import get_user_model
+from django.core.urlresolvers import reverse
+
+from misago.admin.testutils import AdminTestCase
+
+
+class ChangeForumOptionsTests(AdminTestCase):
+    def setUp(self):
+        super(ChangeForumOptionsTests, self).setUp()
+        self.view_link = reverse('misago:usercp_change_forum_options')
+
+    def test_change_forum_options_get(self):
+        """GET to usercp change options view returns 200"""
+        response = self.client.get(self.view_link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('Change forum options', response.content)
+
+    def test_change_forum_options_post(self):
+        """POST to usercp change options view returns 200"""
+        response = self.client.post(self.view_link, data={
+            'timezone': 'Asia/Qatar',
+            'presence_visibility': '2',
+            'subscribe_to_started_threads': '0',
+            'subscribe_to_replied_threads': '1',
+            })
+
+        self.assertEqual(response.status_code, 302)
+
+        test_user = get_user_model().objects.get(pk=self.test_admin.pk)
+        self.assertEqual(test_user.timezone, 'Asia/Qatar')
+        self.assertEqual(test_user.presence_visibility, 2)
+        self.assertEqual(test_user.subscribe_to_started_threads, 0)
+        self.assertEqual(test_user.subscribe_to_replied_threads, 1)

+ 7 - 0
misago/users/urls.py

@@ -35,3 +35,10 @@ urlpatterns += patterns('misago.users.views.api',
     url(r'^api/validate/email/(?P<user_id>\d+)/$', 'validate_email', name='api_validate_email'),
     url(r'^api/validate/password/$', 'validate_password', name='api_validate_password'),
 )
+
+
+urlpatterns += patterns('misago.users.views.usercp',
+    url(r'^usercp/forum-options/$', 'change_forum_options', name="usercp_change_forum_options"),
+    url(r'^usercp/change-username/$', 'change_username', name="usercp_change_username"),
+    url(r'^usercp/change-email-password/$', 'change_email_password', name="usercp_change_email_password"),
+)

+ 30 - 5
misago/users/views/usercp.py

@@ -1,21 +1,46 @@
+from django.contrib import messages
 from django.shortcuts import redirect, render as django_render
+from django.utils.translation import ugettext as _
 
+from misago.users.decorators import deny_guests
+from misago.users.forms.usercp import ChangeForumOptionsForm
 from misago.users.sites import usercp
 
 
 def render(request, template, context=None):
     context = context or {}
-    context['pages'] = usercp.get_pages(request, context['profile'])
+    context['pages'] = usercp.get_pages(request)
+
+    for page in context['pages']:
+        if page['is_active']:
+            context['active_page'] = page
+            break
+
     return django_render(request, template, context)
 
 
+@deny_guests
 def change_forum_options(request):
-    pass
+    form = ChangeForumOptionsForm(instance=request.user)
+    if request.method == 'POST':
+        form = ChangeForumOptionsForm(request.POST, instance=request.user)
+        if form.is_valid():
+            form.save()
+
+            message = _("Your forum options have been changed.")
+            messages.success(request, message)
 
+            return redirect('misago:usercp_change_forum_options')
 
-def change_sigin_credentials(request):
-    pass
+    return render(request, 'misago/usercp/change_forum_options.html',
+                  {'form': form})
 
 
+@deny_guests
 def change_username(request):
-    pass
+    return render(request, 'misago/usercp/change_username.html')
+
+
+@deny_guests
+def change_email_password(request):
+    return render(request, 'misago/usercp/change_email_password.html')