Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
# Set True for Django debug mode
# Django Core
DEBUG=True
# Set True to enable verbose SQL query logging
SQL_DEBUG=False
DJANGO_SECRET_KEY=thisisnotasecretkey
DJANGO_ALLOWED_HOSTS='localhost,"",127.0.0.1,0.0.0.0'
DJANGO_CSRF_TRUSTED_ORIGINS='http://localhost'
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8000,http://127.0.0.1:8000

# Database
# Database
DB_NAME=lasfera
DB_USER=lasfera
DB_PASSWORD=postgres
DB_PASS=postgres
DB_HOST=db
DB_PORT=5432

# Only used in Docker setup
POSTGRES_DB=lasfera
POSTGRES_USER=lasfera
POSTGRES_PASSWORD=postgres
POSTGRES_HOST=db

# File storage (only required for production deployments)
AWS_ACCESS_KEY_ID=<aws-access-key-id>
AWS_SECRET_ACCESS_KEY=<aws-secret-access-key>
AWS_STORAGE_BUCKET_NAME=<bucket>
# Wagtail
WAGTAILADMIN_BASE_URL=http://localhost:8000

# AWS (Leave as placeholders for local dev)
AWS_ACCESS_KEY_ID=local_dev
AWS_SECRET_ACCESS_KEY=local_dev
AWS_STORAGE_BUCKET_NAME=local_dev
AWS_S3_REGION_NAME=us-east-1
42 changes: 42 additions & 0 deletions manuscript/management/commands/run_exports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import csv
from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from manuscript.resources import (
LocationResource,
LocationAliasResource,
FolioResource,
SingleManuscriptResource,
StanzaResource,
StanzaTranslatedResource,
)


class Command(BaseCommand):
help = "Export records to storage backend for public download"

def handle(self, *args, **options):
export_targets = [
(FolioResource(), "exports/folios.csv"),
(SingleManuscriptResource(), "exports/manuscripts.csv"),
(StanzaResource(), "exports/stanzas.csv"),
(StanzaTranslatedResource(), "exports/translated_stanzas.csv"),
(LocationResource(), "exports/toponyms.csv"),
(LocationAliasResource(), "exports/toponym_variants.csv"),
]

for resource_class, file_path in export_targets:
self.stdout.write(f"Exporting {file_path}...")
dataset = resource_class.export()
csv_data = dataset.csv

# since AWS_S3_FILE_OVERWRITE = False, delete the old version first
if default_storage.exists(file_path):
default_storage.delete(file_path)

# save to default storage
default_storage.save(file_path, ContentFile(csv_data))

self.stdout.write(
self.style.SUCCESS("Successfully updated all public exports.")
)
56 changes: 56 additions & 0 deletions manuscript/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from import_export.results import RowResult
from django.contrib import messages
from django.db.models import Q
from django.utils.html import strip_tags
import logging

logger = logging.getLogger(__name__)
Expand All @@ -16,9 +17,20 @@
Location,
LocationAlias,
LineCode,
StanzaTranslated,
)


class ExportOnlyResource(resources.ModelResource):
"""reusable base class to disable import functionality"""

def before_import(self, dataset, **kwargs):
raise NotImplementedError("Importing is disabled for this resource.")

def import_data(self, dataset, **kwargs):
raise NotImplementedError("Importing is disabled for this resource.")


class FolioResource(resources.ModelResource):
"""Resource for importing Folio data with proper object logging"""

Expand Down Expand Up @@ -614,3 +626,47 @@ def import_row(self, row, instance_loader, **kwargs):
def get_diff_headers(self):
"""Define headers for the diff display"""
return ["Code", "Toponyms"]


class StanzaResource(ExportOnlyResource):
class Meta:
model = Stanza
fields = (
"id",
"stanza_line_code_starts",
"stanza_line_code_ends",
"stanza_text",
"stanza_notes",
"language",
"is_rubric",
)
export_order = fields

def dehydrate_stanza_text(self, instance):
return strip_tags(instance.stanza_text) if instance.stanza_text else ""

