From: hernani Date: Mon, 5 Jul 2010 10:29:48 +0000 (+0000) Subject: Adds the user menu to the injectable places, makes several improvements in the user... X-Git-Tag: live~647 X-Git-Url: https://git.openstreetmap.org./osqa.git/commitdiff_plain/f982c8513663a0f9927fb04ba650a5f63bb7f21f Adds the user menu to the injectable places, makes several improvements in the user profile and fixes some errors introduced in the last commits. git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@487 0cfe37f9-358a-4d5e-be75-b63607b5c754 --- diff --git a/forum/forms/qanda.py b/forum/forms/qanda.py index d482d01..a98ef38 100644 --- a/forum/forms/qanda.py +++ b/forum/forms/qanda.py @@ -276,7 +276,7 @@ NOTIFICATION_CHOICES = ( ) class SubscriptionSettingsForm(forms.ModelForm): - user = forms.IntegerField(widget=forms.HiddenInput) + enable_notifications = forms.BooleanField(widget=forms.HiddenInput, required=False) member_joins = forms.ChoiceField(widget=forms.RadioSelect, choices=NOTIFICATION_CHOICES) new_question = forms.ChoiceField(widget=forms.RadioSelect, choices=NOTIFICATION_CHOICES) new_question_watched_tags = forms.ChoiceField(widget=forms.RadioSelect, choices=NOTIFICATION_CHOICES) @@ -286,3 +286,4 @@ class SubscriptionSettingsForm(forms.ModelForm): model = SubscriptionSettings + diff --git a/forum/models/user.py b/forum/models/user.py index f3a0389..7f1df5f 100644 --- a/forum/models/user.py +++ b/forum/models/user.py @@ -331,13 +331,13 @@ class User(BaseModel, DjangoUser): def suspension(self): if self.__dict__.get('_suspension_dencache_', False) != None: try: - self.__dict__['_suspension_dencache_'] = self.actions.get(action_type="suspend", canceled=False) + self.__dict__['_suspension_dencache_'] = self.reputes.get(action__action_type="suspend", action__canceled=False).action except ObjectDoesNotExist: self.__dict__['_suspension_dencache_'] = None except MultipleObjectsReturned: logging.error("Multiple suspension actions found for user %s (%s)" % (self.username, self.id)) - self.__dict__['_suspension_dencache_'] = self.actions.filter(action_type="suspend", canceled=False - ).order_by('-action_date')[0] + self.__dict__['_suspension_dencache_'] = self.reputes.filter(action__action_type="suspend", action__canceled=False + ).order_by('-action__action_date')[0] return self.__dict__['_suspension_dencache_'] @@ -419,7 +419,7 @@ class UserPropertyDict(object): class SubscriptionSettings(models.Model): - user = models.OneToOneField(User, related_name='subscription_settings') + user = models.OneToOneField(User, related_name='subscription_settings', editable=False) enable_notifications = models.BooleanField(default=True) diff --git a/forum/modules/ui.py b/forum/modules/ui.py index 49ce249..48c0246 100644 --- a/forum/modules/ui.py +++ b/forum/modules/ui.py @@ -1,13 +1,13 @@ class Registry(list): - def add(self, register): + def add(self, item): for i, r in enumerate(self): - if r.weight > register.weight: - self.insert(i, register) + if r.weight > item.weight: + self.insert(i, item) return - self.append(register) + self.append(item) HEAD_CONTENT = 'HEAD_CONTENT' @@ -16,6 +16,8 @@ PAGE_TOP_TABS = 'PAGE_TOP_TABS' FOOTER_LINKS = 'FOOTER_LINKS' PROFILE_TABS = 'PROFILE_TABS' +USER_MENU = 'USER_MENU' + __CONTAINER = { HEAD_CONTENT: Registry(), @@ -23,6 +25,8 @@ __CONTAINER = { PAGE_TOP_TABS: Registry(), FOOTER_LINKS: Registry(), PROFILE_TABS: Registry(), + + USER_MENU: Registry(), } diff --git a/forum/modules/ui_objects.py b/forum/modules/ui_objects.py index b88ea50..7fcf96c 100644 --- a/forum/modules/ui_objects.py +++ b/forum/modules/ui_objects.py @@ -2,6 +2,8 @@ from django.core.urlresolvers import reverse from django.template.defaultfilters import slugify from django import template from forum.utils import html +from ui import Registry +from copy import copy class Visibility(object): def __init__(self, level='public'): @@ -15,17 +17,28 @@ class Visibility(object): self.by_reputation = False self.level = level + self.negated = False def show_to(self, user): if self.by_reputation: - return user.is_authenticated() and (user.reputation >= int(self.level) or user.is_staff or user.is_superuser) + res = user.is_authenticated() and (user.reputation >= int(self.level) or user.is_staff or user.is_superuser) else: - return self.level == 'public' or (user.is_authenticated() and ( + res = self.level == 'public' or (user.is_authenticated() and ( self.level == 'authenticated' or ( self.level == 'superuser' and user.is_superuser) or ( self.level == 'staff' and (user.is_staff or user.is_superuser)) or ( self.level == 'owner' and user.is_siteowner))) + if self.negated: + return not res + else: + return res + + def __invert__(self): + inverted = copy(self) + inverted.negated = True + + Visibility.PUBLIC = Visibility('public') Visibility.AUTHENTICATED = Visibility('authenticated') Visibility.STAFF = Visibility('staff') @@ -49,7 +62,8 @@ class ObjectBase(object): def __call__(self, context): if callable(self.argument): - return self.argument(context['request'].user, context) + user = context.get('request', None) and context['request'].user or None + return self.argument(user, context) else: return self.argument @@ -57,8 +71,14 @@ class ObjectBase(object): self.visibility = visibility self.weight = weight + def _visible_to(self, user): + return (not self.visibility) or (self.visibility and self.visibility.show_to(user)) + def can_render(self, context): - return (not self.visibility) or (self.visibility and self.visibility.show_to(context['request'].user)) + try: + return self._visible_to(context['request'].user) + except KeyError: + return True def render(self, context): return '' @@ -139,4 +159,48 @@ class ProfileTab(LoopBase): tab_title=self.title, tab_description = self.description, tab_url=self.url_getter(context['view_user']) - )) \ No newline at end of file + )) + + +class AjaxMenuItem(ObjectBase): + def __init__(self, label, url, a_attrs=None, span_label='', span_attrs=None, visibility=None, weight=500): + super(AjaxMenuItem, self).__init__(visibility, weight) + self.label = self.Argument(label) + self.url = self.Argument(url) + self.a_attrs = self.Argument(a_attrs or {}) + self.span_label = self.Argument(span_label) + self.span_attrs = self.Argument(span_attrs or {}) + + def render(self, context): + return html.buildtag('li', + html.buildtag('span', self.span_label(context), **self.span_attrs(context)) + \ + html.hyperlink(self.url(context), self.label(context), **self.a_attrs(context)), + **{'class': 'item'}) + +class AjaxMenuGroup(ObjectBase, Registry): + def __init__(self, label, items, visibility=None, weight=500): + super(AjaxMenuGroup, self).__init__(visibility, weight) + self.label = label + + for item in items: + self.add(item) + + def can_render(self, context): + if super(AjaxMenuGroup, self).can_render(context): + for item in self: + if item.can_render(context): return True + + return False + + def render(self, context): + return html.buildtag('li', self.label, **{'class': 'separator'}) + "".join([ + item.render(context) for item in self if item.can_render(context) + ]) + +class UserMenuItem(AjaxMenuItem): + def __init__(self, render_to=None, *args, **kwargs): + super(UserMenuItem, self).__init__(*args, **kwargs) + self.render_to = render_to + + def can_render(self, context): + return (not self.render_to or (self.render_to(context['user']))) and super(UserMenuItem, self)._visible_to(context['viewer']) diff --git a/forum/registry.py b/forum/registry.py new file mode 100644 index 0000000..6fad2f0 --- /dev/null +++ b/forum/registry.py @@ -0,0 +1,103 @@ +from forum.modules import ui +from django.utils.translation import ugettext as _ +from django.core.urlresolvers import reverse +from django.template.defaultfilters import slugify +from forum.templatetags.extra_tags import get_score_badge +from forum import settings + + +ui.register(ui.HEADER_LINKS, + ui.Link(_('faq'), ui.Url('faq'), weight=400), + ui.Link(_('about'), ui.Url('about'), weight=300), + + ui.Link( + text=lambda u, c: u.is_authenticated() and _('logout') or _('login'), + url=lambda u, c: u.is_authenticated() and reverse('logout') or reverse('auth_signin'), + weight=200), + + ui.Link( + visibility=ui.Visibility.AUTHENTICATED, + text=lambda u, c: u.username, + url=lambda u, c: u.get_profile_url(), + post_code=lambda u, c: get_score_badge(u), + weight=100), + + ui.Link( + visibility=ui.Visibility.SUPERUSER, + text=_('administration'), + url=lambda u, c: reverse('admin_index'), + weight=0) + +) + +class SupportLink(ui.Link): + def can_render(self, context): + return bool(settings.SUPPORT_URL) + + +ui.register(ui.FOOTER_LINKS, + ui.Link( + text=_('contact'), + url=lambda u, c: settings.CONTACT_URL and settings.CONTACT_URL or "%s?next=%s" % (reverse('feedback'), c['request'].path), + weight=400), + SupportLink(_('support'), settings.SUPPORT_URL, attrs={'target': '_blank'}, weight=300), + ui.Link(_('privacy'), ui.Url('privacy'), weight=200), + ui.Link(_('faq'), ui.Url('faq'), weight=100), + ui.Link(_('about'), ui.Url('about'), weight=0), +) + +class ModerationMenuGroup(ui.AjaxMenuGroup): + def can_render(self, context): + return context['user'] != context['viewer'] and super(ModerationMenuGroup, self).can_render(context) + +class SuperUserSwitchMenuItem(ui.UserMenuItem): + def can_render(self, context): + return context['viewer'].is_siteowner or not context['user'].is_superuser + +ui.register(ui.USER_MENU, + ui.UserMenuItem( + label=_("edit profile"), + url=lambda u, c: reverse('edit_user', kwargs={'id': c['user'].id}), + span_attrs={'class': 'user-edit'}, + weight=0 + ), + ui.UserMenuItem( + label=_("authentication settings"), + url=lambda u, c: reverse('user_authsettings', kwargs={'id': c['user'].id}), + span_attrs={'class': 'user-auth'}, + weight=100 + ), + ui.UserMenuItem( + label=_("email notification settings"), + url=lambda u, c: reverse('user_subscriptions', kwargs={'id': c['user'].id, 'slug': slugify(c['user'].username)}), + span_attrs={'class': 'user-subscriptions'}, + weight=200 + ), + ModerationMenuGroup(_("Moderation tools"), items=( + ui.UserMenuItem( + label=lambda u, c: c['user'].is_suspended() and _("withdraw suspension") or _("suspend this user"), + url=lambda u, c: reverse('user_suspend', kwargs={'id': c['user'].id}), + a_attrs=lambda u, c: {'class': c['user'].is_suspended() and 'ajax-command confirm' or 'ajax-command withprompt'}, + render_to=lambda u: not u.is_superuser, + ), + ui.UserMenuItem( + label=lambda u, c: _("give/take karma"), + url=lambda u, c: reverse('user_award_points', kwargs={'id': c['user'].id}), + a_attrs=lambda u, c: {'id': 'award-rep-points', 'class': 'ajax-command withprompt'}, + span_attrs={'class': 'user-award_rep'}, + render_to=lambda u: not u.is_suspended(), + ), + ui.UserMenuItem( + label=lambda u, c: c['user'].is_staff and _("remove moderator status") or _("grant moderator status"), + url=lambda u, c: reverse('user_powers', kwargs={'id': c['user'].id, 'action':c['user'].is_staff and 'remove' or 'grant', 'status': 'staff'}), + a_attrs=lambda u, c: {'class': 'ajax-command confirm'}, + span_attrs={'class': 'user-moderator'}, + ), + SuperUserSwitchMenuItem( + label=lambda u, c: c['user'].is_superuser and _("remove super user status") or _("grant super user status"), + url=lambda u, c: reverse('user_powers', kwargs={'id': c['user'].id, 'action':c['user'].is_superuser and 'remove' or 'grant', 'status': 'super'}), + a_attrs=lambda u, c: {'class': 'ajax-command confirm'}, + span_attrs={'class': 'user-superuser'}, + ), + ), visibility=ui.Visibility.SUPERUSER, weight=500) +) diff --git a/forum/skins/default/media/js/osqa.main.js b/forum/skins/default/media/js/osqa.main.js index 270a6b4..2a2a48e 100644 --- a/forum/skins/default/media/js/osqa.main.js +++ b/forum/skins/default/media/js/osqa.main.js @@ -299,6 +299,8 @@ $(function() { $('a.ajax-command').live('click', function(evt) { if (running) return false; + $('.context-menu-dropdown').slideUp('fast'); + var el = $(this); if (el.is('.withprompt')) { @@ -344,7 +346,8 @@ $(function() { $dropdown.slideToggle('fast', function() { if ($dropdown.is(':visible')) { $dropdown.one('clickoutside', function() { - $dropdown.slideUp('fast') + if ($dropdown.is(':visible')) + $dropdown.slideUp('fast'); }); } }); diff --git a/forum/skins/default/media/js/osqa.user.js b/forum/skins/default/media/js/osqa.user.js deleted file mode 100644 index 5241880..0000000 --- a/forum/skins/default/media/js/osqa.user.js +++ /dev/null @@ -1,73 +0,0 @@ -$().ready(function() { - var $dropdown = $('#user-menu-dropdown'); - - $('#user-menu').click(function() { - $('.dialog').fadeOut('fast', function() { - $dialog.remove(); - }); - $dropdown.slideToggle('fast', function() { - if ($dropdown.is(':visible')) { - $dropdown.one('clickoutside', function() { - $dropdown.slideUp('fast') - }); - } - }); - }); - - $('.confirm').each(function() { - var $link = $(this); - - $link.click(function(e) { - $dropdown.slideUp('fast'); - - show_dialog({ - html: messages.confirm, - extra_class: 'confirm', - event: e, - yes_callback: function() { - window.location = $link.attr('href'); - }, - yes_text: messages.yes, - show_no: true, - no_text: messages.no - }); - - return false; - }); - }); - - $('#award-rep-points').click(function(e) { - $dropdown.slideUp('fast'); - - var table = '' - + '
' + messages.points + '
' + messages.message + '
'; - - show_dialog({ - html: table, - extra_class: 'award-rep-points', - event: e, - yes_callback: function($dialog) { - var $points_input = $('#points-to-award'); - var _points = parseInt($points_input.val()); - - if (!isNaN(_points)) { - $dialog.fadeOut('fast'); - var _message = $('#award-message').val(); - $.post($('#award-rep-points').attr('href'), {points: _points, message: _message}, function(data) { - if (data.success) { - $('#user-reputation').css('background', 'yellow'); - $('#user-reputation').html(data.reputation); - - $('#user-reputation').animate({ backgroundColor: "transparent" }, 1000); - - } - }, 'json') - } - }, - show_no: true - }); - - return false; - }); - -}); \ No newline at end of file diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css index e0605d6..c61c065 100644 --- a/forum/skins/default/media/style/style.css +++ b/forum/skins/default/media/style/style.css @@ -1622,6 +1622,7 @@ div.dialog, .context-menu-dropdown { top: 1.5em; text-align: left; list-style-type: none; + z-index: 500; } .context-menu-dropdown li.item { @@ -1661,6 +1662,7 @@ div.dialog, .context-menu-dropdown { div.dialog .dialog-content { padding: 12px 12px 37px 12px; + z-index: 1000; } div.dialog .dialog-buttons { diff --git a/forum/skins/default/templates/node/post_controls.html b/forum/skins/default/templates/node/post_controls.html index c34e50f..e756fff 100644 --- a/forum/skins/default/templates/node/post_controls.html +++ b/forum/skins/default/templates/node/post_controls.html @@ -10,9 +10,9 @@ {% endfor %} {% if menu|length %} | - - {% trans "more" %} ▼ -