extensions-website/ratings/admin.py
Anna Sirota caae613747 Make it possible to fully delete unlisted/unrated extensions and versions (#81)
* removes all soft-deletion;
* shows a "Delete extension" button on the draft page in case it can be deleted;
* shows a "Delete version" button on the version page in case it can be deleted;
* a version can be deleted if
  * its file isn't approved, and it doesn't have any ratings;
* an extension can be deleted if
  * it's not listed, and doesn't have any ratings or abuse reports;
  * all it's versions can also be deleted;
* changes default `File.status` from `APPROVED` to `AWAITING_REVIEW`
  With version's file status being `APPROVED` by default, a version can never be deleted, even when the extension is still a draft.
  This change doesn't affect the approval process because
   * when an extension is approved its latest version becomes approved automatically (no change here);
   * when a new version is uploaded to an approved extension, it's approved automatically (this is new).

This allows authors to delete their drafts, freeing the extension slug and making it possible to re-upload the same file.
This also makes it possible to easily fix mistakes during the drafting of a new extension (e.g. delete a version and re-upload it without bumping a version for each typo/mistake in packaging and so on).
(see #78 and #63)

Reviewed-on: #81
2024-04-19 11:00:13 +02:00

97 lines
2.7 KiB
Python

from django.contrib import admin
from django.db.models import Prefetch
from django.template.defaultfilters import truncatechars
from extensions.models import Version
from .models import Rating
class RatingTypeFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = 'Type'
# Parameter for the filter that will be used in the URL query.
parameter_name = 'type'
def lookups(self, request, model_admin):
"""Return a list of lookup option tuples.
The first element in each tuple is the coded value
for the option that will appear in the URL query.
The second element is the human-readable name for
the option that will appear in the right sidebar.
"""
return (
('rating', 'User Rating'),
('reply', 'Developer/Admin Reply'),
)
def queryset(self, request, queryset):
"""Return the filtered queryset.
Filter based on the value provided in the query string
and retrievable via `self.value()`.
"""
if self.value() == 'rating':
return queryset.filter(reply_to__isnull=True)
elif self.value() == 'reply':
return queryset.filter(reply_to__isnull=False)
return queryset
class RatingAdmin(admin.ModelAdmin):
date_hierarchy = 'date_created'
search_fields = ('extension__slug', 'extension__name', 'user__email', 'user__full_name')
raw_id_fields = (
'extension',
'version',
'user',
'reply_to',
)
readonly_fields = (
'date_created',
'date_modified',
'extension',
'version',
'text',
'score',
'user',
)
fields = ('status',) + readonly_fields
list_display = (
'date_created',
'user',
'ip_address',
'score',
'is_reply',
'status',
'truncated_text',
)
list_filter = ('status', RatingTypeFilter, 'score')
actions = ('delete_selected',)
list_select_related = ('user',) # For extension/reply_to see get_queryset()
def get_queryset(self, request):
base_qs = Rating.objects.all()
return base_qs.prefetch_related(
Prefetch('version', queryset=Version.objects.all()),
Prefetch('reply_to', queryset=base_qs),
)
def has_add_permission(self, request):
return False
def truncated_text(self, obj):
return truncatechars(obj.text, 140) if obj.text else ''
def is_reply(self, obj):
return bool(obj.reply_to)
is_reply.boolean = True
is_reply.admin_order_field = 'reply_to'
admin.site.register(Rating, RatingAdmin)