diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-09-05 12:16:43 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-09-05 12:16:43 -0400 |
commit | 3f6ee303f6df25fad5618d113b5b401e707c4fe8 (patch) | |
tree | ac54052a64301d70adfbf97fd950154c5916aea6 | |
parent | a11da46b667d619c55d41294fed12f5b1b9e3c8f (diff) | |
download | askbot-3f6ee303f6df25fad5618d113b5b401e707c4fe8.tar.gz askbot-3f6ee303f6df25fad5618d113b5b401e707c4fe8.tar.bz2 askbot-3f6ee303f6df25fad5618d113b5b401e707c4fe8.zip |
a part of "ask to join group" moderation
-rw-r--r-- | askbot/const/__init__.py | 1 | ||||
-rw-r--r-- | askbot/models/__init__.py | 34 | ||||
-rw-r--r-- | askbot/models/user.py | 8 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_profile/group_join_requests.html | 108 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_profile/user_inbox.html | 7 | ||||
-rw-r--r-- | askbot/tests/db_api_tests.py | 50 | ||||
-rw-r--r-- | askbot/tests/post_model_tests.py | 5 | ||||
-rw-r--r-- | askbot/tests/utils.py | 6 | ||||
-rw-r--r-- | askbot/urls.py | 5 | ||||
-rw-r--r-- | askbot/views/commands.py | 5 | ||||
-rw-r--r-- | askbot/views/users.py | 34 |
11 files changed, 245 insertions, 18 deletions
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py index fd9ece7b..5f47bb79 100644 --- a/askbot/const/__init__.py +++ b/askbot/const/__init__.py @@ -177,6 +177,7 @@ TYPE_ACTIVITY_CREATE_REJECT_REASON = 26 TYPE_ACTIVITY_UPDATE_REJECT_REASON = 27 TYPE_ACTIVITY_VALIDATION_EMAIL_SENT = 28 TYPE_ACTIVITY_POST_SHARED = 29 +TYPE_ACTIVITY_ASK_TO_JOIN_GROUP = 30 #TYPE_ACTIVITY_EDIT_QUESTION = 17 #TYPE_ACTIVITY_EDIT_ANSWER = 18 diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index cba3d5a0..ee0d4269 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -402,6 +402,26 @@ def user_get_or_create_fake_user(self, username, email): user.save() return user +def user_notify_users( + self, notification_type=None, recipients=None, content_object=None +): + """A utility function that creates instance + of :class:`Activity` and adds recipients + * `notification_type` - value should be one of TYPE_ACTIVITY_... + * `recipients` - an iterable of user objects + * `content_object` - any object related to the notification + + todo: possibly add checks on the content_object, depending on the + notification_type + """ + activity = Activity( + user=self, + activity_type=notification_type, + content_object=content_object + ) + activity.save() + activity.add_recipients(recipients) + def _assert_user_can( user = None, post = None, #related post (may be parent) @@ -2630,7 +2650,7 @@ def user_edit_group_membership(self, user=None, group=None, action=None): #calculate new level openness = group.get_openness_level_for_user(user) - #a temporary shunt + #let people join these special groups, but not leave if group.name == askbot_settings.GLOBAL_GROUP_NAME: openness = 'open' elif group.name == format_personal_group_name(user): @@ -2776,6 +2796,7 @@ User.add_to_class( user_update_wildcard_tag_selections ) User.add_to_class('approve_post_revision', user_approve_post_revision) +User.add_to_class('notify_users', user_notify_users) #assertions User.add_to_class('assert_can_vote_for_post', user_assert_can_vote_for_post) @@ -3494,6 +3515,16 @@ def make_admin_if_first_user(instance, **kwargs): instance.set_admin_status() cache.cache.set('admin-created', True) +def moderate_group_joining(sender, instance=None, created=False, **kwargs): + if created and instance.level == GroupMembership.PENDING: + user = instance.user + group = instance.group + user.notify_users( + notification_type=const.TYPE_ACTIVITY_ASK_TO_JOIN_GROUP, + recipients = group.get_moderators(), + content_object = group + ) + #signal for User model save changes django_signals.pre_save.connect(make_admin_if_first_user, sender=User) @@ -3506,6 +3537,7 @@ django_signals.post_save.connect(notify_award_message, sender=Award) django_signals.post_save.connect(record_answer_accepted, sender=Post) django_signals.post_save.connect(record_vote, sender=Vote) django_signals.post_save.connect(record_favorite_question, sender=FavoriteQuestion) +django_signals.post_save.connect(moderate_group_joining, sender=GroupMembership) if 'avatar' in django_settings.INSTALLED_APPS: from avatar.models import Avatar diff --git a/askbot/models/user.py b/askbot/models/user.py index 92927ef2..77e181d3 100644 --- a/askbot/models/user.py +++ b/askbot/models/user.py @@ -369,11 +369,13 @@ class GroupMembership(AuthUserGroups): (FULL, 'full') ) ALL_LEVEL_CHOICES = LEVEL_CHOICES + ((NONE, 'none'),) + level = models.SmallIntegerField( default=FULL, choices=LEVEL_CHOICES, ) + class Meta: app_label = 'askbot' @@ -461,6 +463,12 @@ class Group(AuthGroup): app_label = 'askbot' db_table = 'askbot_group' + def get_moderators(self): + """returns group moderators""" + user_filter = models.Q(is_superuser=True) | models.Q(status='m') + user_filter = user_filter & models.Q(groups__in=[self]) + return User.objects.filter(user_filter) + def get_openness_choices(self): """gives answers to question "How can users join this group?" diff --git a/askbot/skins/default/templates/user_profile/group_join_requests.html b/askbot/skins/default/templates/user_profile/group_join_requests.html new file mode 100644 index 00000000..6d484b88 --- /dev/null +++ b/askbot/skins/default/templates/user_profile/group_join_requests.html @@ -0,0 +1,108 @@ +{% extends "user_profile/user.html" %} +{% import "macros.html" as macros %} +{% block before_css %} + <link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" /> +{% endblock %} +<!-- user_responses.html --> +{# +This template accepts a list of response list +they are a generalized form of any response and + +The following properties of response object are used: +timestamp - when it happened +user - user who gave response (database object) +response_type - type of response +response_url - link to the question +response_title - title of the question +response_snippet - abbreviated content of the response +inbox_section - forum|flags +#} +{% block profilesection %} + {% trans %}inbox{% endtrans %} +{% endblock %} +{% block usercontent %} + <div style="padding-top:5px;font-size:13px;"> + {% set re_count = request.user.new_response_count + + request.user.seen_response_count + %} + {% if moderation_items %} + {% set flag_count = moderation_items['new_count'] + + moderation_items['seen_count'] + %} + {% else %} + {% set flag_count = 0 %} + {% endif %} + {% if re_count > 0 and flag_count > 0 %} + <div id="re_sections"> + {% trans %}Sections:{% endtrans %} + <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=forum" + {% if inbox_section == 'forum' %}class="on"{% endif %} + > + {% trans %}forum responses ({{re_count}}){% endtrans -%} + </a> | + <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=flags" + {% if inbox_section == 'flags' %}class="on"{% endif %} + > + {% trans %}flagged items ({{flag_count}}){% endtrans %} + </a> + {% if join_requests_count %} | + <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=join_requests" + {% if inbox_section == 'join_requests' %}class="on"{% endif %} + > + {% trans %}group join requests{% endtrans %} + </a> + {% endif %} + </div> + {% endif %} + {# content #} + <table> + {% for join_request in join_requests %} + <tr> + <td>{% trans + user=join_request.user + group=groups_dict[join_request.object_id].name + %}{{ user }} wants to join group {{ group }}{% endtrans %} + </td> + <td>{# forms with accept and reject buttons #} + <form action="{% url moderate_group_join_request %}" method="post"> + <input + type="hidden" + name="request_id" + value="{{join_request.id}}" + /> + <input type="hidden" name="action" value="approve"/> + <input + class="btn" + type="submit" + value="{% trans %}Approve{% endtrans %}" + /> + </form> + <form action="{% url moderate_group_join_request %}" method="post"> + <input + type="hidden" + name="request_id" + value="{{join_request.id}}" + /> + <input type="hidden" name="action" value="deny"/> + <input + class="btn" + type="submit" + value="{% trans %}Deny{% endtrans %}" + /> + </form> + </td> + </tr> + {% endfor %} + <table> + data = { + 'active_tab':'users', + 'page_class': 'user-profile-page', + 'tab_name' : 'join_requests', + 'tab_description' : _('group joining requests'), + 'page_title' : _('profile - moderation'), + 'groups_dict': groups_dict, + 'join_requests': join_requests + } +{% endblock %} +{% block userjs %} +{% endblock %} diff --git a/askbot/skins/default/templates/user_profile/user_inbox.html b/askbot/skins/default/templates/user_profile/user_inbox.html index 9f3461f1..076f957c 100644 --- a/askbot/skins/default/templates/user_profile/user_inbox.html +++ b/askbot/skins/default/templates/user_profile/user_inbox.html @@ -45,6 +45,13 @@ inbox_section - forum|flags > {% trans %}flagged items ({{flag_count}}){% endtrans %} </a> + {% if join_requests_count %} | + <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=join_requests" + {% if inbox_section == 'join_requests' %}class="on"{% endif %} + > + {% trans %}group join requests{% endtrans %} + </a> + {% endif %} </div> {% endif %} <div id="re_tools"> diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py index b94e41a8..b9b7e3f4 100644 --- a/askbot/tests/db_api_tests.py +++ b/askbot/tests/db_api_tests.py @@ -401,7 +401,7 @@ class CommentTests(AskbotTestCase): comment = models.Post.objects.get_comments().get(id = self.comment.id) self.assertEquals(comment.score, 0) -class TagAndGroupTests(AskbotTestCase): +class GroupTests(AskbotTestCase): def setUp(self): self.u1 = self.create_user('u1') askbot_settings.update('GROUPS_ENABLED', True) @@ -443,7 +443,7 @@ class TagAndGroupTests(AskbotTestCase): self.assertEqual(c.groups.filter(name=group_name).exists(), True) def test_posts_added_to_private_group(self): - group = self.create_group(group_name='private', user=self.u1) + group = self.create_group(group_name='private') self.u1.join_group(group) q = self.post_question(user=self.u1, is_private=True) @@ -476,7 +476,7 @@ class TagAndGroupTests(AskbotTestCase): def test_making_public_question_private_works(self): question = self.post_question(user=self.u1) comment = self.post_comment(parent_post=question, user=self.u1) - group = self.create_group(group_name='private', user=self.u1) + group = self.create_group(group_name='private') self.u1.join_group(group) self.edit_question(question=question, user=self.u1, is_private=True) self.assertEqual(question.groups.count(), 2) @@ -489,7 +489,7 @@ class TagAndGroupTests(AskbotTestCase): question = self.post_question(user=self.u1) answer = self.post_answer(question=question, user=self.u1) comment = self.post_comment(parent_post=answer, user=self.u1) - group = self.create_group(group_name='private', user=self.u1) + group = self.create_group(group_name='private') self.u1.join_group(group) self.edit_answer(user=self.u1, answer=answer, is_private=True) self.assertEqual(answer.groups.count(), 2) @@ -502,7 +502,7 @@ class TagAndGroupTests(AskbotTestCase): question = self.post_question(self.u1) u2 = self.create_user('u2') - group = self.create_group(group_name='private', user=u2) + group = self.create_group(group_name='private') u2.join_group(group) answer = self.post_answer(question=question, user=u2, is_private=True) @@ -521,7 +521,7 @@ class TagAndGroupTests(AskbotTestCase): def test_thread_answer_count_for_multiple_groups(self): question = self.post_question(self.u1) - group = self.create_group(group_name='private', user=self.u1) + group = self.create_group(group_name='private') self.u1.join_group(group) answer = self.post_answer(question=question, user=self.u1) answer.add_to_groups((group,)) @@ -529,7 +529,7 @@ class TagAndGroupTests(AskbotTestCase): self.assertEqual(answer.thread.posts.get_answers(self.u1).count(), 1) def test_thread_make_public_recursive(self): - private_group = self.create_group(group_name='private', user=self.u1) + private_group = self.create_group(group_name='private') self.u1.join_group(private_group) data = self.post_question_answer_and_comments(is_private=True) @@ -553,7 +553,7 @@ class TagAndGroupTests(AskbotTestCase): def test_thread_add_to_groups_recursive(self): data = self.post_question_answer_and_comments() - private_group = self.create_group(group_name='private', user=self.u1) + private_group = self.create_group(group_name='private') thread = data['thread'] thread.add_to_groups([private_group], recursive=True) @@ -566,24 +566,46 @@ class TagAndGroupTests(AskbotTestCase): self.assertObjectGroupsEqual(data['answer_comment'], groups) def test_private_thread_is_invisible_to_anonymous_user(self): - group = self.create_group(group_name='private', user=self.u1) + group = self.create_group(group_name='private') self.u1.join_group(group) self.post_question(user=self.u1, is_private=True) visible_threads = models.Thread.objects.get_visible(AnonymousUser()) self.assertEqual(visible_threads.count(), 0) -class GroupTests(AskbotTestCase): - - def setUp(self): - self.u1 = self.create_user('user1') - def test_join_group(self): #create group group = models.Group(name='somegroup') + group.openness = models.Group.OPEN group.save() #join + self.u1 = self.create_user('user1') self.u1.join_group(group) #assert membership of askbot group object found_count = self.u1.get_groups().filter(name='somegroup').count() self.assertEqual(found_count, 1) + + def test_group_moderation(self): + #create group + group = models.Group(name='somegroup') + #make it moderated + group.openness = models.Group.MODERATED + group.save() + + #add moderator to the group + mod = self.create_user('mod', status='d') + mod.join_group(group) + + #create a regular user + reg = self.create_user('reg') + reg.join_group(group) + #assert that moderator has a notification + acts = models.Activity.objects.filter( + user=reg, + activity_type=const.TYPE_ACTIVITY_ASK_TO_JOIN_GROUP, + object_id=group.id + ) + self.assertEqual(acts.count(), 1) + self.assertEqual(acts[0].recipients.count(), 1) + recipient = acts[0].recipients.all()[0] + self.assertEqual(recipient, mod) diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py index edbbd7bb..c5ad153f 100644 --- a/askbot/tests/post_model_tests.py +++ b/askbot/tests/post_model_tests.py @@ -441,7 +441,8 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase): 'thread': q.thread, 'question': q, 'search_state': DummySearchState(), - } + 'visitor': None + } html = get_template('widgets/question_summary.html').render(context) return html @@ -595,6 +596,8 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase): self.assertEqual(html, thread.get_cached_summary_html()) def test_view_count(self): + import pdb + pdb.set_trace() question = self.post_question() self.assertEqual(0, question.thread.view_count) self.assertEqual(0, Thread.objects.all()[0].view_count) diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py index eea9b96b..ed83d274 100644 --- a/askbot/tests/utils.py +++ b/askbot/tests/utils.py @@ -251,8 +251,10 @@ class AskbotTestCase(TestCase): tag.save() return tag - def create_group(self, group_name=None, user=None): - return models.Group.objects.get_or_create(group_name='private') + def create_group(self, group_name=None, openness=models.Group.OPEN): + return models.Group.objects.get_or_create( + name='private', openness=openness + ) def post_comment( self, diff --git a/askbot/urls.py b/askbot/urls.py index 3ca81e8f..d69db744 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -87,6 +87,11 @@ urlpatterns = patterns('', name='get_thread_shared_groups' ), url( + r'^moderate-group-join-request/', + views.commands.moderate_group_join_request, + name='moderate_group_join_request' + ), + url( r'^save-draft-question/', views.commands.save_draft_question, name = 'save_draft_question' diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 156d4ff1..4bd5a3b7 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -1308,3 +1308,8 @@ def share_question_with_user(request): error_message = _('Sorry, looks like sharing request was invalid') request.user.message_set.create(message=error_message) return HttpResponseRedirect(thread.get_absolute_url()) + +@csrf.csrf_protect +def moderate_group_join_request(request): + """moderator of the group can accept or reject a new user""" + pass diff --git a/askbot/views/users.py b/askbot/views/users.py index e55c0cc3..357ec4f5 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -671,6 +671,38 @@ def user_recent(request, user, context): context.update(data) return render_into_skin('user_profile/user_recent.html', context, request) +#not a view - no direct url route here, called by `user_responses` +def show_group_join_requests(request, user, context): + """show group join requests to admins who belong to the group""" + if request.user.is_administrator_or_moderator() is False: + raise Http404 + + #get group to which user belongs + groups = request.user.get_groups() + #construct a dictionary group id --> group object + #to avoid loading group via activity content object + groups_dict = dict([(group.id, group) for group in groups]) + + #get join requests for those groups + group_content_type = ContentType.objects.get_for_model(models.Group) + join_requests = models.Activity.objects.filter( + activity_type=const.TYPE_ACTIVITY_ASK_TO_JOIN_GROUP, + content_type=group_content_type, + object_id__in=groups_dict.keys() + ).order_by('-active_at') + data = { + 'active_tab':'users', + 'page_class': 'user-profile-page', + 'tab_name' : 'join_requests', + 'tab_description' : _('group joining requests'), + 'page_title' : _('profile - moderation'), + 'groups_dict': groups_dict, + 'join_requests': join_requests + } + context.update(data) + return render_into_skin('user_profile/group_join_requests.html', context, request) + + @owner_or_moderator_required def user_responses(request, user, context): """ @@ -700,6 +732,8 @@ def user_responses(request, user, context): const.TYPE_ACTIVITY_MODERATED_NEW_POST, const.TYPE_ACTIVITY_MODERATED_POST_EDIT ) + elif section == 'join_requests': + return show_group_join_requests(request, user, context) else: raise Http404 |