-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit introduces a `collections` app that lets projects define collections and use them to tag content or to implement workflows.
- Loading branch information
Showing
15 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.contrib import admin | ||
|
||
from apis_core.collections.models import SkosCollection, SkosCollectionContentObject | ||
|
||
admin.site.register(SkosCollection) | ||
admin.site.register(SkosCollectionContentObject) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class VocabsConfig(AppConfig): | ||
default_auto_field = "django.db.models.AutoField" | ||
name = "apis_core.collections" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Generated by Django 4.2.8 on 2024-01-23 08:41 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
("contenttypes", "0002_remove_content_type_name"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="SkosCollection", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"name", | ||
models.CharField( | ||
help_text="Collection label or name", | ||
max_length=300, | ||
verbose_name="skos:prefLabel", | ||
), | ||
), | ||
( | ||
"label_lang", | ||
models.CharField( | ||
blank=True, | ||
default="en", | ||
help_text="Language of preferred label given above", | ||
max_length=3, | ||
verbose_name="skos:prefLabel language", | ||
), | ||
), | ||
( | ||
"creator", | ||
models.TextField( | ||
blank=True, | ||
help_text="Person or organisation that created this collectionIf more than one list all using a semicolon ;", | ||
verbose_name="dc:creator", | ||
), | ||
), | ||
( | ||
"contributor", | ||
models.TextField( | ||
blank=True, | ||
help_text="Person or organisation that made contributions to the collectionIf more than one list all using a semicolon ;", | ||
verbose_name="dc:contributor", | ||
), | ||
), | ||
( | ||
"parent", | ||
models.ForeignKey( | ||
blank=True, | ||
null=True, | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="collections.skoscollection", | ||
), | ||
), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name="SkosCollectionContentObject", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
("object_id", models.PositiveIntegerField()), | ||
( | ||
"collection", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="collections.skoscollection", | ||
), | ||
), | ||
( | ||
"content_type", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="contenttypes.contenttype", | ||
), | ||
), | ||
], | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from django.contrib.contenttypes.fields import GenericForeignKey | ||
from django.contrib.contenttypes.models import ContentType | ||
from django.db import models | ||
|
||
|
||
class SkosCollection(models.Model): | ||
""" | ||
SKOS collections are labeled and/or ordered groups of SKOS concepts. | ||
Collections are useful where a group of concepts shares something in common, | ||
and it is convenient to group them under a common label, or | ||
where some concepts can be placed in a meaningful order. | ||
Miles, Alistair, and Sean Bechhofer. "SKOS simple knowledge | ||
organization system reference. W3C recommendation (2009)." | ||
""" | ||
|
||
parent = models.ForeignKey("self", null=True, on_delete=models.CASCADE, blank=True) | ||
name = models.CharField( | ||
max_length=300, | ||
verbose_name="skos:prefLabel", | ||
help_text="Collection label or name", | ||
) | ||
label_lang = models.CharField( | ||
max_length=3, | ||
blank=True, | ||
default="en", | ||
verbose_name="skos:prefLabel language", | ||
help_text="Language of preferred label given above", | ||
) | ||
creator = models.TextField( | ||
blank=True, | ||
verbose_name="dc:creator", | ||
help_text="Person or organisation that created this collection" | ||
"If more than one list all using a semicolon ;", | ||
) | ||
contributor = models.TextField( | ||
blank=True, | ||
verbose_name="dc:contributor", | ||
help_text="Person or organisation that made contributions to the collection" | ||
"If more than one list all using a semicolon ;", | ||
) | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
def children(self): | ||
return SkosCollection.objects.filter(parent=self) | ||
|
||
def children_tree_as_list(self): | ||
childtrees = [self] | ||
for child in self.children(): | ||
childtrees.extend(child.children_tree_as_list()) | ||
return childtrees | ||
|
||
|
||
class SkosCollectionContentObject(models.Model): | ||
""" | ||
*Throughtable* datamodel to connect collections to arbitrary content | ||
""" | ||
|
||
collection = models.ForeignKey(SkosCollection, on_delete=models.CASCADE) | ||
|
||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) | ||
object_id = models.PositiveIntegerField() | ||
content_object = GenericForeignKey("content_type", "object_id") | ||
|
||
def __str__(self): | ||
return f"{self.content_object} -> {self.collection}" |
4 changes: 4 additions & 0 deletions
4
apis_core/collections/templates/collections/collection_children_toggle.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{% load apis_collections %} | ||
{% for child in children %} | ||
{% collection_toggle object child %} | ||
{% endfor %} |
5 changes: 5 additions & 0 deletions
5
apis_core/collections/templates/collections/collection_object_parent.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{% if collectionobject.collection.parent %} | ||
<a href="{% url "collectionobjectparent" content_type.id object.id collectionobject.id %}?to={{ request.get_full_path }}" hx-get="{% url "collectionobjectparent" content_type.id object.id collectionobject.id %}" hx-swap="outerHTML" class="btn btn-sm btn-outline-dark" title="Click to change to {{ collectionobject.collection.parent }}" hx-confirm="Change {{ object }} from {{ collectionobject.collection }} to {{ collectionobject.collection.parent }}?">{{ collectionobject.collection }}</a> | ||
{% else %} | ||
<a class="btn btn-sm btn-outline-dark disabled">{{ collectionobject.collection }}</a> | ||
{% endif %} |
3 changes: 3 additions & 0 deletions
3
apis_core/collections/templates/collections/collection_toggle.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<a href="{% url "collectiontoggle" content_type.id object.id collection.id %}?to={{ request.get_full_path }}" hx-get="{% url "collectiontoggle" content_type.id object.id collection.id %}" hx-swap="outerHTML" class="btn btn-sm | ||
{% if exists %}btn-success{% else %}btn-outline-secondary{% endif %} | ||
">{{ collection.name }}</a> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
from django import template | ||
from django.contrib.contenttypes.models import ContentType | ||
|
||
from apis_core.collections.models import SkosCollectionContentObject, SkosCollection | ||
|
||
|
||
register = template.Library() | ||
|
||
|
||
### templatetags for the APIS collections model ### | ||
|
||
|
||
@register.inclusion_tag("collections/collection_toggle.html", takes_context=True) | ||
def collection_toggle(context, obj, collection): | ||
""" | ||
Provide a button to add or remove a connection between a | ||
collection and an object. | ||
""" | ||
content_type = ContentType.objects.get_for_model(obj) | ||
context["content_type"] = content_type | ||
context["object"] = obj | ||
context["collection"] = collection | ||
context["exists"] = SkosCollectionContentObject.objects.filter( | ||
object_id=obj.id, content_type=content_type, collection=collection | ||
).exists() | ||
return context | ||
|
||
|
||
@register.inclusion_tag("collections/collection_toggle.html", takes_context=True) | ||
def collection_toggle_by_id(context, obj, collectionid): | ||
""" | ||
Wrapper templatetag to allow using `collection_toggle` | ||
with just the `id` of the collection. | ||
""" | ||
collection = SkosCollection.objects.get(pk=collectionid) | ||
return collection_toggle(context, obj, collection) | ||
|
||
|
||
@register.inclusion_tag( | ||
"collections/collection_children_toggle.html", takes_context=True | ||
) | ||
def collection_children_toggle(context, obj, collection): | ||
""" | ||
Provide toggle buttons for all the children of a parent collection. | ||
""" | ||
context["children"] = collection.children() | ||
context["object"] = obj | ||
context["collection"] = collection | ||
return context | ||
|
||
|
||
@register.inclusion_tag( | ||
"collections/collection_children_toggle.html", takes_context=True | ||
) | ||
def collection_children_toggle_by_id(context, obj, collectionid): | ||
""" | ||
Wrapper templatetag to allow using `collection_children_toggle` with | ||
just the `id` of the parent collection. | ||
""" | ||
collection = SkosCollection.objects.get(pk=collectionid) | ||
return collection_children_toggle(context, obj, collection) | ||
|
||
|
||
@register.inclusion_tag("collections/collection_object_parent.html", takes_context=True) | ||
def collection_object_parent(context, obj, collectionobject): | ||
""" | ||
Provide a button to change the connection between an object and | ||
a collection to point to the collections parent. | ||
""" | ||
context["collectionobject"] = collectionobject | ||
context["content_type"] = ContentType.objects.get_for_model(obj) | ||
context["object"] = obj | ||
return context | ||
|
||
|
||
@register.inclusion_tag("collections/collection_object_parent.html", takes_context=True) | ||
def collection_object_parent_by_id(context, obj, collectionobject_id): | ||
""" | ||
Wrapper templatetag to allow using `collection_object_parent` with | ||
just the `id` of the collectionobject. | ||
""" | ||
collectionobject = SkosCollectionContentObject.objects.get(pk=collectionobject_id) | ||
return collection_object_parent(context, obj, collectionobject) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from django.urls import path | ||
|
||
from . import views | ||
|
||
urlpatterns = [ | ||
path( | ||
"collectionobjecttoggle/<int:content_type_id>/<int:object_id>/<int:collection>", | ||
views.CollectionToggle.as_view(), | ||
name="collectiontoggle", | ||
), | ||
path( | ||
"collectionobjectparent/<int:content_type_id>/<int:object_id>/<int:collectionobject>", | ||
views.CollectionObjectParent.as_view(), | ||
name="collectionobjectparent", | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from django.contrib.auth.mixins import LoginRequiredMixin | ||
from django.contrib.contenttypes.models import ContentType | ||
from django.views.generic.base import TemplateView | ||
from django.shortcuts import redirect, get_object_or_404 | ||
|
||
from .models import SkosCollection, SkosCollectionContentObject | ||
|
||
|
||
class ContentObjectMixin: | ||
""" | ||
Setup the ContentType and the object used by a view, based on the | ||
`content_type_id` and the `object_id` arguments passed in the URL. | ||
""" | ||
|
||
def setup(self, *args, **kwargs): | ||
super().setup(*args, **kwargs) | ||
self.content_type = get_object_or_404(ContentType, pk=kwargs["content_type_id"]) | ||
self.object = get_object_or_404( | ||
self.content_type.model_class(), pk=kwargs["object_id"] | ||
) | ||
|
||
def get_context_data(self, *args, **kwargs): | ||
context = super().get_context_data(*args, **kwargs) | ||
context["content_type"] = self.content_type | ||
context["object"] = self.object | ||
return context | ||
|
||
|
||
class CollectionToggle(LoginRequiredMixin, ContentObjectMixin, TemplateView): | ||
""" | ||
Toggle a collection - if a CollectionObject connecting the requested object | ||
and collection does not exist, then create it. If it does exist, delete it. | ||
""" | ||
|
||
template_name = "collections/collection_toggle.html" | ||
|
||
def setup(self, *args, **kwargs): | ||
super().setup(*args, **kwargs) | ||
self.collection = get_object_or_404(SkosCollection, pk=kwargs["collection"]) | ||
|
||
def get_context_data(self, *args, **kwargs): | ||
context = super().get_context_data(*args, **kwargs) | ||
context["exists"] = self.created | ||
context["collection"] = self.collection | ||
return context | ||
|
||
def get(self, *args, **kwargs): | ||
scco, self.created = SkosCollectionContentObject.objects.get_or_create( | ||
collection=self.collection, | ||
content_type=self.content_type, | ||
object_id=self.object.id, | ||
) | ||
if not self.created: | ||
scco.delete() | ||
if redirect_to := self.request.GET.get("to", False): | ||
return redirect(redirect_to) | ||
return super().get(*args, **kwargs) | ||
|
||
|
||
class CollectionObjectParent(LoginRequiredMixin, ContentObjectMixin, TemplateView): | ||
""" | ||
Change the requested CollectionObjects collection to point to the parent of the | ||
current collection. | ||
""" | ||
|
||
template_name = "collections/collection_object_parent.html" | ||
|
||
def get_context_data(self, *args, **kwargs): | ||
collectionobject = get_object_or_404( | ||
SkosCollectionContentObject, pk=kwargs["collectionobject"] | ||
) | ||
if collectionobject.collection.parent: | ||
collectionobject.collection = collectionobject.collection.parent | ||
collectionobject.save() | ||
context = super().get_context_data(*args, **kwargs) | ||
context["collectionobject"] = collectionobject | ||
return context |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.