1. 初识acdh-django-browsing包
acdh-django-browsing是奥地利科学院数字人文研究所(ACDH)开发的一个Django应用扩展包,专门为数字人文领域的项目提供数据浏览和检索功能。这个包在文化遗产数据管理、历史文献数字化等场景中特别有用,我在参与一个中世纪手稿数字化项目时首次接触它。
安装过程非常简单,用pip就能搞定:
bash复制pip install acdh-django-browsing
这个包的核心价值在于它提供了一套现成的数据浏览组件,包括:
- 高级过滤搜索界面
- 分页浏览控件
- 数据可视化面板
- 多维度分类导航
注意:使用前需要确保项目已经配置好Django的基本环境,包括数据库连接和静态文件设置。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心语法解析
2.1 基础导入方式
在Django项目的settings.py中,需要先注册这个应用:
python复制INSTALLED_APPS = [
...
'acdh_django_browsing',
'dal', # 依赖项
'dal_select2',
...
]
2.2 主要类和方法
这个包的核心是BrowsingConfig类,它提供了以下关键方法:
- get_filter_form() - 生成动态过滤表单
- build_query_string() - 构造URL查询参数
- paginate_results() - 处理分页逻辑
- render_browsing_view() - 核心渲染方法
一个典型的使用示例:
python复制from acdh_django_browsing.views import BrowsingView
class ManuscriptBrowseView(BrowsingView):
model = Manuscript
template_name = 'browsing/manuscript_list.html'
filter_fields = ['date', 'language', 'repository']
ordering = ['-date']
3. 参数详解
3.1 视图类参数
| 参数名 | 类型 | 必选 | 说明 |
|---|---|---|---|
| model | Model | 是 | 要浏览的Django模型 |
| template_name | str | 否 | 自定义模板路径 |
| filter_fields | list | 否 | 可过滤字段列表 |
| ordering | list | 否 | 默认排序规则 |
| paginate_by | int | 否 | 每页项目数(默认20) |
| context_object_name | str | 否 | 模板上下文变量名 |
3.2 模板标签参数
在模板中常用的标签参数:
html复制{% load browsing_tags %}
<!-- 生成过滤表单 -->
{% browsing_filters form %}
<!-- 分页控件 -->
{% browsing_pagination page_obj %}
<!-- 结果统计 -->
{% browsing_stats paginator %}
4. 实际应用案例
4.1 手稿数字图书馆项目
我在维也纳的一个手稿数字化项目中应用了这个包。项目需要展示15-18世纪的5000多份手稿,每份都有复杂的元数据(年代、语言、出处等)。
关键配置:
python复制class MedievalManuscriptView(BrowsingView):
model = Manuscript
filter_fields = [
'century',
'language__name',
'repository__city',
'has_illustrations'
]
facet_fields = ['language', 'century']
paginate_by = 50
实现的效果包括:
- 多条件组合筛选
- 按语言/世纪的分面导航
- AJAX加载的分页
- 结果统计面板
4.2 博物馆藏品管理系统
另一个案例是某自然历史博物馆的昆虫标本数据库,处理了超过10万条记录。
特殊配置技巧:
python复制class SpecimenBrowseView(BrowsingView):
model = Specimen
filter_fields = {
'species': ['exact', 'icontains'],
'collection_date': ['range'],
'location': ['distance'],
}
search_fields = ['species__name', 'collector__name']
select_related = ['species', 'collector']
重要提示:处理大数据集时需要优化查询,我通常会添加:
- select_related/prefetch_related
- 数据库索引
- 缓存策略
5. 性能优化技巧
5.1 数据库查询优化
- 索引策略:
python复制class Manuscript(models.Model):
century = models.IntegerField(db_index=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE, db_index=True)
- 查询优化:
python复制def get_queryset(self):
return super().get_queryset().select_related(
'language',
'repository'
).prefetch_related(
'authors'
).only(
'title', 'date', 'language__name'
)
5.2 前端优化
- 使用django-compressor压缩静态资源
- 实现无限滚动代替分页
- 对过滤器使用AJAX加载
配置示例:
javascript复制$(document).ready(function() {
$('#filters-form').on('change', function() {
$.ajax({
url: window.location.pathname,
data: $(this).serialize(),
success: function(data) {
$('#results-container').html(data);
}
});
});
});
6. 常见问题解决方案
6.1 性能问题排查
症状:页面加载缓慢
- 检查是否缺少select_related
- 使用Django Debug Toolbar分析查询
- 检查数据库索引
症状:内存溢出
- 减少paginate_by值
- 使用iterator()处理大数据集
- 添加缓存装饰器
6.2 功能异常处理
问题:过滤器不生效
- 检查filter_fields定义
- 验证模型字段类型
- 检查表单媒体文件是否加载
问题:分页错乱
- 检查GET参数保留
- 验证paginate_by设置
- 确保模板使用了正确的paginator对象
7. 高级定制技巧
7.1 自定义过滤器表单
继承BaseFilterForm创建个性化表单:
python复制from acdh_django_browsing.forms import BaseFilterForm
class DateRangeFilterForm(BaseFilterForm):
start_date = forms.DateField(required=False)
end_date = forms.DateField(required=False)
def filter_queryset(self, queryset):
qs = super().filter_queryset(queryset)
if self.cleaned_data['start_date']:
qs = qs.filter(date__gte=self.cleaned_data['start_date'])
if self.cleaned_data['end_date']:
qs = qs.filter(date__lte=self.cleaned_data['end_date'])
return qs
7.2 集成可视化组件
结合Chart.js实现数据可视化:
python复制class VisualBrowseView(BrowsingView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dates = [obj.date for obj in context['object_list']]
context['chart_data'] = {
'labels': sorted(set(dates)),
'data': [dates.count(d) for d in sorted(set(dates))]
}
return context
在模板中使用:
html复制<canvas id="timelineChart"></canvas>
<script></script>
8. 与其他工具的集成
8.1 结合Django REST framework
创建混合视图同时支持HTML和JSON输出:
python复制from rest_framework.views import APIView
from django.http import JsonResponse
class HybridBrowseView(BrowsingView, APIView):
def get(self, request, *args, **kwargs):
if request.accepts('application/json'):
data = {
'results': list(self.get_queryset().values()),
'count': self.get_queryset().count()
}
return JsonResponse(data)
return super().get(request, *args, **kwargs)
8.2 与Elasticsearch集成
对于超大数据集,可以结合Haystack:
python复制from haystack.query import SearchQuerySet
class SearchBrowseView(BrowsingView):
def get_queryset(self):
sqs = SearchQuerySet().filter(
content=self.request.GET.get('q', '')
)
return sqs.load_all().models(self.model)
9. 部署注意事项
9.1 生产环境配置
- 静态文件收集:
bash复制python manage.py collectstatic
- 缓存配置(使用Redis):
python复制CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
9.2 安全考虑
- 限制过滤器字段:
python复制class SafeBrowseView(BrowsingView):
allowed_filter_fields = ['public_field1', 'public_field2']
def get_filter_fields(self):
return self.allowed_filter_fields
- 添加权限控制:
python复制from django.contrib.auth.mixins import LoginRequiredMixin
class RestrictedBrowseView(LoginRequiredMixin, BrowsingView):
login_url = '/accounts/login/'
redirect_field_name = 'redirect_to'
10. 项目实战经验
在最近的一个历史档案项目中,我们遇到了几个特殊需求:
- 多语言支持:
python复制from django.utils.translation import gettext_lazy as _
class MultilingualBrowseView(BrowsingView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filters_title'] = _("Filter Options")
return context
- 复杂字段过滤:
python复制class ComplexFilterBrowseView(BrowsingView):
def get_queryset(self):
qs = super().get_queryset()
if 'has_transcription' in self.request.GET:
qs = qs.annotate(
has_trans=Exists(
Transcription.objects.filter(
manuscript=OuterRef('pk')
)
)
).filter(has_trans=True)
return qs
- 动态字段选择:
python复制class DynamicFieldsBrowseView(BrowsingView):
def get_filter_fields(self):
fields = super().get_filter_fields()
if self.request.user.is_staff:
fields.extend(['internal_code', 'acquisition_date'])
return fields
这个包最让我欣赏的是它的灵活性。在最近一次升级中,我发现可以通过重写get_filter_form_class方法完全自定义表单行为:
python复制class CustomFormBrowseView(BrowsingView):
def get_filter_form_class(self):
form_class = super().get_filter_form_class()
form_class.declared_fields['new_field'] = forms.CharField(required=False)
return form_class
