2
2
3
3
from __future__ import absolute_import
4
4
from django .contrib import admin
5
+ from django .contrib import messages
6
+ from django .contrib .admin .actions import delete_selected
7
+ from django .utils .translation import ugettext_lazy as _
5
8
from guardian .admin import GuardedModelAdmin
6
9
10
+ from readthedocs .core .models import UserProfile
11
+ from readthedocs .core .utils import broadcast
7
12
from readthedocs .builds .models import Version
8
13
from readthedocs .redirects .models import Redirect
9
14
from readthedocs .notifications .views import SendNotificationView
10
15
11
16
from .notifications import ResourceUsageNotification
12
17
from .models import (Project , ImportedFile ,
13
18
ProjectRelationship , EmailHook , WebHook , Domain )
19
+ from .tasks import remove_dir
14
20
15
21
16
22
class ProjectSendNotificationView (SendNotificationView ):
@@ -65,20 +71,45 @@ class DomainInline(admin.TabularInline):
65
71
# return instance.click_ratio * 100
66
72
67
73
74
+ class ProjectOwnerBannedFilter (admin .SimpleListFilter ):
75
+
76
+ """Filter for projects with banned owners
77
+
78
+ There are problems adding `users__profile__banned` to the `list_filter`
79
+ attribute, so we'll create a basic filter to capture banned owners.
80
+ """
81
+
82
+ title = 'project owner banned'
83
+ parameter_name = 'project_owner_banned'
84
+
85
+ OWNER_BANNED = 'true'
86
+
87
+ def lookups (self , request , model_admin ):
88
+ return (
89
+ (self .OWNER_BANNED , _ ('Yes' )),
90
+ )
91
+
92
+ def queryset (self , request , queryset ):
93
+ if self .value () == self .OWNER_BANNED :
94
+ return queryset .filter (users__profile__banned = True )
95
+ return queryset
96
+
97
+
68
98
class ProjectAdmin (GuardedModelAdmin ):
69
99
70
100
"""Project model admin view"""
71
101
72
102
prepopulated_fields = {'slug' : ('name' ,)}
73
103
list_display = ('name' , 'repo' , 'repo_type' , 'allow_comments' , 'featured' , 'theme' )
74
104
list_filter = ('repo_type' , 'allow_comments' , 'featured' , 'privacy_level' ,
75
- 'documentation_type' , 'programming_language' )
105
+ 'documentation_type' , 'programming_language' ,
106
+ ProjectOwnerBannedFilter )
76
107
list_editable = ('featured' ,)
77
108
search_fields = ('slug' , 'repo' )
78
109
inlines = [ProjectRelationshipInline , RedirectInline ,
79
110
VersionInline , DomainInline ]
80
111
raw_id_fields = ('users' , 'main_language_project' )
81
- actions = ['send_owner_email' ]
112
+ actions = ['send_owner_email' , 'ban_owner' ]
82
113
83
114
def send_owner_email (self , request , queryset ):
84
115
view = ProjectSendNotificationView .as_view (
@@ -88,6 +119,51 @@ def send_owner_email(self, request, queryset):
88
119
89
120
send_owner_email .short_description = 'Notify project owners'
90
121
122
+ def ban_owner (self , request , queryset ):
123
+ """Ban project owner
124
+
125
+ This will only ban single owners, because a malicious user could add a
126
+ user as a co-owner of the project. We don't want to induce and
127
+ collatoral damage when flagging users.
128
+ """
129
+ total = 0
130
+ for project in queryset :
131
+ if project .users .count () == 1 :
132
+ count = (UserProfile .objects
133
+ .filter (user__projects = project )
134
+ .update (banned = True ))
135
+ total += count
136
+ else :
137
+ messages .add_message (request , messages .ERROR ,
138
+ 'Project has multiple owners: {0}' .format (project ))
139
+ if total == 0 :
140
+ messages .add_message (request , messages .ERROR , 'No users banned' )
141
+ else :
142
+ messages .add_message (request , messages .INFO ,
143
+ 'Banned {0} user(s)' .format (total ))
144
+
145
+ ban_owner .short_description = 'Ban project owner'
146
+
147
+ def delete_selected_and_artifacts (self , request , queryset ):
148
+ """Remove HTML/etc artifacts from application instances
149
+
150
+ Prior to the query delete, broadcast tasks to delete HTML artifacts from
151
+ application instances.
152
+ """
153
+ if request .POST .get ('post' ):
154
+ for project in queryset :
155
+ broadcast (type = 'app' , task = remove_dir , args = [project .doc_path ])
156
+ return delete_selected (self , request , queryset )
157
+
158
+ def get_actions (self , request ):
159
+ actions = super (ProjectAdmin , self ).get_actions (request )
160
+ actions ['delete_selected' ] = (
161
+ self .__class__ .delete_selected_and_artifacts ,
162
+ 'delete_selected' ,
163
+ delete_selected .short_description
164
+ )
165
+ return actions
166
+
91
167
92
168
class ImportedFileAdmin (admin .ModelAdmin ):
93
169
0 commit comments