Anna Sirota
1de50ba72e
The following was fixed: RecordModificationMixin used to presume the record's fields are changing even when `update_fields` imply they aren't. Admin now shows the list of `LogEntry`s, and links to it in addition to the `History` tab.
695 lines
22 KiB
Python
695 lines
22 KiB
Python
from typing import List, Tuple, Any
|
|
|
|
from django import forms
|
|
from django.contrib import admin
|
|
from django.contrib import messages
|
|
from django.contrib.admin.filters import SimpleListFilter
|
|
from django.contrib.admin.models import LogEntry, DELETION
|
|
from django.contrib.admin.options import IncorrectLookupParameters
|
|
from django.contrib.admin.views.main import ChangeList
|
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
|
from django.contrib.flatpages.admin import FlatPageAdmin
|
|
from django.contrib.flatpages.models import FlatPage
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.files.uploadedfile import UploadedFile
|
|
from django.db import models as django_models
|
|
from django.db.models import QuerySet, Sum, F, Value, Count, OuterRef, Subquery
|
|
from django.db.models.functions import Coalesce
|
|
from django.http import HttpRequest
|
|
from django.http.response import HttpResponse, HttpResponseBadRequest, JsonResponse
|
|
from django.shortcuts import render
|
|
from django.urls import path
|
|
from django.urls import reverse
|
|
from django.utils.encoding import force_str
|
|
from django.utils.html import escape
|
|
from django.utils.html import format_html
|
|
from django.utils.safestring import mark_safe, SafeText
|
|
from django.utils.translation import ngettext
|
|
from django.views.generic.base import View
|
|
from tinymce.widgets import AdminTinyMCE
|
|
|
|
from conference_main import models, permissions, widgets
|
|
from conference_main.forms import LocationForm
|
|
from conference_main.admin_mixins import ExportCsvMixin
|
|
|
|
admin.site.enable_nav_sidebar = False
|
|
|
|
|
|
def get_admin_change_path(obj=None, app_label=None, model_name=None, pk=None):
|
|
"""Return a path to the admin change page for a given object."""
|
|
if obj:
|
|
related_class = obj._meta.model
|
|
app_label = related_class._meta.app_label
|
|
model_name = related_class._meta.model_name
|
|
pk = obj.pk
|
|
if app_label and model_name and pk:
|
|
url_name = f'admin:{app_label}_{model_name}_change'
|
|
return reverse(url_name, kwargs={'object_id': pk})
|
|
|
|
|
|
def get_admin_change_url(obj=None, app_label=None, model_name=None, pk=None, text=''):
|
|
"""Return a link to the admin change page for a given object."""
|
|
url = get_admin_change_path(obj=obj, app_label=app_label, model_name=model_name, pk=pk)
|
|
if url:
|
|
text = repr(obj) if obj else text
|
|
return format_html(f'<a href="{url}">{text}</a>')
|
|
return ''
|
|
|
|
|
|
class PreFilteredListFilter(SimpleListFilter):
|
|
"""Reusable admin filtering.
|
|
|
|
Via Greg and JohnGalt on SO.
|
|
"""
|
|
|
|
# Either set this or override .get_default_value()
|
|
default_value = None
|
|
|
|
no_filter_value = 'all'
|
|
no_filter_name = 'All'
|
|
|
|
# Human-readable title which will be displayed in the
|
|
# right admin sidebar just above the filter options.
|
|
title = None
|
|
|
|
# Parameter for the filter that will be used in the URL query.
|
|
parameter_name = None
|
|
|
|
def get_default_value(self):
|
|
if self.default_value is not None:
|
|
return self.default_value
|
|
raise NotImplementedError(
|
|
'Either the .default_value attribute needs to be set or '
|
|
'the .get_default_value() method must be overridden to '
|
|
'return a URL query argument for parameter_name.'
|
|
)
|
|
|
|
def get_lookups(self) -> List[Tuple[Any, str]]:
|
|
"""
|
|
Returns a list of 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.
|
|
"""
|
|
raise NotImplementedError(
|
|
'The .get_lookups() method must be overridden to '
|
|
'return a list of tuples (value, verbose value).'
|
|
)
|
|
|
|
# Overriding parent class:
|
|
def lookups(self, request, model_admin) -> List[Tuple[Any, str]]:
|
|
return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups()
|
|
|
|
# Overriding parent class:
|
|
def queryset(self, request, queryset: QuerySet) -> QuerySet:
|
|
"""
|
|
Returns the filtered queryset based on the value
|
|
provided in the query string and retrievable via
|
|
`self.value()`.
|
|
"""
|
|
if self.value() is None:
|
|
return self.get_default_queryset(queryset)
|
|
if self.value() == self.no_filter_value:
|
|
return queryset.all()
|
|
return self.get_filtered_queryset(queryset)
|
|
|
|
def get_default_queryset(self, queryset: QuerySet) -> QuerySet:
|
|
return queryset.filter(**{self.parameter_name: self.get_default_value()})
|
|
|
|
def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet:
|
|
try:
|
|
return queryset.filter(**self.used_parameters)
|
|
except (ValueError, ValidationError) as e:
|
|
# Fields may raise a ValueError or ValidationError when converting
|
|
# the parameters to the correct type.
|
|
raise IncorrectLookupParameters(e)
|
|
|
|
# Overriding parent class:
|
|
def choices(self, changelist: ChangeList):
|
|
"""
|
|
Overridden to prevent the default "All".
|
|
"""
|
|
value = self.value() or force_str(self.get_default_value())
|
|
for lookup, title in self.lookup_choices:
|
|
yield {
|
|
'selected': value == force_str(lookup),
|
|
'query_string': changelist.get_query_string({self.parameter_name: lookup}),
|
|
'display': title,
|
|
}
|
|
|
|
|
|
class StatusFilter(PreFilteredListFilter):
|
|
default_value = models.Event.Statuses.ACCEPTED
|
|
title = 'status'
|
|
parameter_name = 'status__exact'
|
|
|
|
def get_lookups(self):
|
|
return [s for s in models.Event.Statuses.choices]
|
|
|
|
|
|
class EditionFilter(PreFilteredListFilter):
|
|
title = 'edition'
|
|
parameter_name = 'edition__id__exact'
|
|
|
|
def get_lookups(self):
|
|
return [(e.id, e.path) for e in models.Edition.objects.all().order_by('-path')]
|
|
|
|
def get_default_value(self):
|
|
return models.SiteSettings.objects.first().current_edition.id
|
|
|
|
|
|
class EventsAdminForm(forms.ModelForm):
|
|
"""Event Admin Form
|
|
|
|
Override the default admin form to filter the days available so
|
|
they match the current Edition.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if not self.instance.pk:
|
|
return
|
|
self.fields['day'].queryset = models.Day.objects.filter(edition=self.instance.edition)
|
|
|
|
|
|
@admin.register(models.Tag)
|
|
class TagsAdmin(admin.ModelAdmin):
|
|
search_fields = ['name']
|
|
prepopulated_fields = {'slug': ('name',)}
|
|
|
|
|
|
@admin.register(models.Location)
|
|
class LocationsAdmin(admin.ModelAdmin):
|
|
prepopulated_fields = {'slug': ('name',)}
|
|
form = LocationForm
|
|
list_display = ['name', 'slug', 'order', 'color']
|
|
|
|
|
|
class FavoriteInlineAdmin(admin.TabularInline):
|
|
model = models.Event.favorites.through
|
|
raw_id_fields = ['user']
|
|
verbose_name = 'Favorite'
|
|
verbose_name_plural = 'Favorites'
|
|
extra = 0
|
|
|
|
|
|
class AttendeeInlineAdmin(admin.TabularInline):
|
|
model = models.Event.attendees.through
|
|
raw_id_fields = ['user']
|
|
verbose_name = 'Attendee'
|
|
verbose_name_plural = 'Attendees'
|
|
extra = 0
|
|
|
|
|
|
class IsScheduledFilter(PreFilteredListFilter):
|
|
title = 'scheduled'
|
|
parameter_name = 'is_scheduled'
|
|
default_value = 'all'
|
|
|
|
def get_lookups(self):
|
|
return [
|
|
('Yes', 'Yes'),
|
|
('No', 'No'),
|
|
]
|
|
|
|
def queryset(self, request, queryset):
|
|
value = self.value()
|
|
if value == 'Yes':
|
|
return queryset.filter(day__isnull=False, time__isnull=False)
|
|
elif value == 'No':
|
|
return queryset.filter(day__isnull=True, time__isnull=True)
|
|
return queryset
|
|
|
|
|
|
@admin.register(models.Event)
|
|
class EventsAdmin(admin.ModelAdmin, ExportCsvMixin):
|
|
save_on_top = True
|
|
list_display = (
|
|
'id',
|
|
'name',
|
|
'get_speaker_links',
|
|
'category',
|
|
'location',
|
|
'get_tags',
|
|
'favorites_count',
|
|
'attendees_count',
|
|
'status',
|
|
'is_scheduled',
|
|
'show_link',
|
|
)
|
|
fields = (
|
|
'name',
|
|
'description',
|
|
'speakers',
|
|
'tags',
|
|
'picture',
|
|
'category',
|
|
'location',
|
|
'day',
|
|
'time',
|
|
'duration_minutes',
|
|
'website',
|
|
'recording',
|
|
'slides_url',
|
|
'proposal',
|
|
'user',
|
|
'status',
|
|
'edition',
|
|
)
|
|
inlines = [FavoriteInlineAdmin, AttendeeInlineAdmin]
|
|
actions = ['export_as_csv']
|
|
|
|
readonly_fields = ('proposal',)
|
|
|
|
list_filter = (EditionFilter, StatusFilter, IsScheduledFilter, 'category', 'location')
|
|
search_fields = ('name', 'description', 'speakers__full_name')
|
|
form = EventsAdminForm
|
|
|
|
formfield_overrides = {
|
|
django_models.CharField: {'widget': forms.TextInput(attrs={'size': '93'})},
|
|
}
|
|
|
|
def get_urls(self):
|
|
"""Return URLs of additional admin views, such as printable view."""
|
|
urls = super().get_urls()
|
|
model_name = f'{self.model._meta.app_label}_{self.model._meta.model_name}'
|
|
extra_urls = [
|
|
path(
|
|
'printable/',
|
|
self.printable_view,
|
|
name=f'{model_name}_printable',
|
|
),
|
|
]
|
|
return extra_urls + urls
|
|
|
|
def printable_view(self, request):
|
|
cl = self.get_changelist_instance(request)
|
|
|
|
return render(request, 'admin/printable_events.pug', {'events': cl.queryset})
|
|
|
|
def get_queryset(self, request):
|
|
queryset = super().get_queryset(request)
|
|
queryset = queryset.prefetch_related('tags')
|
|
queryset = queryset.annotate(
|
|
favorites_count=Count('favorites', distinct=True),
|
|
attendees_count=Count('attendees', distinct=True),
|
|
)
|
|
return queryset
|
|
|
|
def favorites_count(self, obj):
|
|
return obj.favorites_count
|
|
|
|
favorites_count.admin_order_field = 'favorites_count'
|
|
favorites_count.short_description = 'Favs'
|
|
|
|
def attendees_count(self, obj):
|
|
return obj.attendees_count
|
|
|
|
attendees_count.admin_order_field = 'attendees_count'
|
|
attendees_count.short_description = 'Going'
|
|
|
|
# TODO(fsiddi) Improve this function with better working and url construction
|
|
def show_link(self, obj):
|
|
return mark_safe('<a href="%s">Review</a>' % obj.get_review_url())
|
|
|
|
show_link.short_description = 'View'
|
|
|
|
def get_speaker_links(self, obj: models.Event) -> str:
|
|
if not obj.speakers.all():
|
|
return "-"
|
|
speaker_links = []
|
|
for profile in obj.speakers.all():
|
|
full_name: str = profile.full_name or profile.user.username
|
|
speaker_links.append(f"<a href='{obj.user.profile.get_absolute_url()}'>{full_name}</a>")
|
|
return format_html(", ".join(speaker_links))
|
|
|
|
get_speaker_links.short_description = 'Speaker(s)'
|
|
|
|
def get_tags(self, obj: models.Event) -> str:
|
|
tags = [t.name for t in obj.tags.all()]
|
|
return ', '.join(tags)
|
|
|
|
get_tags.short_description = 'Tags'
|
|
|
|
exclude = ('picture_height', 'picture_width', 'favorites')
|
|
autocomplete_fields = ['speakers', 'user', 'tags']
|
|
|
|
date_hierarchy = 'day__date'
|
|
|
|
# Enable "View on Site" button
|
|
view_on_site = True
|
|
|
|
def date(self, event: models.Event) -> str:
|
|
if not event.day:
|
|
return ''
|
|
return str(event.day.date)
|
|
|
|
def is_scheduled(self, event: models.Event) -> bool:
|
|
return True if event.day and event.time else False
|
|
|
|
is_scheduled.short_description = 'Scheduled'
|
|
is_scheduled.boolean = True
|
|
|
|
def save_model(self, request: Any, obj: models.Event, form: Any, change: Any) -> None:
|
|
if obj.status == obj.Statuses.ACCEPTED and not obj.duration_minutes:
|
|
messages.set_level(request, messages.ERROR)
|
|
messages.error(request, 'Could not save: Approved events must have a duration')
|
|
return
|
|
super(EventsAdmin, self).save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(models.Profile)
|
|
class ProfilesAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'id',
|
|
'full_name',
|
|
'user',
|
|
'company',
|
|
'country',
|
|
'is_public',
|
|
]
|
|
list_filter = [
|
|
'user__tickets__edition',
|
|
'user__tickets__is_paid',
|
|
'user__tickets__is_free',
|
|
'is_public',
|
|
]
|
|
readonly_fields = ['user']
|
|
search_fields = ['full_name', 'user__email', 'user__username', 'country']
|
|
|
|
# Enable "View on Site" button
|
|
view_on_site = True
|
|
|
|
|
|
class FestivalEntryFilter(PreFilteredListFilter):
|
|
title = 'edition'
|
|
parameter_name = 'edition__id__exact'
|
|
|
|
def get_lookups(self):
|
|
return [(e.id, e.path) for e in models.Edition.objects.all().order_by('-path')]
|
|
|
|
def get_default_value(self):
|
|
return models.SiteSettings.objects.first().current_edition.id
|
|
|
|
|
|
class FestivalEntryVotesAdmin(admin.TabularInline):
|
|
model = models.FestivalEntryVotes
|
|
readonly_fields = ['user', 'rating']
|
|
extra = 0
|
|
verbose_name = 'Vote'
|
|
verbose_name_plural = 'Votes'
|
|
classes = ['collapse']
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def has_add_permission(self, request, obj=None):
|
|
return False
|
|
|
|
|
|
class FestivalEntryFinalVotesAdmin(admin.TabularInline):
|
|
model = models.FestivalEntryFinalVotes
|
|
readonly_fields = ['user', 'points']
|
|
extra = 0
|
|
verbose_name = 'Final Vote'
|
|
verbose_name_plural = 'Final Votes'
|
|
classes = ['collapse']
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def has_add_permission(self, request, obj=None):
|
|
return False
|
|
|
|
|
|
@admin.register(models.FestivalEntry)
|
|
class FestivalEntryAdmin(admin.ModelAdmin):
|
|
save_on_top = True
|
|
inlines = [FestivalEntryVotesAdmin, FestivalEntryFinalVotesAdmin]
|
|
list_display = (
|
|
'title',
|
|
'show_link',
|
|
'user',
|
|
'category',
|
|
'status',
|
|
'popularity',
|
|
'score',
|
|
'has_thumbnail',
|
|
)
|
|
list_filter = (FestivalEntryFilter, 'status', 'category')
|
|
search_fields = ('title', 'description')
|
|
actions = ['make_accepted']
|
|
|
|
autocomplete_fields = ['user']
|
|
|
|
# Enable "View on Site" button
|
|
view_on_site = True
|
|
|
|
@admin.action(description='Mark selected entries as accepted')
|
|
def make_accepted(self, request, queryset):
|
|
updated = queryset.update(status='accepted')
|
|
self.message_user(
|
|
request,
|
|
ngettext(
|
|
'%d entry was successfully marked as accepted.',
|
|
'%d entries were successfully marked as accepted.',
|
|
updated,
|
|
)
|
|
% updated,
|
|
messages.SUCCESS,
|
|
)
|
|
|
|
def get_queryset(self, request: HttpRequest) -> 'QuerySet[models.FestivalEntry]':
|
|
qs = super().get_queryset(request)
|
|
return qs.annotate(
|
|
# Caveat: because we need to aggregate 2 fields, .annotate() won't work here,
|
|
# returning nonsensical results because it uses a join.
|
|
popularity=Coalesce(
|
|
Subquery(
|
|
models.FestivalEntryVotes.objects.filter(festival_entry_id=OuterRef('pk'))
|
|
.values('festival_entry_id')
|
|
.annotate(popularity=Coalesce(Sum(F('rating')), Value(0)))
|
|
.values('popularity')
|
|
),
|
|
Value(0),
|
|
),
|
|
score=Coalesce(
|
|
Subquery(
|
|
models.FestivalEntryFinalVotes.objects.filter(festival_entry_id=OuterRef('pk'))
|
|
.values('festival_entry_id')
|
|
.annotate(score=Coalesce(Sum(F('points')), Value(0)))
|
|
.values('score')
|
|
),
|
|
Value(0),
|
|
),
|
|
)
|
|
|
|
def show_link(self, obj: models.FestivalEntry) -> SafeText:
|
|
return mark_safe(f'<a href="{obj.get_absolute_url()}">View</a>')
|
|
|
|
show_link.short_description = 'View'
|
|
|
|
def popularity(self, obj: models.FestivalEntry) -> int:
|
|
popularity: int = obj.popularity # type: ignore
|
|
return popularity
|
|
|
|
popularity.admin_order_field = 'popularity' # type: ignore
|
|
popularity.short_description = mark_safe('Popularity<br>(sum of ratings)') # type: ignore
|
|
|
|
def score(self, obj: models.FestivalEntry) -> int:
|
|
score: int = obj.score # type: ignore
|
|
return score
|
|
|
|
score.admin_order_field = 'score' # type: ignore
|
|
|
|
def has_thumbnail(self, obj: models.FestivalEntry) -> bool:
|
|
return bool(obj.thumbnail)
|
|
|
|
has_thumbnail.short_description = 'Thumbnail?'
|
|
has_thumbnail.boolean = True
|
|
|
|
|
|
@admin.register(models.Message)
|
|
class MessageAdmin(admin.ModelAdmin):
|
|
autocomplete_fields = ['user']
|
|
search_fields = ('title', 'content')
|
|
|
|
list_display = (
|
|
'id',
|
|
'user',
|
|
'content',
|
|
'created_at',
|
|
)
|
|
|
|
|
|
class TinyMCEFlatFileImageUploadView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
|
def has_permission(self) -> bool:
|
|
return permissions.can_add_flatfile(self.request.user)
|
|
|
|
def post(self, request: HttpRequest, *args: str, **kwargs: str) -> HttpResponse:
|
|
if not request.FILES:
|
|
return HttpResponseBadRequest('No file provided.')
|
|
elif len(request.FILES) > 1:
|
|
return HttpResponseBadRequest('Multiple files provided.')
|
|
else:
|
|
file: UploadedFile = next(iter(request.FILES.values()))
|
|
flatfile: models.FlatFile = models.FlatFile.objects.create(
|
|
file=file, type=models.FlatFile.IMAGE
|
|
)
|
|
return JsonResponse({'location': flatfile.file.url})
|
|
|
|
|
|
class TinyMCEFlatFileImageListView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
|
def has_permission(self) -> bool:
|
|
return self.request.user.has_perm('can_view_flatfile')
|
|
|
|
def get(self, request: HttpRequest, *args: str, **kwargs: str) -> HttpResponse:
|
|
return JsonResponse(
|
|
[
|
|
{'title': str(image), 'value': str(image.file.url)}
|
|
for image in models.FlatFile.objects.filter(type=models.FlatFile.IMAGE)
|
|
],
|
|
safe=False,
|
|
)
|
|
|
|
|
|
class TinyMCEFlatPagesLinkListView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
|
def has_permission(self) -> bool:
|
|
return self.request.user.has_perm('can_view_flatpage')
|
|
|
|
def get(self, request: HttpRequest, *args: str, **kwargs: str) -> HttpResponse:
|
|
return JsonResponse(
|
|
[
|
|
{'title': str(flatpage), 'value': str(flatpage.get_absolute_url())}
|
|
for flatpage in FlatPage.objects.all()
|
|
]
|
|
+ [
|
|
{'title': str(flatfile), 'value': str(flatfile.file.url)}
|
|
for flatfile in models.FlatFile.objects.filter(type=models.FlatFile.BINARY)
|
|
],
|
|
safe=False,
|
|
)
|
|
|
|
|
|
class TinyMCEFlatPageAdmin(FlatPageAdmin): # type: ignore
|
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
|
if db_field.name == 'content':
|
|
return db_field.formfield(
|
|
widget=AdminTinyMCE(
|
|
mce_attrs={
|
|
'link_list': reverse('tinymce_flatpages_link_list'),
|
|
'image_list': reverse('tinymce_flatfile_image_list'),
|
|
'automatic_uploads': True,
|
|
'images_reuse_filename': True,
|
|
'images_upload_handler': 'tinyMCEImageUploadHandler',
|
|
}
|
|
)
|
|
)
|
|
return super(TinyMCEFlatPageAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
|
|
|
|
|
class FlatFileImageForm(forms.ModelForm):
|
|
class Meta:
|
|
model = models.FlatFile
|
|
fields = ('file', 'type')
|
|
widgets = {'file': widgets.ImageWidget}
|
|
|
|
|
|
@admin.register(models.FlatFile)
|
|
class FlatFileAdmin(admin.ModelAdmin):
|
|
def get_form(self, request, obj=None, change=False, **kwargs):
|
|
if change and obj.type == models.FlatFile.IMAGE:
|
|
kwargs['form'] = FlatFileImageForm
|
|
return super().get_form(request, obj, change, **kwargs)
|
|
|
|
|
|
@admin.register(models.Photo)
|
|
class PhotoAdmin(admin.ModelAdmin):
|
|
date_hierarchy = 'created_at'
|
|
raw_id_fields = ['user']
|
|
list_filter = ['edition', 'albums']
|
|
list_display = ['created_at', 'file', 'user']
|
|
search_fields = ['user__email', 'user__username', 'user__profile__full_name', 'hash']
|
|
readonly_fields = ['hash']
|
|
|
|
|
|
@admin.register(models.Album)
|
|
class AlbumAdmin(admin.ModelAdmin):
|
|
prepopulated_fields = {'slug': ('title',)}
|
|
view_on_site = True
|
|
date_hierarchy = 'created_at'
|
|
raw_id_fields = ['photos']
|
|
list_filter = ['edition', 'is_upload_open']
|
|
list_display = ['created_at', 'title', 'slug', 'cover_image']
|
|
|
|
|
|
class SponsorsInlineAdmin(admin.TabularInline):
|
|
model = models.Sponsor
|
|
verbose_name = 'Sponsor'
|
|
verbose_name_plural = 'Sponsors'
|
|
extra = 1
|
|
|
|
|
|
@admin.register(models.Edition)
|
|
class EditionAdmin(admin.ModelAdmin):
|
|
save_on_top = True
|
|
inlines = [SponsorsInlineAdmin]
|
|
list_display = ('year', 'is_archived')
|
|
|
|
|
|
@admin.register(LogEntry)
|
|
class LogEntryAdmin(admin.ModelAdmin):
|
|
date_hierarchy = 'action_time'
|
|
|
|
list_filter = [
|
|
'action_flag',
|
|
'user__is_staff',
|
|
'user__is_superuser',
|
|
'action_time',
|
|
'content_type',
|
|
]
|
|
|
|
search_fields = ['object_repr', 'change_message', 'user__email', 'user__username']
|
|
|
|
list_display = [
|
|
'action_time',
|
|
'user',
|
|
'content_type',
|
|
'object_link',
|
|
'action_flag',
|
|
'get_change_message',
|
|
]
|
|
|
|
def has_add_permission(self, request):
|
|
return False
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def has_view_permission(self, request, obj=None):
|
|
return request.user.is_superuser
|
|
|
|
def object_link(self, obj):
|
|
if obj.action_flag == DELETION:
|
|
return escape(obj.object_repr)
|
|
ct = obj.content_type
|
|
return get_admin_change_url(
|
|
app_label=ct.app_label,
|
|
model_name=ct.model,
|
|
text=escape(obj.object_repr),
|
|
pk=obj.object_id,
|
|
)
|
|
|
|
object_link.admin_order_field = "object_repr"
|
|
object_link.short_description = "object"
|
|
|
|
|
|
admin.site.register(models.Day)
|
|
admin.site.register(models.SiteSettings)
|
|
|
|
admin.site.unregister(FlatPage)
|
|
admin.site.register(FlatPage, TinyMCEFlatPageAdmin)
|
|
|
|
admin.site.register(models.SponsorLevel)
|