Skip to content

Commit

Permalink
Add a CSV export functionality in admin and add price fields (#41)
Browse files Browse the repository at this point in the history
* Implement an action do download data in csv
* Refactor CSV download
* Move price to main models and add csv to bookshelf
* Update template and API
* Small refactoring
  • Loading branch information
daniviga authored Dec 29, 2024
1 parent 7eddd1b commit 026ab06
Show file tree
Hide file tree
Showing 18 changed files with 419 additions and 13 deletions.
152 changes: 149 additions & 3 deletions ram/bookshelf/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import html

from django.conf import settings
from django.contrib import admin
from django.utils.html import strip_tags
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin

from ram.utils import generate_csv
from portal.utils import get_site_conf
from bookshelf.models import (
BaseBookProperty,
BaseBookImage,
Expand Down Expand Up @@ -71,9 +77,25 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
"number_of_pages",
"publication_year",
"description",
"tags",
)
},
),
(
"Purchase data",
{
"fields": (
"purchase_date",
"price",
)
},
),
(
"Notes",
{
"classes": ("collapse",),
"fields": (
"notes",
"tags",
)
},
),
Expand All @@ -89,13 +111,66 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
),
)

def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields["price"].label = "Price ({})".format(
get_site_conf().currency
)
return form

@admin.display(description="Publisher")
def get_publisher(self, obj):
return obj.publisher.name

@admin.display(description="Authors")
def get_authors(self, obj):
return ", ".join(a.short_name() for a in obj.authors.all())
return obj.authors_list

def download_csv(modeladmin, request, queryset):
header = [
"Title",
"Authors",
"Publisher",
"ISBN",
"Language",
"Number of Pages",
"Publication Year",
"Description",
"Tags",
"Purchase Date",
"Price ({})".format(get_site_conf().currency),
"Notes",
"Properties",
]

data = []
for obj in queryset:
properties = settings.CSV_SEPARATOR_ALT.join(
"{}:{}".format(property.property.name, property.value)
for property in obj.property.all()
)
data.append([
obj.title,
obj.authors_list.replace(",", settings.CSV_SEPARATOR_ALT),
obj.publisher.name,
obj.ISBN,
dict(settings.LANGUAGES)[obj.language],
obj.number_of_pages,
obj.publication_year,
html.unescape(strip_tags(obj.description)),
settings.CSV_SEPARATOR_ALT.join(
t.name for t in obj.tags.all()
),
obj.purchase_date,
obj.price,
html.unescape(strip_tags(obj.notes)),
properties,
])

return generate_csv(header, data, "bookshelf_books.csv")

download_csv.short_description = "Download selected items as CSV"
actions = [download_csv]


@admin.register(Author)
Expand Down Expand Up @@ -146,9 +221,25 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
"number_of_pages",
"publication_year",
"description",
"tags",
)
},
),
(
"Purchase data",
{
"fields": (
"purchase_date",
"price",
)
},
),
(
"Notes",
{
"classes": ("collapse",),
"fields": (
"notes",
"tags",
)
},
),
Expand All @@ -164,6 +255,61 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
),
)

def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields["price"].label = "Price ({})".format(
get_site_conf().currency
)
return form

@admin.display(description="Scales")
def get_scales(self, obj):
return "/".join(s.scale for s in obj.scales.all())

def download_csv(modeladmin, request, queryset):
header = [
"Catalog",
"Manufacturer",
"Years",
"Scales",
"ISBN",
"Language",
"Number of Pages",
"Publication Year",
"Description",
"Tags",
"Purchase Date",
"Price ({})".format(get_site_conf().currency),
"Notes",
"Properties",
]

data = []
for obj in queryset:
properties = settings.CSV_SEPARATOR_ALT.join(
"{}:{}".format(property.property.name, property.value)
for property in obj.property.all()
)
data.append([
obj.__str__,
obj.manufacturer.name,
obj.years,
obj.get_scales,
obj.ISBN,
dict(settings.LANGUAGES)[obj.language],
obj.number_of_pages,
obj.publication_year,
html.unescape(strip_tags(obj.description)),
settings.CSV_SEPARATOR_ALT.join(
t.name for t in obj.tags.all()
),
obj.purchase_date,
obj.price,
html.unescape(strip_tags(obj.notes)),
properties,
])

return generate_csv(header, data, "bookshelf_catalogs.csv")

download_csv.short_description = "Download selected items as CSV"
actions = [download_csv]
36 changes: 36 additions & 0 deletions ram/bookshelf/migrations/0019_basebook_price.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 5.1.4 on 2024-12-29 17:06

from django.db import migrations, models


def price_to_property(apps, schema_editor):
basebook = apps.get_model("bookshelf", "BaseBook")
for row in basebook.objects.all():
prop = row.property.filter(property__name__icontains="price")
for p in prop:
try:
row.price = float(p.value)
except ValueError:
pass
row.save()


class Migration(migrations.Migration):

dependencies = [
("bookshelf", "0018_alter_basebookdocument_options"),
]

