Browse Source

Signatures are parseable and editable

Ralfp 12 years ago
parent
commit
c9271f76a8

+ 1 - 1
misago/acl/middleware.py

@@ -23,4 +23,4 @@ class ACLMiddleware(object):
         if request.user.is_authenticated() and (request.acl.team or request.user.is_god()) != request.user.is_team:
         if request.user.is_authenticated() and (request.acl.team or request.user.is_god()) != request.user.is_team:
             request.user.is_team = (request.acl.team or request.user.is_god())
             request.user.is_team = (request.acl.team or request.user.is_god())
             request.user.save(force_update=True)
             request.user.save(force_update=True)
-        
+        request.session.team = request.user.is_team

+ 1 - 0
misago/markdown/__init__.py

@@ -0,0 +1 @@
+from misago.markdown.factory import *

+ 31 - 0
misago/markdown/factory.py

@@ -0,0 +1,31 @@
+from django.conf import settings
+import markdown
+
+def signature_markdown(acl, text):
+    md = markdown.Markdown(
+                           safe_mode='escape',
+                           output_format=settings.OUTPUT_FORMAT)
+    
+    if not acl.usercp.allow_signature_links():
+        del md.inlinePatterns['link']
+        del md.inlinePatterns['autolink']
+    if not acl.usercp.allow_signature_images():
+        del md.inlinePatterns['image_link']
+        del md.inlinePatterns['image_reference']
+        
+    del md.parser.blockprocessors['hashheader']
+    del md.parser.blockprocessors['setextheader']
+    del md.parser.blockprocessors['code']
+    del md.parser.blockprocessors['quote']
+    del md.parser.blockprocessors['hr']
+    del md.parser.blockprocessors['olist']
+    del md.parser.blockprocessors['ulist']
+    
+    return md.convert(text)
+
+
+def post_markdown(request, text):
+    md = markdown.Markdown(
+                           safe_mode='escape',
+                           output_format=settings.OUTPUT_FORMAT)
+    return md.convert(text)

+ 1 - 1
misago/sessions/models.py

@@ -9,7 +9,7 @@ class Session(models.Model):
     agent = models.CharField(max_length=255)
     agent = models.CharField(max_length=255)
     start = models.DateTimeField()
     start = models.DateTimeField()
     last = models.DateTimeField()
     last = models.DateTimeField()
-    staff = models.BooleanField(default=False)
+    team = models.BooleanField(default=False)
     admin = models.BooleanField(default=False)
     admin = models.BooleanField(default=False)
     matched = models.BooleanField(default=False)
     matched = models.BooleanField(default=False)
     hidden = models.BooleanField(default=False)
     hidden = models.BooleanField(default=False)

+ 2 - 2
misago/sessions/sessions.py

@@ -118,7 +118,7 @@ class SessionHuman(SessionMisago):
     def __init__(self, request):
     def __init__(self, request):
         self.expired = False
         self.expired = False
         self.hidden = False
         self.hidden = False
-        self.staff = False
+        self.team = False
         self.remember_me = None
         self.remember_me = None
         self._user = None
         self._user = None
         self._ip = self.get_ip(request)
         self._ip = self.get_ip(request)
@@ -195,7 +195,7 @@ class SessionHuman(SessionMisago):
     def save(self, request, response):
     def save(self, request, response):
         self._session_rk.user = self._user
         self._session_rk.user = self._user
         self._session_rk.hidden = self.hidden
         self._session_rk.hidden = self.hidden
-        self._session_rk.staff = self.staff
+        self._session_rk.team = self.team
         super(SessionHuman, self).save(request, response)
         super(SessionHuman, self).save(request, response)
         
         
     def human_session(self):
     def human_session(self):

+ 10 - 44
misago/settings/fixtures.py

@@ -117,54 +117,20 @@ def load_settings_group_fixture(group, fixture):
 
 
 def update_settings_group_fixture(group, fixture):
 def update_settings_group_fixture(group, fixture):
     try:
     try:
-        # Get and update group entry
         model_group = Group.objects.get(key=group)
         model_group = Group.objects.get(key=group)
-        model_group.name = get_msgid(fixture['name'])
-        model_group.description = get_msgid(fixture.get('description'))
-        model_group.save(force_update=True)
+        settings = {}
+        for setting in model_group.setting_set.all():
+            settings[setting.pk] = setting.value
+        model_group.delete()
+        load_settings_group_fixture(group, fixture)
         
         
-        # Update group settings
-        fixture = fixture.get('settings', ())
-        for setting in fixture:
-            # Clear setting value
-            value = setting[1].get('value')
-            value_default = setting[1].get('default')
-            # Convert boolean True and False to 1 and 0, otherwhise it wont work
-            if setting[1].get('type') == 'boolean':
-                value = 1 if value else 0
-                value_default = 1 if value_default else 0
-            # Convert array value to string
-            if setting[1].get('type') == 'array':
-                value = ','.join(value) if value else ''
-                value_default = ','.join(value_default) if value_default else ''
+        for setting in settings:
             try:
             try:
-                # Update setting entry
-                model_setting = Setting.objects.get(setting=setting[0])
-                model_setting.value_default = value_default
-                model_setting.type = setting[1].get('type')
-                model_setting.input = setting[1].get('input')
-                model_setting.extra = base64.encodestring(pickle.dumps(setting[1].get('extra', {}), pickle.HIGHEST_PROTOCOL))
-                model_setting.position = setting[1].get('position')
-                model_setting.separator = get_msgid(setting[1].get('separator'))
-                model_setting.name = get_msgid(setting[1].get('name'))
-                model_setting.description = get_msgid(setting[1].get('description'))
-                model_setting.save(force_update=True)
+                new_setting = Setting.objects.get(pk=setting)
+                new_setting.value = settings[setting]
+                new_setting.save(force_update=True)
             except Setting.DoesNotExist:
             except Setting.DoesNotExist:
-                # Store setting in database
-                model_setting = Setting(
-                                        setting=setting[0],
-                                        group=model_group,
-                                        value=value,
-                                        value_default=value_default,
-                                        type=setting[1].get('type'),
-                                        input=setting[1].get('input'),
-                                        extra=base64.encodestring(pickle.dumps(setting[1].get('extra', {}), pickle.HIGHEST_PROTOCOL)),
-                                        position=setting[1].get('position'),
-                                        separator=get_msgid(setting[1].get('separator')),
-                                        name=get_msgid(setting[1].get('name')),
-                                        description=get_msgid(setting[1].get('description')),
-                                    )
-                model_setting.save(force_insert=True)
+                pass
     except Group.DoesNotExist:
     except Group.DoesNotExist:
         load_settings_group_fixture(group, fixture)
         load_settings_group_fixture(group, fixture)
     
     

+ 22 - 4
misago/usercp/acl.py