def dehydrate_stanza_notes(self, instance):
return strip_tags(instance.stanza_notes) if instance.stanza_notes else ""


class StanzaTranslatedResource(ExportOnlyResource):
# export the line code of the parent stanza instead of just the id
parent_stanza_code = fields.Field(
attribute="stanza__stanza_line_code_starts", column_name="original_stanza_code"
)

class Meta:
model = StanzaTranslated
fields = (
"id",
"parent_stanza_code",
"stanza_line_code_starts",
"stanza_line_code_ends",
"stanza_text",
"language",
"is_rubric",
)
export_order = fields

def dehydrate_stanza_text(self, instance):
return strip_tags(instance.stanza_text) if instance.stanza_text else ""
34 changes: 34 additions & 0 deletions pages/migrations/0008_datapage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 5.1.15 on 2026-02-19 18:40

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("pages", "0007_alter_aboutpage_body_alter_homeintroduction_body_and_more"),
]

operations = [
migrations.CreateModel(
name="DataPage",
fields=[
(
"sitepage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="pages.sitepage",
),
),
],
options={
"abstract": False,
},
bases=("pages.sitepage",),
),
]
31 changes: 31 additions & 0 deletions pages/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.snippets.models import register_snippet
from django.conf import settings
from django.core.files.storage import default_storage
from django.db import models

from wagtail import blocks
Expand Down Expand Up @@ -73,3 +75,32 @@ class ManuscriptsIntroduction(models.Model):

def __str__(self):
return self.title


class DataPage(SitePage):
template = "pages/data_page.html"

def get_context(self, request):
"""add our public export URLs to context to include in the template"""
context = super().get_context(request)
files = [
("Manuscripts", "manuscripts"),
("Folios", "folios"),
("Stanzas (Italian)", "stanzas"),
("Stanzas (English)", "translated_stanzas"),
("Toponyms", "toponyms"),
("Toponym Variants", "toponym_variants"),
]
export_list = []
for label, slug in files:
file_path = f"exports/{slug}.csv"
# Use default_storage to generate the safe S3 URL
# Fallback to '#' if the file hasn't been generated by the command yet
if default_storage.exists(file_path):
file_url = default_storage.url(file_path)
export_list.append({
"label": label,
"csv": file_url,
})
context["exports"] = export_list
return context
32 changes: 32 additions & 0 deletions templates/pages/data_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% load wagtailcore_tags %}

{% block title %}{{ page.title }} - La Sfera{% endblock title %}

{% block content %}
<div class="mx-auto container pt-3">
<section class="w-full p-4 my-10">
<h3 class="text-4xl pb-4 font-bold" id="top">
{{ page.title }}
</h3>

<div class="grid grid-cols-3 gap-4">
{# Left Column (2/3) #}
<section id="page" class="prose col-span-2">
{% include_block page.body %}
</section>

{# Right Column (1/3) #}
<aside class="prose border-l border-gray-200 pl-5">
<ul>
{% for export in exports %}
<li>
<a href="{{ export.csv }}" class="underline hover:no-underline" download>{{ export.label }} (CSV)</a>
</li>
{% endfor %}
</ul>
</aside>
</div>
</section>
</div>
{% endblock %}
17 changes: 4 additions & 13 deletions templates/pages/site_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,13 @@

{% block content %}
<div class="mx-auto container pt-3">
<section class="w-full p-4 my-10">
<section class="mx-auto p-4 my-10">
<h3 class="text-4xl pb-4 font-bold" id="top">
{{ page.title }}
</h3>

<div class="grid grid-cols-3 gap-4">
{# Left Column (2/3) #}
<section id="page" class="prose col-span-2">
{% include_block page.body %}
</section>

{# Right Column (1/3) #}
<aside class="prose border-l border-gray-200 pl-5">
{{ page.team|richtext }}
</aside>
</div>
<section id="page" class="prose">
{% include_block page.body %}
</section>
</section>
</div>
{% endblock %}