operations = [
migrations.AddField(
model_name="basebook",
name="price",
field=models.DecimalField(
blank=True, decimal_places=2, max_digits=10, null=True
),
),
migrations.RunPython(
price_to_property,
reverse_code=migrations.RunPython.noop
),
]
11 changes: 11 additions & 0 deletions ram/bookshelf/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ class BaseBook(BaseModel):
number_of_pages = models.SmallIntegerField(null=True, blank=True)
publication_year = models.SmallIntegerField(null=True, blank=True)
description = tinymce.HTMLField(blank=True)
price = models.DecimalField(
max_digits=10,
decimal_places=2,
null=True,
blank=True,
)
purchase_date = models.DateField(null=True, blank=True)
tags = models.ManyToManyField(
Tag, related_name="bookshelf", blank=True
Expand Down Expand Up @@ -114,9 +120,14 @@ class Meta:
def __str__(self):
return self.title

@property
def publisher_name(self):
return self.publisher.name

@property
def authors_list(self):
return ", ".join(a.short_name() for a in self.authors.all())

def get_absolute_url(self):
return reverse(
"bookshelf_item",
Expand Down
21 changes: 18 additions & 3 deletions ram/bookshelf/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from rest_framework import serializers
from bookshelf.models import Book, Author, Publisher
from metadata.serializers import TagSerializer
from bookshelf.models import Book, Catalog, Author, Publisher
from metadata.serializers import (
ScaleSerializer,
ManufacturerSerializer,
TagSerializer
)


class AuthorSerializer(serializers.ModelSerializer):
Expand All @@ -22,5 +26,16 @@ class BookSerializer(serializers.ModelSerializer):

class Meta:
model = Book
fields = "__all__"
exclude = ("price",)
read_only_fields = ("creation_time", "updated_time")


class CatalogSerializer(serializers.ModelSerializer):
scales = ScaleSerializer(many=True)
manufacturer = ManufacturerSerializer()
tags = TagSerializer(many=True)

class Meta:
model = Catalog
exclude = ("price",)
read_only_fields = ("creation_time", "updated_time")
4 changes: 3 additions & 1 deletion ram/bookshelf/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from django.urls import path
from bookshelf.views import BookList, BookGet
from bookshelf.views import BookList, BookGet, CatalogList, CatalogGet

urlpatterns = [
path("book/list", BookList.as_view()),
path("book/get/<uuid:uuid>", BookGet.as_view()),
path("catalog/list", CatalogList.as_view()),
path("catalog/get/<uuid:uuid>", CatalogGet.as_view()),
]
20 changes: 18 additions & 2 deletions ram/bookshelf/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from rest_framework.generics import ListAPIView, RetrieveAPIView
from rest_framework.schemas.openapi import AutoSchema

from bookshelf.models import Book
from bookshelf.serializers import BookSerializer
from bookshelf.models import Book, Catalog
from bookshelf.serializers import BookSerializer, CatalogSerializer


class BookList(ListAPIView):
Expand All @@ -19,3 +19,19 @@ class BookGet(RetrieveAPIView):

def get_queryset(self):
return Book.objects.get_published(self.request.user)


class CatalogList(ListAPIView):
serializer_class = CatalogSerializer

def get_queryset(self):
return Catalog.objects.get_published(self.request.user)


class CatalogGet(RetrieveAPIView):
serializer_class = CatalogSerializer
lookup_field = "uuid"
schema = AutoSchema(operation_id_base="retrieveCatalogByUUID")

def get_queryset(self):
return Book.objects.get_published(self.request.user)
1 change: 1 addition & 0 deletions ram/portal/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SiteConfigurationAdmin(SingletonModelAdmin):
"about",
"items_per_page",
"items_ordering",
"currency",
"footer",
"footer_extended",
)
Expand Down
21 changes: 21 additions & 0 deletions ram/portal/migrations/0018_siteconfiguration_currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.4 on 2024-12-29 15:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
(
"portal",
"0017_alter_flatpage_content_alter_siteconfiguration_about_and_more",
),
]

operations = [
migrations.AddField(
model_name="siteconfiguration",
name="currency",
field=models.CharField(default="EUR", max_length=3),
),
]
1 change: 1 addition & 0 deletions ram/portal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class SiteConfiguration(SingletonModel):
],
default="type",
)
currency = models.CharField(max_length=3, default="EUR")
footer = tinymce.HTMLField(blank=True)
footer_extended = tinymce.HTMLField(blank=True)
show_version = models.BooleanField(default=True)
Expand Down
6 changes: 6 additions & 0 deletions ram/portal/templates/bookshelf/book.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@
<th scope="row">Purchase date</th>
<td>{{ book.purchase_date|default:"-" }}</td>
</tr>
{% if request.user.is_staff %}
<tr>
<th scope="row">Price ({{ site_conf.currency }})</th>
<td>{{ book.price|default:"-" }}</td>
</tr>
{% endif %}
</tbody>
</table>
{% if properties %}
Expand Down
6 changes: 6 additions & 0 deletions ram/portal/templates/rollingstock.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@
<th scope="row">Purchase date</th>
<td>{{ rolling_stock.purchase_date|default:"-" }}</td>
</tr>
{% if request.user.is_staff %}
<tr>
<th scope="row">Price ({{ site_conf.currency }})</th>
<td>{{ rolling_stock.price|default:"-" }}</td>
</tr>
{% endif %}
</tbody>
</table>
{% if properties %}
Expand Down
Loading

0 comments on commit 026ab06

Please sign in to comment.