@@ -10,12 +10,16 @@ def make_form(request, role, form):
         form.base_fields['name_changes_allowed'] = forms.IntegerField(min_value=0,initial=1)
         form.base_fields['name_changes_allowed'] = forms.IntegerField(min_value=0,initial=1)
         form.base_fields['changes_expire'] = forms.IntegerField(min_value=0,initial=0)
         form.base_fields['changes_expire'] = forms.IntegerField(min_value=0,initial=0)
         form.base_fields['can_use_signature'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
         form.base_fields['can_use_signature'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+        form.base_fields['allow_signature_links'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+        form.base_fields['allow_signature_images'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
         form.layout.append((
         form.layout.append((
                             _("User Profile"),
                             _("User Profile"),
                             (
                             (
                              ('name_changes_allowed', {'label': _("Allowed Username changes number"), 'help_text': _("Enter zero to don't allow users with this role to change their names.")}),
                              ('name_changes_allowed', {'label': _("Allowed Username changes number"), 'help_text': _("Enter zero to don't allow users with this role to change their names.")}),
                              ('changes_expire', {'label': _("Don't count username changes older than"), 'help_text': _("Number of days since name change that makes that change no longer count to limit. For example, if you enter 7 days and set changes limit 3, users with this rank will not be able to make more than three changes in duration of 7 days. Enter zero to make all changes count.")}),
                              ('changes_expire', {'label': _("Don't count username changes older than"), 'help_text': _("Number of days since name change that makes that change no longer count to limit. For example, if you enter 7 days and set changes limit 3, users with this rank will not be able to make more than three changes in duration of 7 days. Enter zero to make all changes count.")}),
                              ('can_use_signature', {'label': _("Can have signature")}),
                              ('can_use_signature', {'label': _("Can have signature")}),
+                             ('allow_signature_links', {'label': _("Can put links in signature")}),
+                             ('allow_signature_images', {'label': _("Can put images in signature")}),
                              ),
                              ),
                             ))
                             ))
 
 
@@ -44,20 +48,34 @@ class UserCPACL(BaseACL):
     
     
     def can_use_signature(self):
     def can_use_signature(self):
         return self.acl['signature']
         return self.acl['signature']
+    
+    def allow_signature_links(self):
+        return self.acl['signature_links']
+    
+    def allow_signature_images(self):
+        return self.acl['signature_images']
 
 
 
 
 def build(acl, roles):
 def build(acl, roles):
     acl.usercp = UserCPACL()
     acl.usercp = UserCPACL()
-    acl.usercp.acl['signature'] = False
     acl.usercp.acl['name_changes_allowed'] = 0
     acl.usercp.acl['name_changes_allowed'] = 0
     acl.usercp.acl['changes_expire'] = 0
     acl.usercp.acl['changes_expire'] = 0
+    acl.usercp.acl['signature'] = False
+    acl.usercp.acl['signature_links'] = False
+    acl.usercp.acl['signature_images'] = False
     
     
     for role in roles:
     for role in roles:
-        if 'can_use_signature' in role and role['can_use_signature'] > acl.usercp.acl['signature']:
-            acl.usercp.acl['signature'] = role['can_use_signature']
-
         if 'name_changes_allowed' in role and role['name_changes_allowed'] > acl.usercp.acl['name_changes_allowed']:
         if 'name_changes_allowed' in role and role['name_changes_allowed'] > acl.usercp.acl['name_changes_allowed']:
             acl.usercp.acl['name_changes_allowed'] = role['name_changes_allowed']
             acl.usercp.acl['name_changes_allowed'] = role['name_changes_allowed']
 
 
         if 'changes_expire' in role and role['changes_expire'] > acl.usercp.acl['changes_expire']:
         if 'changes_expire' in role and role['changes_expire'] > acl.usercp.acl['changes_expire']:
             acl.usercp.acl['changes_expire'] = role['changes_expire']
             acl.usercp.acl['changes_expire'] = role['changes_expire']
+            
+        if 'can_use_signature' in role and role['can_use_signature'] > acl.usercp.acl['signature']:
+            acl.usercp.acl['signature'] = role['can_use_signature']
+            
+        if 'allow_signature_links' in role and role['allow_signature_links'] > acl.usercp.acl['signature_links']:
+            acl.usercp.acl['signature_links'] = role['allow_signature_links']
+            
+        if 'allow_signature_images' in role and role['allow_signature_images'] > acl.usercp.acl['signature_images']:
+            acl.usercp.acl['signature_images'] = role['allow_signature_images']

+ 16 - 0
misago/usercp/signature/forms.py

@@ -0,0 +1,16 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from misago.forms import Form
+
+
+class SignatureForm(Form):
+    signature = forms.CharField(widget=forms.Textarea,required=False)
+    
+    layout = (
+              (
+               None,
+               (
+                ('signature', {'label': _("Your Signature"), 'attrs': {'rows': 10}}),
+                )
+               ),
+              )

+ 30 - 1
misago/usercp/signature/views.py

@@ -1,5 +1,13 @@
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
 from misago.authn.decorators import block_guest
 from misago.authn.decorators import block_guest
+from misago.forms import FormLayout
+from misago.markdown import signature_markdown
+from misago.messages import Message
 from misago.usercp.template import RequestContext
 from misago.usercp.template import RequestContext
+from misago.usercp.signature.forms import SignatureForm
+from misago.views import error404
 
 
 @block_guest
 @block_guest
 def signature(request):
 def signature(request):
@@ -9,8 +17,29 @@ def signature(request):
                                                 context_instance=RequestContext(request, {
                                                 context_instance=RequestContext(request, {
                                                   'tab': 'signature',
                                                   'tab': 'signature',
                                                  }));
                                                  }));
-                                                
+    
+    siggy_text = ''
+    message = request.messages.get_message('usercp_signature')
+    if request.method == 'POST':
+        form = SignatureForm(request.POST, request=request, initial={'signature': request.user.signature})
+        if form.is_valid():
+            request.user.signature = form.cleaned_data['signature']
+            if request.user.signature:
+                request.user.signature_preparsed = signature_markdown(request.acl,
+                                                                      request.user.signature)
+            else:
+                request.user.signature_preparsed = None
+            request.user.save(force_update=True)
+            request.messages.set_flash(Message(_("Your signature has been changed.")), 'success', 'usercp_signature')
+            return redirect(reverse('usercp_signature'))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = SignatureForm(request=request, initial={'signature': request.user.signature})
+        
     return request.theme.render_to_response('usercp/signature.html',
     return request.theme.render_to_response('usercp/signature.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
+                                              'message': message,
                                               'tab': 'signature',
                                               'tab': 'signature',
+                                              'form': FormLayout(form),
                                              }));
                                              }));

