1
1
"""Django administration interface for `projects.models`."""
2
2
3
+ from django .db .models import Sum
4
+ from django .conf import settings
3
5
from django .contrib import admin , messages
4
6
from django .contrib .admin .actions import delete_selected
5
7
from django .forms import BaseInlineFormSet
@@ -143,42 +145,90 @@ def queryset(self, request, queryset):
143
145
return queryset
144
146
145
147
148
+ class ProjectSpamThreshold (admin .SimpleListFilter ):
149
+
150
+ """Filter for projects that are potentially SPAM."""
151
+
152
+ title = 'Spam Threshold'
153
+ parameter_name = 'spam_threshold'
154
+
155
+ DONT_SHOW_ADS = 'dont_show_ads'
156
+ DENY_ON_ROBOTS = 'deny_on_robots'
157
+ DONT_SERVE_DOCS = 'dont_serve_docs'
158
+ DONT_SHOW_DASHBOARD = 'dont_show_dashboard'
159
+ DELETE_PROJECT = 'delete_project'
160
+
161
+ def lookups (self , request , model_admin ):
162
+ return (
163
+ (self .DONT_SHOW_ADS , _ ("Don't show Ads" )),
164
+ (self .DENY_ON_ROBOTS , _ ('Deny on robots' )),
165
+ (self .DONT_SERVE_DOCS , _ ("Don't serve docs" )),
166
+ (self .DONT_SHOW_DASHBOARD , _ ("Don't show dashboard" )),
167
+ (self .DELETE_PROJECT , _ ('Delete project' )),
168
+ )
169
+
170
+ def queryset (self , request , queryset ):
171
+ queryset = queryset .annotate (spam_score = Sum ('spam_rules__value' ))
172
+ if self .value () == self .DONT_SHOW_ADS :
173
+ return queryset .filter (spam_score__gte = settings .RTD_SPAM_THRESHOLD_DONT_SHOW_ADS )
174
+ if self .value () == self .DENY_ON_ROBOTS :
175
+ return queryset .filter (spam_score__gte = settings .RTD_SPAM_THRESHOLD_DENY_ON_ROBOTS )
176
+ if self .value () == self .DONT_SERVE_DOCS :
177
+ return queryset .filter (spam_score__gte = settings .RTD_SPAM_THRESHOLD_DONT_SERVE_DOCS )
178
+ if self .value () == self .DONT_SHOW_DASHBOARD :
179
+ return queryset .filter (spam_score__gte = settings .RTD_SPAM_THRESHOLD_DONT_SHOW_DASHBOARD )
180
+ if self .value () == self .DELETE_PROJECT :
181
+ return queryset .filter (spam_score__gte = settings .RTD_SPAM_THRESHOLD_DELETE_PROJECT )
182
+ return queryset
183
+
184
+
146
185
class ProjectAdmin (ExtraSimpleHistoryAdmin ):
147
186
148
187
"""Project model admin view."""
149
188
150
189
prepopulated_fields = {'slug' : ('name' ,)}
151
- list_display = ('name' , 'slug' , 'repo' , 'repo_type' , 'featured' )
152
- list_filter = (
190
+ list_display = ('name' , 'slug' , 'repo' )
191
+
192
+ list_filter = tuple ()
193
+ if 'readthedocsext.spamfighting' in settings .INSTALLED_APPS :
194
+ list_filter = list_filter + (ProjectSpamThreshold ,)
195
+
196
+ list_filter = list_filter + (
197
+ ProjectOwnerBannedFilter ,
198
+ 'feature__feature_id' ,
153
199
'repo_type' ,
154
- 'featured' ,
155
200
'privacy_level' ,
156
- 'documentation_type' ,
157
201
'programming_language' ,
158
- 'feature__feature_id' ,
159
- ProjectOwnerBannedFilter ,
202
+ 'documentation_type' ,
160
203
)
161
- list_editable = ( 'featured' ,)
204
+
162
205
search_fields = ('slug' , 'repo' )
163
206
inlines = [
164
207
ProjectRelationshipInline ,
165
208
RedirectInline ,
166
209
VersionInline ,
167
210
DomainInline ,
168
211
]
169
- readonly_fields = ('pub_date' , 'feature_flags' ,)
212
+ readonly_fields = ('pub_date' , 'feature_flags' , 'matching_spam_rules' )
170
213
raw_id_fields = ('users' , 'main_language_project' , 'remote_repository' )
171
214
actions = [
172
215
'send_owner_email' ,
173
216
'ban_owner' ,
217
+ 'run_spam_rule_checks' ,
174
218
'build_default_version' ,
175
219
'reindex_active_versions' ,
176
220
'wipe_all_versions' ,
177
221
'import_tags_from_vcs' ,
178
222
]
179
223
224
+ def matching_spam_rules (self , obj ):
225
+ result = []
226
+ for spam_rule in obj .spam_rules .filter (enabled = True ):
227
+ result .append (f'{ spam_rule .spam_rule_type } ({ spam_rule .value } )' )
228
+ return '\n ' .join (result ) or 'No matching spam rules'
229
+
180
230
def feature_flags (self , obj ):
181
- return ', ' .join ([str (f .get_feature_display ()) for f in obj .features ])
231
+ return '\n ' .join ([str (f .get_feature_display ()) for f in obj .features ])
182
232
183
233
def send_owner_email (self , request , queryset ):
184
234
view = ProjectSendNotificationView .as_view (
@@ -188,6 +238,26 @@ def send_owner_email(self, request, queryset):
188
238
189
239
send_owner_email .short_description = 'Notify project owners'
190
240
241
+ def run_spam_rule_checks (self , request , queryset ):
242
+ """Run all the spam checks on this project."""
243
+ if 'readthedocsext.spamfighting' not in settings .INSTALLED_APPS :
244
+ messages .add_message (
245
+ request ,
246
+ messages .ERROR ,
247
+ 'Spam fighting Django application not installed' ,
248
+ )
249
+ return
250
+
251
+ from readthedocsext .spamfighting .tasks import spam_rules_check # noqa
252
+ project_slugs = queryset .values_list ('slug' , flat = True )
253
+ # NOTE: convert queryset to a simple list so Celery can serialize it
254
+ spam_rules_check .delay (project_slugs = list (project_slugs ))
255
+ messages .add_message (
256
+ request ,
257
+ messages .INFO ,
258
+ 'Spam check task triggered for {} projects' .format (queryset .count ()),
259
+ )
260
+
191
261
def ban_owner (self , request , queryset ):
192
262
"""
193
263
Ban project owner.
0 commit comments