diff --git a/server/api/urls.py b/server/api/urls.py index 7b85921..c0f3074 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -19,10 +19,13 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.static import static +from bingo.actions import export2csv # This will not show in production. We will need use nginx to serve the media files in production media = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +admin.site.add_action(export2csv, "csv_export_selected") + urlpatterns = [ path("admin/", admin.site.urls), path("api/", include("bingo.urls")), diff --git a/server/bingo/actions.py b/server/bingo/actions.py new file mode 100644 index 0000000..67ae097 --- /dev/null +++ b/server/bingo/actions.py @@ -0,0 +1,46 @@ +from django.http import HttpResponse +from django.contrib.admin import action +from .models import User, TileInteraction + + +def get_friendly_value(instance, field_name, value): + display_method = f'get_{field_name}_display' + if hasattr(instance, display_method): + return getattr(instance, display_method)() + else: + # If it's not a choice field, return the raw value + return value + + +@action(description="Export selected objects as CSV") +def export2csv(modeladmin, request, queryset): + import csv + + response = HttpResponse( + content_type="text/csv", + headers={ + "Content-Disposition": f'attachment; filename="{modeladmin.model._meta.model_name}s.csv"'}, + ) + + writer = csv.writer(response) + if modeladmin.model == User: + exclude = ('password', 'is_superuser', 'is_active') + elif modeladmin.model == TileInteraction: + exclude = ('image_display',) + else: + exclude = tuple() + + fields = modeladmin.fields if modeladmin.fields else [ + f.name for f in modeladmin.model._meta.fields()] + fields = [f for f in fields if f not in exclude] + writer.writerow(fields) + if modeladmin.model == User: + queryset = queryset.filter(is_superuser=False, is_active=True) + raw_values = queryset.values_list(*fields) + readable_rows = [] + for instance, values in zip(queryset, raw_values): + readable_rows.append((get_friendly_value(instance, field_name, value) + for field_name, value in zip(fields, values))) + + writer.writerows(readable_rows) + return response diff --git a/server/bingo/admin.py b/server/bingo/admin.py index 25df778..e3d242e 100644 --- a/server/bingo/admin.py +++ b/server/bingo/admin.py @@ -5,11 +5,14 @@ @admin.register(User) -class AuthorAdmin(admin.ModelAdmin): +class UserAdmin(admin.ModelAdmin): # custom order of fields fields = ("username", "first_name", "last_name", "bio", "total_points", "email", "birthdate", "visibility", "avatar", "gender_identity", "indigenous_identity", "last_login", "is_active", "is_superuser", "password") + list_display = ("username", "first_name", "last_name", "total_points", "email", "birthdate", + "visibility", "avatar", "gender_identity", "indigenous_identity", "is_superuser", "is_active") + def save_model(self, request, obj, form, change): if obj.pk is not None: # Existing user original_obj = self.model.objects.get(pk=obj.pk) @@ -32,14 +35,31 @@ class ChallengeAdmin(admin.ModelAdmin): 'points', 'total_completions') readonly_fields = ('total_completions', 'id') + list_display = fields + + +@admin.register(Friendship) +class FriendshipAdmin(admin.ModelAdmin): + list_display = ('requester', 'receiver', 'status') + fields = list_display -admin.site.register(Friendship) -admin.site.register(BingoGrid) + +@admin.register(BingoGrid) +class BingoGridAdmin(admin.ModelAdmin): + # disable the export2csv action + def get_actions(self, request): + actions = super().get_actions(request) + return {k: v for k, v in actions.items() if k != 'csv_export_selected'} @admin.register(TileInteraction) class TileInteractionAdmin(admin.ModelAdmin): - readonly_fields = ('image_display',) + fields = ('user', 'grid', 'position', 'description', 'image', 'completed', + 'consent', 'date_started', 'date_completed', 'image_display') + readonly_fields = ('image_display', 'date_started') + + list_display = ('user', 'grid', 'position', 'completed', + 'consent', 'date_started', 'date_completed') def image_display(self, obj): return obj.get_image_html()