+ 1 - 8
misago/users/models.py

@@ -311,14 +311,7 @@ class User(models.Model):
                     model_obj.objects.update_username(self)
                     model_obj.objects.update_username(self)
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
-     
-    def set_signature(self, signature):
-        self.signature = signature.strip()
-        self.signature_preparsed = ''
-        if self.signature:
-            import markdown
-            self.signature_preparsed = markdown.markdown(signature, safe_mode='escape', output_format=settings.OUTPUT_FORMAT)
-        
+    
     def is_username_valid(self, e):
     def is_username_valid(self, e):
         try:
         try:
             raise ValidationError(e.message_dict['username'])
             raise ValidationError(e.message_dict['username'])

+ 10 - 1
misago/users/views.py

@@ -3,6 +3,7 @@ from django.db.models import Q
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 from misago.admin import site
 from misago.admin import site
 from misago.admin.widgets import *
 from misago.admin.widgets import *
+from misago.markdown import signature_markdown
 from misago.users.forms import UserForm, NewUserForm, SearchUsersForm
 from misago.users.forms import UserForm, NewUserForm, SearchUsersForm
 from misago.users.models import User
 from misago.users.models import User
 from misago.utils import get_random_string
 from misago.utils import get_random_string
@@ -280,11 +281,19 @@ class Edit(FormWidget):
         target.rank = form.cleaned_data['rank']
         target.rank = form.cleaned_data['rank']
         target.avatar_ban_reason_user = form.cleaned_data['avatar_ban_reason_user']
         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.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 = form.cleaned_data['signature_ban']
         target.signature_ban_reason_user = form.cleaned_data['signature_ban_reason_user']
         target.signature_ban_reason_user = form.cleaned_data['signature_ban_reason_user']
         target.signature_ban_reason_admin = form.cleaned_data['signature_ban_reason_admin']
         target.signature_ban_reason_admin = form.cleaned_data['signature_ban_reason_admin']
         
         
