Skip to content

Commit

Permalink
feat: make generic crudl views
Browse files Browse the repository at this point in the history
Create generic Create, View, Update, Delete and List views that work
with ContentTypes

Closes: #509
  • Loading branch information
b1rger committed Dec 22, 2023
1 parent 3fcd5d8 commit c4b7193
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 0 deletions.
6 changes: 6 additions & 0 deletions apis_core/core/templatetags/apiscore.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import template
from django.conf import settings
from django.contrib.contenttypes.models import ContentType


register = template.Library()
Expand All @@ -13,3 +14,8 @@ def shared_url():
@register.simple_tag
def page_range(paginator, number):
return paginator.get_elided_page_range(number=number)


@register.filter
def contenttype(model):
return ContentType.objects.get_for_model(model)
13 changes: 13 additions & 0 deletions apis_core/generic/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import django_tables2 as tables
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType


class GenericTable(tables.Table):
desc = tables.Column(empty_values=(), linkify=lambda record: reverse("apis_core:generic:genericdetail", args=[ContentType.objects.get_for_model(record), record.pk]))

class Meta:
fields = ['id', 'desc']

def render_desc(self, record):
return str(record)
32 changes: 32 additions & 0 deletions apis_core/generic/templates/generic/generic_confirm_delete.html
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">&times;</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 %}
6 changes: 6 additions & 0 deletions apis_core/generic/templates/generic/generic_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% extends basetemplate|default:"base.html" %}
{% load render_table from django_tables2 %}

{% block content %}
{{ object }}
{% endblock content %}
44 changes: 44 additions & 0 deletions apis_core/generic/templates/generic/generic_filter.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends basetemplate|default:"base.html" %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
{% load apiscore %}

{% block content %}
<div class="container-fluid">
<div class="row">
{% if filter %}
<div class="col-4">
<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>
</div>
{% endif %}
{% if table %}
<div class="col-8">
<div class="card">
<div class="card-header">
</div>
<div class="card-body">
{% block table %}
{% render_table table %}
{% endblock table %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}
6 changes: 6 additions & 0 deletions apis_core/generic/templates/generic/generic_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% extends basetemplate|default:"base.html" %}
{% load crispy_forms_tags %}

{% block content %}
{% crispy form %}
{% endblock content %}
36 changes: 36 additions & 0 deletions apis_core/generic/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
from django.urls import include, path, register_converter

from apis_core.generic import views


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(
"<ccc:contenttype>/",
include(
[
path("", views.ListCC.as_view(), name="genericlist"),
path("<int:pk>", views.DetailCC.as_view(), name="genericdetail"),
path("create", views.CreateCC.as_view(), name="create"),
path("delete/<int:pk>", views.DeleteCC.as_view()),
path("update/<int:pk>", views.UpdateCC.as_view()),
]
),
)
]
55 changes: 55 additions & 0 deletions apis_core/generic/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.views.generic import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django_filters.views import FilterView
from django_tables2 import SingleTableMixin
from django_tables2.tables import table_factory

from .tables import GenericTable

class CCMixin:
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.model = kwargs.get("contenttype").model_class()
self.queryset = self.model.objects.all()

def get_template_names(self):
return super().get_template_names() + [
f"generic/generic{self.template_name_suffix}.html"
]


class ListCC(CCMixin, SingleTableMixin, FilterView):
def get_table_class(self):
return table_factory(self.kwargs.get("contenttype").model_class(), GenericTable)

def get_filterset(self, filterset_class):
filterset = super().get_filterset(filterset_class)
filterset.form.helper = FormHelper()
filterset.form.helper.form_method = "GET"
filterset.form.helper.add_input(Submit('submit', 'Submit'))
return filterset


class DetailCC(CCMixin, DetailView):
pass


class CreateCC(CCMixin, CreateView):
fields = "__all__"
template_name = "generic/generic_form.html"


class DeleteCC(CCMixin, DeleteView):
def get_success_url(self):
return reverse(
"apis:generic:genericlist",
args=[self.request.resolver_match.kwargs["contenttype"]],
)


class UpdateCC(CCMixin, UpdateView):
fields = "__all__"
1 change: 1 addition & 0 deletions apis_core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ def build_apis_mock_request(method, path, view, original_request, **kwargs):
name="GetEntityGeneric",
),
path("api/dumpdata", Dumpdata.as_view()),
path("", include("apis_core.generic.urls", namespace="generic")),
]

if "apis_fulltext_download" in settings.INSTALLED_APPS:
Expand Down

0 comments on commit c4b7193

Please sign in to comment.