# HG changeset patch # User Brian Neal # Date 1260691876 0 # Node ID 152d77265da66dd2dc6fe1d30aea344a61e18d26 # Parent 023132c9002186524746a46e24c9c9ba564c0787 Implement #38: add function to mark user as a spammer. Display only active members on member list. Display login form as table (not sure why wasn't doing this before). diff -r 023132c90021 -r 152d77265da6 gpp/bio/admin.py --- a/gpp/bio/admin.py Wed Dec 09 22:58:05 2009 +0000 +++ b/gpp/bio/admin.py Sun Dec 13 08:11:16 2009 +0000 @@ -1,15 +1,92 @@ """ This file contains the admin definitions for the bio application. """ +import datetime from django.contrib import admin from bio.models import UserProfile from bio.models import UserProfileFlag +import bio.models +from comments.models import Comment +from forums.tools import delete_user_posts + class UserProfileAdmin(admin.ModelAdmin): search_fields = ('user__username', 'user__first_name', 'user__last_name', 'user__email') exclude = ('profile_html', 'signature_html') + list_display = ('__unicode__', 'get_status_display', 'status_date') + list_filter = ('status', ) + date_hierarchy = 'status_date' + actions = ( + 'mark_active', + 'mark_resigned', + 'mark_removed', + 'mark_suspended', + 'mark_spammer', + ) + + def get_status_display(self, obj): + return obj.get_status_display() + get_status_display.short_description = 'Status' + + def mark_user_status(self, request, qs, status): + """ + Common code for the admin actions. Updates the status field in the + profiles to 'status'. Updates the status_date. Sets the is_active + field to True if the status is STA_ACTIVE and False otherwise. + """ + now = datetime.datetime.now() + for profile in qs: + profile.user.is_active = status == bio.models.STA_ACTIVE + profile.user.save() + profile.status = status + profile.status_date = now + profile.save() + + count = qs.count() + msg = "1 user" if count == 1 else "%d users" % count + self.message_user(request, "%s successfully marked as %s." % (msg, + bio.models.USER_STATUS_CHOICES[status][1])) + + def mark_active(self, request, qs): + """ + Marks users as active. Updates their profile status to STA_ACTIVE. + """ + self.mark_user_status(request, qs, bio.models.STA_ACTIVE) + mark_active.short_description = "Mark selected users as active" + + def mark_resigned(self, request, qs): + """ + Marks users as inactive. Updates their profile status to STA_RESIGNED. + """ + self.mark_user_status(request, qs, bio.models.STA_RESIGNED) + mark_resigned.short_description = "Mark selected users as resigned" + + def mark_removed(self, request, qs): + """ + Marks users as inactive. Updates their profile status to STA_REMOVED. + """ + self.mark_user_status(request, qs, bio.models.STA_REMOVED) + mark_removed.short_description = "Mark selected users as removed" + + def mark_suspended(self, request, qs): + """ + Marks users as inactive. Updates their profile status to STA_SUSPENDED. + """ + self.mark_user_status(request, qs, bio.models.STA_SUSPENDED) + mark_suspended.short_description = "Mark selected users as suspended" + + def mark_spammer(self, request, qs): + """ + Marks users as inactive. Updates their profile status to STA_SPAMMER. + Deletes all their comments and forum posts. + """ + self.mark_user_status(request, qs, bio.models.STA_SPAMMER) + for profile in qs: + Comment.objects.filter(user=profile.user).delete() + delete_user_posts(profile.user) + mark_spammer.short_description = "Mark selected users as spammers" class UserProfileFlagAdmin(admin.ModelAdmin): diff -r 023132c90021 -r 152d77265da6 gpp/bio/models.py --- a/gpp/bio/models.py Wed Dec 09 22:58:05 2009 +0000 +++ b/gpp/bio/models.py Sun Dec 13 08:11:16 2009 +0000 @@ -13,6 +13,16 @@ from core.markup import SiteMarkup +(STA_ACTIVE, STA_RESIGNED, STA_REMOVED, STA_SUSPENDED, STA_SPAMMER) = range(5) + +USER_STATUS_CHOICES = ( + (STA_ACTIVE, "Active"), + (STA_RESIGNED, "Resigned"), + (STA_REMOVED, "Removed"), + (STA_SUSPENDED, "Suspended"), + (STA_SPAMMER, "Spammer"), +) + def avatar_file_path_for_user(username, filename): return os.path.join(settings.AVATAR_DIR, 'users', username, filename) @@ -39,6 +49,9 @@ default='US/Pacific') use_24_time = models.BooleanField(default=False) forum_post_count = models.IntegerField(default=0) + status = models.IntegerField(default=STA_ACTIVE, + choices=USER_STATUS_CHOICES) + status_date = models.DateTimeField(auto_now_add=True) def __unicode__(self): return self.user.username diff -r 023132c90021 -r 152d77265da6 gpp/bio/views.py --- a/gpp/bio/views.py Wed Dec 09 22:58:05 2009 +0000 +++ b/gpp/bio/views.py Sun Dec 13 08:11:16 2009 +0000 @@ -30,12 +30,17 @@ @login_required def member_list(request, type='user', page=1): + """ + This view displays the member list. Only active members are displayed. + """ + qs = auth.models.User.objects.filter(is_active=True) if type == 'user': - users = auth.models.User.objects.all().order_by('username') + qs = qs.order_by('username') else: - users = auth.models.User.objects.all().order_by('date_joined') + qs = qs.order_by('date_joined') + num_members = qs.count() - paginator = DiggPaginator(users, 10, body=5, tail=3, margin=3, padding=2) + paginator = DiggPaginator(qs, 20, body=5, tail=3, margin=3, padding=2) try: the_page = paginator.page(int(page)) except InvalidPage: @@ -54,6 +59,7 @@ return render_to_response('bio/members.html', { 'page': the_page, 'type': type, + 'num_members': num_members, }, context_instance = RequestContext(request)) diff -r 023132c90021 -r 152d77265da6 gpp/forums/tools.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/forums/tools.py Sun Dec 13 08:11:16 2009 +0000 @@ -0,0 +1,69 @@ +""" +This module contains misc. utility functions for forum management. +""" +from forums.models import Post, Topic, Forum, ForumLastVisit, TopicLastVisit + + +def delete_user_posts(user): + """ + This function deletes all the posts for a given user. + It also cleans up any last visit database records for the user. + This function adjusts the last post foreign keys before deleting + the posts to avoid the cascading delete behavior. + """ + posts = Post.objects.filter(user=user).select_related() + + # build a set of topics and forums affected by the post deletions + + topics = set(post.topic for post in posts) + forums = set(topic.forum for topic in topics) + + post_ids = [post.pk for post in posts] + pending_delete = [] + + for topic in topics: + if topic.last_post.pk in post_ids: + topic_posts = Post.objects.filter(topic=topic).exclude( + pk__in=post_ids) + topic.post_count = topic_posts.count() + if topic.post_count > 0: + topic.last_post = topic_posts.latest() + topic.update_date = topic.last_post.creation_date + topic.save() + else: + # Topic should be deleted, it has no posts; + # We can't delete it now as it could cascade and take out a + # forum. Remember it for later deletion. + pending_delete.append(topic) + + for forum in forums: + if forum.last_post.pk in post_ids: + forum_posts = Post.objects.filter(topic__forum=forum).exclude( + pk__in=post_ids) + forum.post_count = forum_posts.count() + if forum.post_count > 0: + forum.last_post = forum_posts.latest() + else: + forum.last_post = None + forum.save() + + # Delete pending topics now because forums have just adjusted their + # foreign keys into Post + if pending_delete: + topic_ids = [topic.pk for topic in pending_delete] + Topic.objects.filter(pk__in=topic_ids).delete() + + # Topics have been deleted, re-compute topic counts for forums + for forum in forums: + forum.topic_count = Topic.objects.filter(forum=forum).count() + forum.save() + + # All foreign keys are accounted for, we can now delete the posts in bulk. + # Since some posts in our original queryset may have been deleted already, + # run a new query (although it may be ok) + Post.objects.filter(pk__in=post_ids).delete() + + # delete all the last visit records for this user + TopicLastVisit.objects.filter(user=user).delete() + ForumLastVisit.objects.filter(user=user).delete() + diff -r 023132c90021 -r 152d77265da6 gpp/forums/views.py --- a/gpp/forums/views.py Wed Dec 09 22:58:05 2009 +0000 +++ b/gpp/forums/views.py Sun Dec 13 08:11:16 2009 +0000 @@ -325,13 +325,22 @@ if not can_delete: return HttpResponseForbidden("You don't have permission to delete that post.") + delete_single_post(post) + return HttpResponse("The post has been deleted.") + + +def delete_single_post(post): + """ + This function deletes a single post. It handles the case of where + a post is the sole post in a topic by deleting the topic also. It + adjusts any foreign keys in Topic or Forum objects that might be pointing + to this post before deleting the post to avoid a cascading delete. + """ if post.topic.post_count == 1 and post == post.topic.last_post: _delete_topic(post.topic) else: _delete_post(post) - return HttpResponse("The post has been deleted.") - def _delete_post(post): """ @@ -371,7 +380,7 @@ Note that we don't bother adjusting all the users' post counts as that doesn't seem to be worth the effort. """ - if topic.forum.last_post.topic == topic: + if topic.forum.last_post and topic.forum.last_post.topic == topic: topic.forum.last_post_pre_delete() topic.forum.save() diff -r 023132c90021 -r 152d77265da6 gpp/templates/accounts/login.html --- a/gpp/templates/accounts/login.html Wed Dec 09 22:58:05 2009 +0000 +++ b/gpp/templates/accounts/login.html Sun Dec 13 08:11:16 2009 +0000 @@ -2,19 +2,19 @@ {% block title %}Login{% endblock %} {% block content %}

Login

-{% if form.errors %} -

Your username and password didn't match. Please try again.

-{% endif %}
- - - +{{ form.as_table }} +
{{ form.username.label_tag }}:{{ form.username }}
{{ form.password.label_tag }}:{{ form.password }}
  -
 
+
-

Forgot your password? You can reset it here.

-

Don't have an account? Why don't you register?

+ + {% endblock %} diff -r 023132c90021 -r 152d77265da6 gpp/templates/bio/members.html --- a/gpp/templates/bio/members.html Wed Dec 09 22:58:05 2009 +0000 +++ b/gpp/templates/bio/members.html Sun Dec 13 08:11:16 2009 +0000 @@ -7,7 +7,7 @@ {% endblock %} {% block content %}

Member List

- +

Surfguitar101.com currently has {{ num_members }} active members.

{% if page.object_list %}