+        # Do signature mumbo-jumbo
+        if form.cleaned_data['signature']:
+            target.signature = form.cleaned_data['signature']
+            target.signature_preparsed = signature_markdown(target.get_acl(request),
+                                                            form.cleaned_data['signature'])
+        else:
+            target.signature = None
+            target.signature_preparsed = None
+        
         # Do avatar ban mumbo-jumbo
         # Do avatar ban mumbo-jumbo
         if target.avatar_ban != form.cleaned_data['avatar_ban']:
         if target.avatar_ban != form.cleaned_data['avatar_ban']:
             if form.cleaned_data['avatar_ban']:
             if form.cleaned_data['avatar_ban']:

+ 8 - 1
static/sora/css/sora.css

@@ -834,7 +834,8 @@ form fieldset{border-top:1px solid #e8e8e8;margin:0px;padding:0px;padding-top:16
 form fieldset .control-group{padding-bottom:4px;}
 form fieldset .control-group{padding-bottom:4px;}
 form fieldset .control-group:last-child{padding-bottom:0px;}
 form fieldset .control-group:last-child{padding-bottom:0px;}
 form fieldset:first-child{border-top:none;padding-top:0px;}
 form fieldset:first-child{border-top:none;padding-top:0px;}
-form fieldset:last-child{padding-bottom:0px;margin-bottom:0px;}
+form fieldset:last-child{padding-bottom:0px;margin-bottom:-8px;}
+.form-actions{margin-top:0px;}
 textarea{resize:vertical;}
 textarea{resize:vertical;}
 .radio-group,.select-multiple,.yes-no-switch{margin-bottom:8px;}.radio-group label,.select-multiple label,.yes-no-switch label{color:#000000;font-weight:normal;}
 .radio-group,.select-multiple,.yes-no-switch{margin-bottom:8px;}.radio-group label,.select-multiple label,.yes-no-switch label{color:#000000;font-weight:normal;}
 .checkbox{color:#000000;font-weight:normal;}
 .checkbox{color:#000000;font-weight:normal;}
@@ -933,6 +934,12 @@ th.table-sort.sort-desc a:hover{border-bottom:3px solid #eca09a;padding-bottom:5
 .nav-tabs .tab-search form{marging:0px;margin-bottom:-4px;}
 .nav-tabs .tab-search form{marging:0px;margin-bottom:-4px;}
 .nav-tabs .tab-search.tab-search-no-tabs{position:relative;bottom:12px;}
 .nav-tabs .tab-search.tab-search-no-tabs{position:relative;bottom:12px;}
 .nav-tabs button{padding-left:7px;padding-right:7px;}
 .nav-tabs button{padding-left:7px;padding-right:7px;}
+.editor{background-color:#e3e3e3;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.editor .editor-input{padding:8px;}.editor .editor-input div{margin-right:14px;}
+.editor .editor-input textarea{margin:0px;width:100%;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;}
+.editor .editor-actions{border-top:2px solid #c9c9c9;overflow:auto;padding:8px;}
+.markdown{margin-bottom:-12px;}
+.post-content{font-size:100%;}
+.signature{font-size:90%;}
 .clickable{cursor:pointer;}
 .clickable{cursor:pointer;}
 .profile-header{overflow:auto;border-bottom:none;margin-bottom:30px;}.profile-header .avatar-height{overflow:auto;}.profile-header .avatar-height .avatar{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;margin-right:24px;width:125px;height:125px;}
 .profile-header{overflow:auto;border-bottom:none;margin-bottom:30px;}.profile-header .avatar-height{overflow:auto;}.profile-header .avatar-height .avatar{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;margin-right:24px;width:125px;height:125px;}
 .profile-header .avatar-height h1{font-size:300%;}
 .profile-header .avatar-height h1{font-size:300%;}

+ 2 - 0
static/sora/css/sora.less

@@ -81,6 +81,8 @@
 @import "sora/navs.less";
 @import "sora/navs.less";
 @import "sora/navbar.less";
 @import "sora/navbar.less";
 
 
+@import "sora/editor.less";
+@import "sora/markdown.less";
 @import "sora/utilities.less";
 @import "sora/utilities.less";
 
 
 @import "jquery.Jcrop.min.css";
 @import "jquery.Jcrop.min.css";

+ 27 - 0
static/sora/css/sora/editor.less

@@ -0,0 +1,27 @@
+// Misago editor
+// --------------------------------------------------
+.editor {
+  background-color: darken(@bodyBackground, 10%);
+  .border-radius(3px);
+  
+  .editor-input {
+    padding: 8px;
+    
+    div {
+      margin-right: 14px;
+    }
+    
+    textarea {
+      margin: 0px;
+      width: 100%;
+      
+      font-family: @monoFontFamily;
+    }
+  }
+  
+  .editor-actions {
+    border-top: 2px solid darken(@bodyBackground, 20%);
+    overflow: auto;
+    padding: 8px;
+  }
+}

+ 5 - 1
static/sora/css/sora/forms.less

@@ -37,10 +37,14 @@ form {
   
   
   fieldset:last-child {
   fieldset:last-child {
     padding-bottom: 0px;
     padding-bottom: 0px;
-    margin-bottom: 0px;
+    margin-bottom: -8px;
   }
   }
 }
 }
 
 
+.form-actions {
+  margin-top: 0px;
+}
+
 // Make textarea resizeable vertically only
 // Make textarea resizeable vertically only
 // -------------------------
 // -------------------------
 textarea {
 textarea {

+ 13 - 0
static/sora/css/sora/markdown.less

@@ -0,0 +1,13 @@
+// Markdown styles
+// -------------------------
+.markdown {
+  margin-bottom: -12px
+}
+
+.post-content {
+  font-size: 100%;
+}
+
+.signature {
+  font-size: 90%;
+}

+ 1 - 1
static/sora/css/sora/utilities.less

@@ -43,4 +43,4 @@
   h3 {
   h3 {
     margin-top: 0px;
     margin-top: 0px;
   }
   }
-}
+}

+ 0 - 0
static/sora/js/editor.js


+ 15 - 0
templates/sora/editor.html

@@ -0,0 +1,15 @@
+{% macro editor(field, submit_button) %}
+<div class="editor">
+  <div class="editor-input">
+    <div>
+      <textarea name="{{ field.html_name }}" id="{{ field.html_id }}" rows="4">{% if field.has_value %}{{ field.value }}{% endif %}</textarea>
+    </div>
+  </div>
+  <div class="editor-actions">
+    <button name="save" type="submit" class="btn btn-primary pull-right">{{ submit_button }}</button>
+  </div>
+</div>
+{% endmacro %}
+
+{% macro editor_js() %}
+{% endmacro %}

+ 14 - 0
templates/sora/usercp/signature.html

@@ -1,10 +1,24 @@
 {% extends "sora/usercp/layout.html" %}
 {% extends "sora/usercp/layout.html" %}
 {% load i18n %}
 {% load i18n %}
 {% load url from future %}
 {% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% import "sora/editor.html" as editor with context %}
 {% import "sora/macros.html" as macros with context %}
 {% import "sora/macros.html" as macros with context %}
 
 
 {% block title %}{{ macros.page_title(title=_('Edit your Signature')) }}{% endblock %}
 {% block title %}{{ macros.page_title(title=_('Edit your Signature')) }}{% endblock %}
 
 
 {% block action %}
 {% block action %}
 <h2>{% trans %}Edit your Signature{% endtrans %}</h2>
 <h2>{% trans %}Edit your Signature{% endtrans %}</h2>
+{% if message %}{{ macros.draw_message(message, 'alert-form') }}{% endif %}
+{% if user.signature_preparsed %}
+<div class="well" style="margin: 0px; margin-bottom: 32px; padding: 8px;">
+  <div class="markdown">
+    {{ user.signature_preparsed|safe }}
+  </div>
+</div>
+{% endif %}
+<form action="{% url 'usercp_signature' %}" method="post">
+  <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+  {{ editor.editor(form.fields.signature, _('Save Signature')) }}
+</form>
 {% endblock %}
 {% endblock %}