summaryrefslogtreecommitdiffstats
path: root/askbot
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-09-05 12:16:43 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-09-05 12:16:43 -0400
commit3f6ee303f6df25fad5618d113b5b401e707c4fe8 (patch)
treeac54052a64301d70adfbf97fd950154c5916aea6 /askbot
parenta11da46b667d619c55d41294fed12f5b1b9e3c8f (diff)
downloadaskbot-3f6ee303f6df25fad5618d113b5b401e707c4fe8.tar.gz
askbot-3f6ee303f6df25fad5618d113b5b401e707c4fe8.tar.bz2
askbot-3f6ee303f6df25fad5618d113b5b401e707c4fe8.zip
a part of "ask to join group" moderation
Diffstat (limited to 'askbot')
-rw-r--r--askbot/const/__init__.py1
-rw-r--r--askbot/models/__init__.py34
-rw-r--r--askbot/models/user.py8
-rw-r--r--askbot/skins/default/templates/user_profile/group_join_requests.html108
-rw-r--r--askbot/skins/default/templates/user_profile/user_inbox.html7
-rw-r--r--askbot/tests/db_api_tests.py50
-rw-r--r--askbot/tests/post_model_tests.py5
-rw-r--r--askbot/tests/utils.py6
-rw-r--r--askbot/urls.py5
-rw-r--r--askbot/views/commands.py5
-rw-r--r--askbot/views/users.py34
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&section=forum"
+ {% if inbox_section == 'forum' %}class="on"{% endif %}
+ >
+ {% trans %}forum responses ({{re_count}}){% endtrans -%}
+ </a> |
+ <a href="{{request.user.get_absolute_url()}}?sort=inbox&section=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&section=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&section=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