You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

acdh-django-browsing

Package Overview
Dependencies
Maintainers
2
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

acdh-django-browsing - pypi Package Compare versions

Comparing version
2.0
to
3.0
+12
browsing/filters.py
import django_filters
def get_generic_filter(model_class):
class GenericFilter(django_filters.FilterSet):
class Meta:
model = model_class
fields = "__all__"
return GenericFilter
<div class="pt-2 ps-5">
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="{{ object.get_listview_url }}">{{ verbose_name_plural }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ object }}</li>
</ol>
</nav>
</div>
<div class="row">
<div class="col-md-2">
{% if object.get_prev %}
<p class="text-start">
<a href="{{ object.get_prev }}">
<i class="fs-3 bi bi-chevron-left" title="previous entry" aria-hidden="true"></i>
<span class="visually-hidden">previous entry</span>
</a>
</p>
{% endif %}
</div>
<div class="col-md-8 text-center">
<h1 class="text-center">
{{ object }}
</h1>
{% if user.is_authenticated %}
<a href="{{ object.get_edit_url }}">
<i class="fs-3 bi bi-pencil-square" title="edit entry" aria-hidden="true"></i>
<span class="visually-hidden">edit entry</span>
</a>
{% endif %}
</div>
<div class="col-md-2">
{% if object.get_next %}
<p class="text-end">
<a href="{{ object.get_next }}">
<i class="fs-3 bi bi-chevron-right" title="next entry" aria-hidden="true"></i>
<span class="visually-hidden">next entry</span>
</a>
</p>
{% endif %}
</div>
</div>
<div class="pt-2 ps-5">
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item active">{{ verbose_name_plural }}</li>
</ol>
</nav>
</div>
import django_tables2
from django.utils.safestring import mark_safe
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from crispy_forms.helper import FormHelper
from django_tables2.export.views import ExportMixin
from browsing.filters import get_generic_filter
input_form = """
<input type="checkbox" name="keep" value="{}" title="keep this"/> |
<input type="checkbox" name="remove" value="{}" title="remove this"/>
"""
class MergeColumn(django_tables2.Column):
"""renders a column with to checkbox - used to select objects for merging"""
def __init__(self, *args, **kwargs):
super(MergeColumn, self).__init__(*args, **kwargs)
def render(self, value):
return mark_safe(input_form.format(value, value))
def get_entities_table(model_class):
class GenericEntitiesTable(django_tables2.Table):
id = django_tables2.LinkColumn(verbose_name="ID")
merge = MergeColumn(verbose_name="keep | remove", accessor="pk")
class Meta:
model = model_class
attrs = {"class": "table table-hover table-striped table-condensed"}
return GenericEntitiesTable
class GenericFilterFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(GenericFilterFormHelper, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.form_class = "genericFilterForm"
self.form_method = "GET"
self.form_tag = False
class GenericListView(ExportMixin, django_tables2.SingleTableView):
filter_class = None
formhelper_class = GenericFilterFormHelper
context_filter_name = "filter"
paginate_by = 50
template_name = "browsing/generic_list.html"
init_columns = [
"id",
]
enable_merge = False
excluded_cols = []
h1 = ""
create_button_text = "Create new item"
introduction = ""
def get_filterset_class(self):
if self.filter_class:
return self.filter_class
else:
filter_class = get_generic_filter(self.model)
return filter_class
def get_table_class(self):
if self.table_class:
return self.table_class
else:
return get_entities_table(self.model)
def get_all_cols(self):
all_cols = {
key: value.header for key, value in self.get_table().base_columns.items()
}
return all_cols
def get_queryset(self, **kwargs):
qs = super(GenericListView, self).get_queryset()
filter_class = self.get_filterset_class()
self.filter = filter_class(self.request.GET, queryset=qs)
self.filter.form.helper = self.formhelper_class()
return self.filter.qs.distinct()
def get_table(self, **kwargs):
table = super(GenericListView, self).get_table()
default_cols = self.init_columns
all_cols = table.base_columns.keys()
selected_cols = self.request.GET.getlist("columns") + default_cols
exclude_vals = [x for x in all_cols if x not in selected_cols]
table.exclude = exclude_vals
return table
def get_context_data(self, **kwargs):
context = super(GenericListView, self).get_context_data()
context["enable_merge"] = self.enable_merge
togglable_colums = {
key: value
for key, value in self.get_all_cols().items()
if key not in self.init_columns and key not in self.exclude_columns
}
context["togglable_colums"] = togglable_colums
context[self.context_filter_name] = self.filter
context["docstring"] = f"{self.model.__doc__}"
try:
context["create_view_link"] = self.model.get_createview_url()
except AttributeError:
context["create_view_link"] = None
model_name = self.model.__name__.lower()
context["entity"] = model_name
context["app_name"] = self.model._meta.app_label
if self.h1:
context["h1"] = self.h1
else:
context["h1"] = f"Browse {self.model._meta.verbose_name_plural}"
context["create_button_text"] = self.create_button_text
context["verbose_name"] = self.model._meta.verbose_name
context["verbose_name_plural"] = self.model._meta.verbose_name_plural
return context
class BaseDetailView(DetailView):
model = None
template_name = "browsing/generic_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["docstring"] = f"{self.model.__doc__}"
context["class_name"] = f"{self.model.__name__}"
context["app_name"] = f"{self.model._meta.app_label}"
context["verbose_name"] = self.model._meta.verbose_name
context["verbose_name_plural"] = self.model._meta.verbose_name_plural
return context
class BaseCreateView(CreateView):
model = None
form_class = None
template_name = "browsing/generic_create.html"
def get_context_data(self, **kwargs):
context = super(BaseCreateView, self).get_context_data()
context["docstring"] = f"{self.model.__doc__}"
context["class_name"] = f"{self.model.__name__}"
return context
class BaseUpdateView(UpdateView):
model = None
form_class = None
template_name = "browsing/generic_create.html"
def get_context_data(self, **kwargs):
context = super(BaseUpdateView, self).get_context_data()
context["docstring"] = f"{self.model.__doc__}"
context["class_name"] = f"{self.model.__name__}"
return context
def model_to_dict(instance):
"""
serializes a model.object to dict, including non editable fields as well as
ManyToManyField fields
"""
field_dicts = []
for x in instance._meta.get_fields():
f_type = "{}".format(type(x))
field_dict = {
"name": x.name,
"help_text": getattr(x, "help_text", ""),
}
try:
field_dict["extra_fields"] = x.extra
except AttributeError:
field_dict["extra_fields"] = None
if "reverse_related" in f_type:
values = getattr(instance, x.name, None)
if values is not None:
field_dict["value"] = values.all()
else:
field_dict["value"] = []
if getattr(x, "related_name", None) is not None:
field_dict["verbose_name"] = getattr(x, "related_name", x.name)
else:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["f_type"] = "REVRESE_RELATION"
elif "related.ForeignKey" in f_type:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["value"] = getattr(instance, x.name, "")
field_dict["f_type"] = "FK"
elif "TreeForeignKey" in f_type:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["value"] = getattr(instance, x.name, "")
field_dict["f_type"] = "FK"
elif "related.ManyToManyField" in f_type:
values = getattr(instance, x.name, None)
if values is not None:
field_dict["value"] = values.all()
else:
field_dict["value"] = []
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["f_type"] = "M2M"
elif "fields.DateTimeField" in f_type:
field_value = getattr(instance, x.name, "")
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["f_type"] = "DateTime"
if field_value:
field_dict["value"] = field_value.strftime("%Y-%m-%d %H:%M:%S")
else:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["value"] = f"{getattr(instance, x.name, '')}"
field_dict["f_type"] = "SIMPLE"
field_dicts.append(field_dict)
return field_dicts
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "acdh-django-browsing"
version = "3.0"
description = "Django-App providing some useful things to create browsing views"
readme = "README.md"
license = { file = "LICENSE" }
authors = [
{name = "Peter Andorfer", email = "peter.andorfer@oeaw.ac.at"}
]
requires-python = ">=3.10"
keywords = ["django"]
dependencies = [
"django-crispy-forms",
"django-filter",
"django-super-deduper",
"django-tables2",
"tablib",
]
[project.optional-dependencies]
# Example for dev dependencies, add as needed
tests = ["pytest", "coverage"]
linting = ["flake8"]
build = ["build"]
[project.urls]
Homepage = "https://github.com/acdh-oeaw/acdh-django-browsing"
[tool.setuptools]
packages = ["browsing",]
+41
-14
Metadata-Version: 2.1
Name: acdh-django-browsing
Version: 2.0
Version: 3.0
Summary: Django-App providing some useful things to create browsing views
Home-page: https://github.com/acdh-oeaw/acdh-django-browsing
Author: Peter Andorfer
Author-email: peter.andorfer@oeaw.ac.at
License: MIT License
Platform: UNKNOWN
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.6
Author-email: Peter Andorfer <peter.andorfer@oeaw.ac.at>
Project-URL: Homepage, https://github.com/acdh-oeaw/acdh-django-browsing
Keywords: django
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: django-crispy-forms
Requires-Dist: django-filter
Requires-Dist: django-super-deduper
Requires-Dist: django-tables2
Requires-Dist: tablib
Provides-Extra: tests
Requires-Dist: pytest; extra == "tests"
Requires-Dist: coverage; extra == "tests"
Provides-Extra: linting
Requires-Dist: flake8; extra == "linting"
Provides-Extra: build
Requires-Dist: build; extra == "build"
[![flake8 Lint](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/lint.yml/badge.svg)](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/lint.yml)
[![Test](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/test.yml/badge.svg)](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/test.yml)

@@ -44,1 +48,24 @@ # acdh-django-browsing

## Develop
### install dev-dependencies
```bash
pip install build pytest coverage flake8 black django django-next-prev
```
### install package
```bash
pip install .
```
### run migrations and start dev server
```
python manage.py migrate
python manage.py runserver
```
### build the package
```
python -m build
```

@@ -1,8 +0,15 @@

acdh-django-charts==0.5.3
crispy-bootstrap5<1,>=0.7
django-autocomplete-light<4,>=3.8.1
django-crispy-forms<3,>=2.0
django-filter<24,>=23.1
django-super-deduper>=0.1.4
django-tables2<3,>=2.5
tablib<4,>=3.5.0
django-crispy-forms
django-filter
django-super-deduper
django-tables2
tablib
[build]
build
[linting]
flake8
[tests]
pytest
coverage
LICENSE.txt
MANIFEST.in
README.md
setup.py
pyproject.toml
acdh_django_browsing.egg-info/PKG-INFO

@@ -12,10 +12,7 @@ acdh_django_browsing.egg-info/SOURCES.txt

browsing/apps.py
browsing/browsing_utils.py
browsing/models.py
browsing/filters.py
browsing/tests.py
browsing/urls.py
browsing/utils.py
browsing/views.py
browsing/migrations/0001_initial.py
browsing/migrations/0002_delete_browsconf.py
browsing/migrations/__init__.py
browsing/static/browsing/js/filter-for-blank-fields.js

@@ -27,10 +24,10 @@ browsing/static/browsing/js/set-form-attributes.js

browsing/templates/browsing/generic_list.html
browsing/templates/browsing/partials/chart_form.html
browsing/templates/browsing/partials/column_selector.html
browsing/templates/browsing/partials/detailview_breadcrumb.html
browsing/templates/browsing/partials/detailview_title.html
browsing/templates/browsing/partials/download_menu.html
browsing/templates/browsing/partials/listview_breadcrumb.html
browsing/templates/browsing/partials/pagination.html
browsing/templates/browsing/partials/table.html
browsing/templates/browsing/tags/class_definition.html
browsing/templates/browsing/tags/column_selector.html
browsing/templatetags/__init__.py
browsing/templatetags/browsing_extras.py
browsing/templates/browsing/tags/column_selector.html

@@ -1,15 +0,15 @@

$(function()
{
var form = $( 'form' )[0];
$( form ).submit(function()
{
$('input, select').each(function()
{
if ($(this).val().length === 0)
{
$(this).prop('disabled', true);
$(this).next().prop('disabled', true);
}
});
document.addEventListener("DOMContentLoaded", function () {
var form = document.querySelector("form");
form.addEventListener("submit", function () {
var inputs = form.querySelectorAll("input, select");
inputs.forEach(function (input) {
if (input.value.length === 0) {
input.disabled = true;
var nextElement = input.nextElementSibling;
if (nextElement) {
nextElement.disabled = true;
}
}
});
});
});
});
(function set_form_attributes() {
var textinput = document.getElementsByClassName('textinput');
for (var i = 0; i < textinput.length; i++) {
textinput[i].classList.add('form-control');
}
var select = document.getElementsByClassName('select');
for (var i = 0; i < select.length; i++) {
select[i].classList.add('custom-select');
}
})();
var textinput = document.getElementsByClassName("textinput");
for (var i = 0; i < textinput.length; i++) {
textinput[i].classList.add("form-control");
}
var select = document.getElementsByClassName("select");
for (var i = 0; i < select.length; i++) {
select[i].classList.add("custom-select");
}
})();

@@ -1,2 +0,2 @@

{% extends "webpage/base.html" %}
{% extends "base.html" %}
{% block title %}{% endblock %}

@@ -3,0 +3,0 @@ {% load crispy_forms_tags %}

@@ -1,63 +0,24 @@

{% extends "webpage/base.html" %}
{% extends "base.html" %}
{% load static %}
{% load webpage_extras %}
{% block title %}{{ object.name }}{% endblock %}
{% block title %}{{ object }}{% endblock %}
{% block content %}
<!-- <div class="container"> -->
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-md-2">
{% if object.get_prev %}
<h2 class="text-start">
<a href="{{ object.get_prev }}" >
<i class="bi bi-chevron-left" title="previous entry"></i>
</a>
</h2>
{% endif %}
</div>
<div class="col-md-8">
<h2 class="text-center">
<small><a href="{{ object.get_listview_url }}"><i class="bi bi-list"
title="back to list view"></i></a></small>
{{ object }}
{% if user.is_authenticated %}
<small>
<a href="{{ object.get_edit_url }}">
<i class="bi bi-pencil-square" title="edit entry"></i>
</a>
</small>
{% endif %}
</h2>
</div>
<div class="col-md-2">
<h2 class="text-end">
{% if object.get_next %}
<a href="{{ object.get_next }}" >
<i class="bi bi-chevron-right" title="next entry"></i>
</a>
</h2>
{% endif %}
</div>
</div>
</div>
<div class="card-body">
{% include 'browsing/partials/detailview_breadcrumb.html' %}
<div class="container">
{% include 'browsing/partials/detailview_title.html' %}
{% block custom %}
<legend>Basic Information</legend>
<table class="table table-bordered table-hover">
<h2>Basic Information</h2>
<dl>
{% for x in object.field_dict %}
<tr>
<th>
<dt>
<abbr title="{{x.help_text}}">{{ x.verbose_name }}</abbr>
</th>
<td>
</dt>
<dd>
{{ x.value }}
</td>
</tr>
</dd>
{% endfor %}
</table>
</div>
</dl>
{% if user.is_authenticated %}
<div class="card-footer">
<div class="float-end">

@@ -68,3 +29,2 @@ <a href="{{ object.get_delete_url }}">

</div>
</div>
{% endif %}

@@ -71,0 +31,0 @@ </div>

@@ -1,8 +0,7 @@

{% extends "webpage/base.html" %}
{% extends "base.html" %}
{% load static %}
{% load django_tables2 %}
{% load browsing_extras %}
{% load crispy_forms_field %}
{% load crispy_forms_tags %}
{% block title %} Browse {{ class_name }} {% endblock %}
{% block title %} Browse {{ verbose_name_plural }} {% endblock %}
{% block scriptHeader %}

@@ -12,58 +11,43 @@

{% block content %}
{% include 'browsing/partials/listview_breadcrumb.html' %}
<div class="card mt-3">
<div class="card-header text-center">
<h1 >
Browse {% class_definition %} {% block list_title %}{% endblock %}
</h1>
{% if user.is_authenticated %}
{% if create_view_link %}
<div class="d-grid gap-2">
<a class="btn btn-primary float-center ms-5 me-5" href="{{ create_view_link }}">Create new {{ class_name }}</a>
<div class="container">
<h1 class="display-5 text-center">{{ h1 }}</h1>
{% if user.is_authenticated and create_view_link %}
<div class="d-grid gap-2 pb-2">
<a class="btn btn-primary float-center ms-5 me-5" href="{{ create_view_link }}">
Create new {{ verbose_name }}</a>
</div>
{% endif %}
{% endif %}
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h2 class="text-center">Search</h2>
{% block customView %}{% endblock %}
{% block create_button %}{% endblock %}
<!--Search mask-->
{% load django_tables2 crispy_forms_tags %}
<form action="." class="uniForm" method="get">
{% crispy filter.form filter.form.helper %}
{% include 'browsing/partials/column_selector.html' %}
<a class="btn btn-primary" href=".">Reset</a>
<button type="submit" class="btn btn-primary">Search</button>
</form>
{% include 'browsing/partials/chart_form.html' %}
</div>
<div class="row">
<div class="col-md-4">
<!--Search mask-->
<h2 class="text-center pb-4 pt-3">Refine Results</h2>
<form action="." class="uniForm" method="get">
{% crispy filter.form filter.form.helper %}
{% include 'browsing/partials/column_selector.html' %}
<div class="d-grid gap-2 pt-2" aria-label="Search or reset search">
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-primary" href=".">Reset</a>
</div>
</form>
</div>
<div class="col-md-8" id="results">
{% with table.paginator.count as total %}
<h2 class="text-center pb-4 pt-3">{{ total }} Result(s)</h2>
{% endwith %}
{% block table %}
{% include 'browsing/partials/table.html' %}
{% endblock table %}
{% block pagination.allpages %}
{% include 'browsing/partials/pagination.html' %}
{% endblock pagination.allpages %}
<div class="float-end">
{% include "browsing/partials/download_menu.html" %}
</div>
<div class="col-md-8" id="results">
<div class="card">
{% with table.paginator.count as total %}
<h2 class="text-center">{{ total }} Result(s)</h2>
{% endwith %}
<div class="card-body table-responsive">
{% block table %}
{% include 'browsing/partials/table.html' %}
{% endblock table %}
{% block pagination.allpages %}
{% include 'browsing/partials/pagination.html' %}
{% endblock pagination.allpages %}
<div class="float-end">
{% include "browsing/partials/download_menu.html" %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@@ -70,0 +54,0 @@ {% endblock %}

@@ -16,6 +16,4 @@ from django.contrib.auth.decorators import login_required

app_name = request.POST.get("app_name", None)
print("##############################")
print(keep, remove, model_name, app_name)
if keep and remove and model_name and app_name:
print("all good")
try:

@@ -38,5 +36,5 @@ ct = ContentType.objects.get(

x.delete()
print("merged {} into {}".format(x, keep_obj))
print(f"merged {x} into {keep_obj}")
return HttpResponseRedirect(go_back)
else:
return HttpResponseRedirect(go_back)
+41
-14
Metadata-Version: 2.1
Name: acdh-django-browsing
Version: 2.0
Version: 3.0
Summary: Django-App providing some useful things to create browsing views
Home-page: https://github.com/acdh-oeaw/acdh-django-browsing
Author: Peter Andorfer
Author-email: peter.andorfer@oeaw.ac.at
License: MIT License
Platform: UNKNOWN
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.6
Author-email: Peter Andorfer <peter.andorfer@oeaw.ac.at>
Project-URL: Homepage, https://github.com/acdh-oeaw/acdh-django-browsing
Keywords: django
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: django-crispy-forms
Requires-Dist: django-filter
Requires-Dist: django-super-deduper
Requires-Dist: django-tables2
Requires-Dist: tablib
Provides-Extra: tests
Requires-Dist: pytest; extra == "tests"
Requires-Dist: coverage; extra == "tests"
Provides-Extra: linting
Requires-Dist: flake8; extra == "linting"
Provides-Extra: build
Requires-Dist: build; extra == "build"
[![flake8 Lint](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/lint.yml/badge.svg)](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/lint.yml)
[![Test](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/test.yml/badge.svg)](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/test.yml)

@@ -44,1 +48,24 @@ # acdh-django-browsing

## Develop
### install dev-dependencies
```bash
pip install build pytest coverage flake8 black django django-next-prev
```
### install package
```bash
pip install .
```
### run migrations and start dev server
```
python manage.py migrate
python manage.py runserver
```
### build the package
```
python -m build
```
[![flake8 Lint](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/lint.yml/badge.svg)](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/lint.yml)
[![Test](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/test.yml/badge.svg)](https://github.com/acdh-oeaw/acdh-django-browsing/actions/workflows/test.yml)

@@ -22,1 +23,26 @@ # acdh-django-browsing

```
## Develop
### install dev-dependencies
```bash
pip install build pytest coverage flake8 black django django-next-prev
```
### install package
```bash
pip install .
```
### run migrations and start dev server
```
python manage.py migrate
python manage.py runserver
```
### build the package
```
python -m build
```
import django_tables2
from django.conf import settings
from django.utils.safestring import mark_safe
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django_tables2.export.views import ExportMixin
if "charts" in settings.INSTALLED_APPS:
from charts.models import ChartConfig
from charts.views import create_payload
input_form = """
<input type="checkbox" name="keep" value="{}" title="keep this"/> |
<input type="checkbox" name="remove" value="{}" title="remove this"/>
"""
class MergeColumn(django_tables2.Column):
"""renders a column with to checkbox - used to select objects for merging"""
def __init__(self, *args, **kwargs):
super(MergeColumn, self).__init__(*args, **kwargs)
def render(self, value):
return mark_safe(input_form.format(value, value))
def get_entities_table(model_class):
class GenericEntitiesTable(django_tables2.Table):
id = django_tables2.LinkColumn()
class Meta:
model = model_class
attrs = {"class": "table table-hover table-striped table-condensed"}
return GenericEntitiesTable
class GenericFilterFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(GenericFilterFormHelper, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.form_class = "genericFilterForm"
self.form_method = "GET"
self.helper.form_tag = False
self.add_input(Submit("Filter", "Search"))
class GenericListView(ExportMixin, django_tables2.SingleTableView):
filter_class = None
formhelper_class = None
context_filter_name = "filter"
paginate_by = 50
template_name = "browsing/generic_list.html"
init_columns = []
enable_merge = False
excluded_cols = []
def get_table_class(self):
if self.table_class:
return self.table_class
else:
return get_entities_table(self.model)
def get_all_cols(self):
all_cols = {
key: value.header for key, value in self.get_table().base_columns.items()
}
return all_cols
def get_queryset(self, **kwargs):
qs = super(GenericListView, self).get_queryset()
self.filter = self.filter_class(self.request.GET, queryset=qs)
self.filter.form.helper = self.formhelper_class()
return self.filter.qs.distinct()
def get_table(self, **kwargs):
table = super(GenericListView, self).get_table()
default_cols = self.init_columns
all_cols = table.base_columns.keys()
selected_cols = self.request.GET.getlist("columns") + default_cols
exclude_vals = [x for x in all_cols if x not in selected_cols]
table.exclude = exclude_vals
return table
def get_context_data(self, **kwargs):
context = super(GenericListView, self).get_context_data()
context["enable_merge"] = self.enable_merge
togglable_colums = {
key: value
for key, value in self.get_all_cols().items()
if key not in self.init_columns and key not in self.exclude_columns
}
context["togglable_colums"] = togglable_colums
context[self.context_filter_name] = self.filter
context["docstring"] = "{}".format(self.model.__doc__)
if self.model._meta.verbose_name_plural:
context["class_name"] = "{}".format(self.model._meta.verbose_name.title())
else:
if self.model.__name__.endswith("s"):
context["class_name"] = "{}".format(self.model.__name__)
else:
context["class_name"] = "{}s".format(self.model.__name__)
try:
context["create_view_link"] = self.model.get_createview_url()
except AttributeError:
context["create_view_link"] = None
model_name = self.model.__name__.lower()
context["entity"] = model_name
context["app_name"] = self.model._meta.app_label
if "charts" in settings.INSTALLED_APPS:
model = self.model
app_label = model._meta.app_label
filtered_objs = ChartConfig.objects.filter(
model_name=model.__name__.lower(), app_name=app_label
)
context["vis_list"] = filtered_objs
context["property_name"] = self.request.GET.get("property")
context["charttype"] = self.request.GET.get("charttype")
if context["charttype"] and context["property_name"]:
qs = self.get_queryset()
chartdata = create_payload(
context["entity"],
context["property_name"],
context["charttype"],
qs,
app_label=app_label,
)
context = dict(context, **chartdata)
return context
class BaseDetailView(DetailView):
model = None
template_name = "browsing/generic_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["docstring"] = "{}".format(self.model.__doc__)
context["class_name"] = "{}".format(self.model.__name__)
context["app_name"] = "{}".format(self.model._meta.app_label)
return context
class BaseCreateView(CreateView):
model = None
form_class = None
template_name = "browsing/generic_create.html"
def get_context_data(self, **kwargs):
context = super(BaseCreateView, self).get_context_data()
context["docstring"] = "{}".format(self.model.__doc__)
context["class_name"] = "{}".format(self.model.__name__)
return context
class BaseUpdateView(UpdateView):
model = None
form_class = None
template_name = "browsing/generic_create.html"
def get_context_data(self, **kwargs):
context = super(BaseUpdateView, self).get_context_data()
context["docstring"] = "{}".format(self.model.__doc__)
context["class_name"] = "{}".format(self.model.__name__)
# if self.model.__name__.endswith('s'):
# context['class_name'] = "{}".format(self.model.__name__)
# else:
# context['class_name'] = "{}s".format(self.model.__name__)
return context
def model_to_dict(instance):
"""
serializes a model.object to dict, including non editable fields as well as
ManyToManyField fields
"""
field_dicts = []
for x in instance._meta.get_fields():
f_type = "{}".format(type(x))
field_dict = {
"name": x.name,
"help_text": getattr(x, "help_text", ""),
}
try:
field_dict["extra_fields"] = x.extra
except AttributeError:
field_dict["extra_fields"] = None
if "reverse_related" in f_type:
values = getattr(instance, x.name, None)
if values is not None:
field_dict["value"] = values.all()
else:
field_dict["value"] = []
if getattr(x, "related_name", None) is not None:
field_dict["verbose_name"] = getattr(x, "related_name", x.name)
else:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["f_type"] = "REVRESE_RELATION"
elif "related.ForeignKey" in f_type:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["value"] = getattr(instance, x.name, "")
field_dict["f_type"] = "FK"
elif "TreeForeignKey" in f_type:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["value"] = getattr(instance, x.name, "")
field_dict["f_type"] = "FK"
elif "related.ManyToManyField" in f_type:
values = getattr(instance, x.name, None)
if values is not None:
field_dict["value"] = values.all()
else:
field_dict["value"] = []
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["f_type"] = "M2M"
elif "fields.DateTimeField" in f_type:
field_value = getattr(instance, x.name, "")
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["f_type"] = "DateTime"
if field_value:
field_dict["value"] = field_value.strftime("%Y-%m-%d %H:%M:%S")
else:
field_dict["verbose_name"] = getattr(x, "verbose_name", x.name)
field_dict["value"] = f"{getattr(instance, x.name, '')}"
field_dict["f_type"] = "SIMPLE"
field_dicts.append(field_dict)
return field_dicts
# Generated by Django 2.2.6 on 2019-12-11 13:34
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="BrowsConf",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"model_name",
models.CharField(
blank=True,
help_text="The name of the model class you like to analyse.",
max_length=255,
),
),
(
"label",
models.CharField(
blank=True,
help_text="The label of the value of interest.",
max_length=255,
),
),
(
"field_path",
models.CharField(
blank=True,
help_text="The constructor of to the value of interest.",
max_length=255,
),
),
],
),
]
# Generated by Django 3.2 on 2021-07-21 09:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("browsing", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name="BrowsConf",
),
]
# from django.db import models
{% if vis_list %}
<div class="btn-group">
<button class="btn btn-default dropdown-toggle dropdown-custom" type="button" id="dropdownMenuVis" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Visualize <span class="caret"></span>
</button>
<div class="dropdown-menu" role="menu">
{% for x in vis_list %}
<li>
{% for y in x.chart_types.all %}
<a class="dropdown-item" title="{{ x.help_text }}" href="?{{ request.GET.urlencode }}&charttype={{ y }}&property={{ x.field_path }}">{{ x.label }} {{ y.icon|safe }}</a>
{% endfor %}
</li>
<hr/>
{% endfor %}
</div>
</div>
{% endif %}
{% if vis_list %}
{% if data %}
<div class="card">
<div class="card-heading">
<h1>{{ data.title }}</h1>
<legend>{{ data.items }}</legend>
<button type="button" class="btn btn-default" data-toggle="collapse" data-target="#howtocite" id="howtocite-btn">JSON data</button>
<div id="howtocite" class="collapse">
<p>{{data}}</p>
</div>
</div>
<div class="card-body">
<div id="bar"></div>
</div>
{% if error %}
<h4>{{ error_msg|safe }}</h4>
{% endif %}
</div>
{% endif %}
{% endif %}
from django import template
from django.contrib.contenttypes.models import ContentType
register = template.Library()
@register.simple_tag
def nav_menu(app=None):
"""creates links to all class of the passed in application
for which get_listview_url() methods have been registered"""
if app:
models = ContentType.objects.filter(app_label=app)
result = []
for x in models:
modelname = x.name
modelname = modelname.replace(" ", "").lower()
try:
fetched_model = ContentType.objects.get(
app_label=app, model=modelname
).model_class()
item = {
"name": modelname.title(),
}
except Exception as e:
print(e)
item = {"name": None}
try:
item["link"] = fetched_model.get_listview_url()
result.append(item)
except AttributeError:
item["link"] = None
return result
@register.inclusion_tag("browsing/tags/class_definition.html", takes_context=True)
def class_definition(context):
values = {}
try:
values["class_name"] = context["class_name"]
values["docstring"] = context["docstring"]
except Exception as e:
print(e)
pass
return values
@register.inclusion_tag("browsing/tags/column_selector.html", takes_context=True)
def column_selector(context):
return context
from setuptools import find_packages, setup
with open("README.md") as readme_file:
readme = readme_file.read()
setup(
name="acdh-django-browsing",
version="2.0",
packages=find_packages(),
include_package_data=True,
license="MIT License",
description="Django-App providing some useful things to create browsing views",
long_description=readme,
long_description_content_type="text/markdown",
url="https://github.com/acdh-oeaw/acdh-django-browsing",
author="Peter Andorfer",
author_email="peter.andorfer@oeaw.ac.at",
classifiers=[
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 3.0",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", # example license
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.6",
],
install_requires=[
"acdh-django-charts==0.5.3",
"django-autocomplete-light>=3.8.1,<4",
"django-crispy-forms>=2.0,<3",
"crispy-bootstrap5>=0.7,<1",
"django-filter>=23.1,<24",
"django-super-deduper>=0.1.4",
"django-tables2>=2.5,<3",
"tablib>=3.5.0,<4",
],
)