-
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.
Create generic Create, View, Update, Delete and List views that work with ContentTypes Closes: #509
- Loading branch information
Showing
17 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
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
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,14 @@ | ||
from rest_framework import viewsets | ||
from .serializers import serializer_factory | ||
|
||
|
||
class GenericViewSet(viewsets.ModelViewSet): | ||
def dispatch(self, *args, **kwargs): | ||
self.model = kwargs.get("contenttype").model_class() | ||
return super().dispatch(*args, **kwargs) | ||
|
||
def get_queryset(self): | ||
return self.model.objects.all() | ||
|
||
def get_serializer_class(self): | ||
return serializer_factory(self.model) |
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,23 @@ | ||
from django_filters.filterset import FilterSet | ||
from .forms import GenericFilterSetForm | ||
|
||
|
||
class GenericFilterSet(FilterSet): | ||
|
||
@property | ||
def form(self): | ||
if not hasattr(self, "_form"): | ||
Form = self.get_form_class() | ||
if self.is_bound: | ||
self._form = Form(self.data, prefix=self.form_prefix, model=self._meta.model) | ||
else: | ||
self._form = Form(prefix=self.form_prefix, model=self._meta.model) | ||
return self._form | ||
|
||
|
||
def filterset_factory(model, fields="__all__"): | ||
meta = type(str("Meta"), (object,), {"model": model, "fields": fields, "form": GenericFilterSetForm}) | ||
filterset = type( | ||
str("%sFilterSet" % model._meta.object_name), (GenericFilterSet,), {"Meta": meta} | ||
) | ||
return filterset |
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,31 @@ | ||
from django import forms | ||
from crispy_forms.helper import FormHelper | ||
from crispy_forms.layout import Submit | ||
|
||
|
||
class GenericFilterSetForm(forms.Form): | ||
columns = forms.MultipleChoiceField(required=False) | ||
|
||
def __init__(self, *args, **kwargs): | ||
model = kwargs.pop("model") | ||
super().__init__(*args, **kwargs) | ||
self.fields["columns"].choices = [(field.name, field.verbose_name) for field in model._meta.fields] | ||
|
||
self.helper = FormHelper() | ||
self.helper.form_method = "GET" | ||
self.helper.add_input(Submit('submit', 'Submit')) | ||
|
||
def clean(self): | ||
self.cleaned_data = super().clean() | ||
self.cleaned_data.pop("columns") | ||
return self.cleaned_data | ||
|
||
|
||
class GenericModelForm(forms.ModelForm): | ||
class Meta: | ||
fields = "__all__" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.helper = FormHelper(self) | ||
self.helper.add_input(Submit('submit', 'Submit')) |
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,9 @@ | ||
from rest_framework.serializers import ModelSerializer | ||
|
||
|
||
def serializer_factory(model, fields="__all__", **kwargs): | ||
meta = type(str("Meta"), (object,), {"model": model, "fields": fields}) | ||
serializer = type( | ||
str("%sModelSerializer" % model._meta.object_name), (ModelSerializer,), {"Meta": meta} | ||
) | ||
return serializer |
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,38 @@ | ||
import django_tables2 as tables | ||
|
||
|
||
class CustomTemplateColumn(tables.TemplateColumn): | ||
template_name = None | ||
orderable = None | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(template_name=self.template_name, orderable=self.orderable, *args, **kwargs) | ||
|
||
|
||
class DeleteColumn(CustomTemplateColumn): | ||
template_name = "columns/delete.html" | ||
orderable = False | ||
|
||
|
||
class DescriptionColumn(CustomTemplateColumn): | ||
template_name = "columns/description.html" | ||
orderable = False | ||
|
||
|
||
class GenericTable(tables.Table): | ||
desc = DescriptionColumn() | ||
delete = DeleteColumn() | ||
|
||
class Meta: | ||
fields = ['id', 'desc'] | ||
|
||
# TODO: refactor | ||
def __init__(self, *args, **kwargs): | ||
extra_columns = kwargs.get("extra_columns", []) | ||
fields = [field for field in self._meta.model._meta.fields if field.name in kwargs.get("columns", [])] | ||
for field in fields: | ||
extra_columns.append((field.name, tables.columns.library.column_for_field(field, accessor=field.name))) | ||
del kwargs["columns"] | ||
kwargs["extra_columns"] = extra_columns | ||
|
||
super().__init__(*args, **kwargs) |
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,2 @@ | ||
{% load apiscore %} | ||
<a href="{% url 'apis_core:generic:delete' record|contenttype record.id %}" class="text-danger">✘</a> |
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,2 @@ | ||
{% load apiscore %} | ||
<a href="{% url 'apis_core:generic:detail' record|contenttype record.id %}">{{ record }}</a> |
32 changes: 32 additions & 0 deletions
32
apis_core/generic/templates/generic/generic_confirm_delete.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,32 @@ | ||
{% extends basetemplate|default:"base.html" %} | ||
|
||
{% block content %} | ||
<div class="modal-dialog"> | ||
<div class="modal-content"> | ||
<div class="modal-header"> | ||
<h3 class="modal-title">Confirm delete</h3> | ||
<button type="button" class="close" data-dismiss="modal">×</button> | ||
</div> | ||
<div class="modal-body"> | ||
<form action="" method="post"> | ||
{% csrf_token %} | ||
<h4> | ||
Are you sure you want to delete: <strong>{{ object }}</strong> ? | ||
</h4> | ||
<input class="btn btn-danger" type="submit" value="Yes, I want to delete" /> | ||
</form> | ||
</div> | ||
<div class="modal-footer"> | ||
<input class="btn" | ||
type="submit" | ||
value="No, bring me back" | ||
onclick="goBack()" /> | ||
</div> | ||
</div> | ||
</div> | ||
<script> | ||
function goBack() { | ||
window.history.back(); | ||
} | ||
</script> | ||
{% endblock content %} |
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,14 @@ | ||
{% extends basetemplate|default:"base.html" %} | ||
|
||
{% block content %} | ||
<div class="container-fluid"> | ||
<div class="row"> | ||
<div class="col"> | ||
{% block col %} | ||
{% endblock col %} | ||
</div> | ||
{% block additionalcols %} | ||
{% endblock additionalcols %} | ||
</div> | ||
</div> | ||
{% endblock content %} |
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,23 @@ | ||
{% extends "generic/generic_content.html" %} | ||
{% load apiscore %} | ||
|
||
{% if object %} | ||
{% block col %} | ||
<div class="card"> | ||
<div class="card-header"> | ||
{{ object }} | ||
</div> | ||
<div class="card-body"> | ||
<table class="table table-hover"> | ||
{% modeldict object as d %} | ||
{% for key, value in d.items %} | ||
<tr> | ||
<th>{{ key.verbose_name }}</th> | ||
<td>{{ value }}</td> | ||
</tr> | ||
{% endfor %} | ||
</table> | ||
</div> | ||
</div> | ||
{% endblock col %} | ||
{% endif %} |
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,13 @@ | ||
{% extends "generic/generic_content.html" %} | ||
{% load crispy_forms_tags %} | ||
|
||
{% block col %} | ||
<div class="card"> | ||
<div class="card-header"> | ||
Edit {{ object }} | ||
</div> | ||
<div class="card-body"> | ||
{% crispy form form.helper %} | ||
</div> | ||
</div> | ||
{% endblock col %} |
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,43 @@ | ||
{% extends "generic/generic_content.html" %} | ||
{% load render_table from django_tables2 %} | ||
{% load crispy_forms_tags %} | ||
{% load apiscore %} | ||
|
||
{% if filter %} | ||
{% block col %} | ||
<div class="card"> | ||
<div class="card-header"> | ||
<div class="row"> | ||
<div class="col">{{ object_list.model|contenttype }}</div> | ||
<div class="col"><a class="btn btn-outline-success float-right btn-sm" href="{% url 'apis_core:generic:create' object_list.model|contenttype %}">Create</a></div> | ||
</div> | ||
</div> | ||
<div class="card-body"> | ||
{% block filter %} | ||
{% crispy filter.form filter.form.helper %} | ||
{% endblock filter %} | ||
</div> | ||
<div class="card-footer text-muted"> | ||
<a class="btn btn-outline-secondary" href=".">Reset filter</a> | ||
</div> | ||
</div> | ||
{% endblock col %} | ||
{% endif %} | ||
|
||
|
||
{% if table %} | ||
{% block additionalcols %} | ||
<div class="col-8"> | ||
<div class="card"> | ||
<div class="card-header"> | ||
{{ table.paginator.count }} results | ||
</div> | ||
<div class="card-body"> | ||
{% block table %} | ||
{% render_table table %} | ||
{% endblock table %} | ||
</div> | ||
</div> | ||
</div> | ||
{% endblock additionalcols %} | ||
{% endif %} |
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 @@ | ||
{% extends "generic/generic_content.html" %} | ||
{% load apiscore %} | ||
|
||
{% block col %} | ||
<div class="card"> | ||
<div class="card-header"> | ||
Overview | ||
</div> | ||
<div class="card-body text-center"> | ||
{% contenttypes "apis_ontology" as contenttypes %} | ||
{% for contenttype in contenttypes %} | ||
<a href="{% url 'apis_core:generic:list' contenttype %}"><button type="button" class="btn btn-outline-dark m-2">{{ contenttype }}</button></a> | ||
{% endfor %} | ||
</div> | ||
</div> | ||
{% endblock col %} |
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,42 @@ | ||
from django.contrib.contenttypes.models import ContentType | ||
from django.shortcuts import get_object_or_404 | ||
from django.urls import include, path, register_converter | ||
from rest_framework import routers | ||
|
||
from apis_core.generic import views, api_views | ||
|
||
router = routers.DefaultRouter() | ||
router.register(r"", api_views.GenericViewSet, basename="foo") | ||
|
||
|
||
class ContenttypeConverter: | ||
regex = r"\w+\.\w+" | ||
|
||
def to_python(self, value): | ||
app_label, model = value.split(".") | ||
return get_object_or_404(ContentType, app_label=app_label, model=model) | ||
|
||
def to_url(self, value): | ||
return f"{value.app_label}.{value.model}" | ||
|
||
|
||
register_converter(ContenttypeConverter, "ccc") | ||
|
||
app_name = "generic" | ||
|
||
urlpatterns = [ | ||
path("overview/", views.Overview.as_view(), name="overview"), | ||
path( | ||
"<ccc:contenttype>/", | ||
include( | ||
[ | ||
path("", views.ListCC.as_view(), name="list"), | ||
path("<int:pk>", views.DetailCC.as_view(), name="detail"), | ||
path("create", views.CreateCC.as_view(), name="create"), | ||
path("delete/<int:pk>", views.DeleteCC.as_view(), name="delete"), | ||
path("update/<int:pk>", views.UpdateCC.as_view(), name="update"), | ||
] | ||
), | ||
), | ||
path("api/<ccc:contenttype>/", include(router.urls)), | ||
] |
Oops, something went wrong.