Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update from carltongibson/neapolitan upstream #15

Merged
merged 21 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dee47bf
Adjusted doc strings for autodoc usage.
carltongibson Mar 24, 2024
c908766
Moved request handlers to top of CRUDView.
carltongibson Mar 24, 2024
3436085
Added request handlers to reference docs.
carltongibson Mar 24, 2024
af39a49
Tweaked docs wording.
carltongibson Mar 24, 2024
9a9b197
Added QuerySet and object lookup methods to reference.
carltongibson Mar 24, 2024
2e5e20c
Add form, pagination and filtering, and rendering methods to reference.
carltongibson Mar 24, 2024
1290d17
Added get_urls() to reference.
carltongibson Mar 24, 2024
91e472f
Added filterset to list view context.
carltongibson Mar 24, 2024
cf2cd39
Added change notes and bumped version for 24.3 release.
carltongibson Mar 24, 2024
9c2b96d
Improved app folder discovery in mktemplate command. (#43)
nanorepublica Apr 26, 2024
5987386
Allow URL customisation and specifying subset of CRUD roles.
carltongibson Apr 26, 2024
b6a2067
Added docs for get_urls() and as_view().
carltongibson Apr 26, 2024
dcd03ad
Added changelog for 9c2b96d5.
carltongibson Apr 26, 2024
eacd6fe
Bumped version number for v23.4 release.
carltongibson Apr 26, 2024
40be954
Fixed code-block markup in Changelog.
carltongibson Apr 26, 2024
b2f45b9
Fixed typo in changelog.
carltongibson Apr 26, 2024
a9ec3f8
Merge branch 'main' of https://github.com/carltongibson/neapolitan in…
joshuadavidthomas Jul 1, 2024
7d93b56
first pass of pulling our changes back in
joshuadavidthomas Jul 1, 2024
8c16c17
add back missing `self`
joshuadavidthomas Jul 1, 2024
79a5e87
add missing arg
joshuadavidthomas Jul 2, 2024
0b38778
switch to using filterset in context
joshuadavidthomas Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,89 @@ Version numbers correspond to git tags. Please use the compare view on GitHub
for full details. Until we're further along, I will just note the highlights
here:

24.4
====

* ``CRUDView`` subclasses may now pass a set of ``roles`` to ``get_urls()`` in
order to route only a subset of all the available CRUD roles.

As an example, to route only the list and detail views, the README/Quickstart example
would become::

from neapolitan.views import CRUDView, Role
from .models import Bookmark

class BookmarkView(CRUDView):
model = Bookmark
fields = ["url", "title", "note"]
filterset_fields = [
"favourite",
]

urlpatterns = [
*BookmarkView.get_urls(roles={Role.LIST, Role.DETAIL}),
]

In order to keep this logic within the view here, you would likely override
``get_urls()`` in this case, rather than calling it from the outside in your
URL configuration.

* As well as setting the existing ``lookup_field`` (which defaults to ``"pk"``)
and ``lookup_url_kwarg`` (which defaults to ``lookup_field`` if not set) you
may now set ``path_converter`` and ``url_base`` attributes on your
``CRUDView`` subclass in order to customise URL generation.

For example, for this model and ``CRUDView``::

class NamedCollection(models.Model):
name = models.CharField(max_length=25, unique=True)
code = models.UUIDField(unique=True, default=uuid.uuid4)

class NamedCollectionView(CRUDView):
model = NamedCollection
fields = ["name", "code"]

lookup_field = "code"
path_converter = "uuid"
url_base = "named-collections"

``CRUDView`` will generate URLs such as ``/named-collections/``,
``/named-collections/<uuid:code>/``, and so on. URL patterns will be named
using ``url_base``: "named-collections-list", "named-collections-detail", and
so on.

Thanks to Kasun Herath for preliminary discussion and exploration here.

* BREAKING CHANGE. In order to facilitate the above the ``object_list``
template tag now takes the whole ``view`` from the context, rather than just
the ``view.fields``.

If you've overridden the ``object_list.html`` template and are still using
the ``object_list`` template tag, you will need to update your usage to be
like this:

.. code-block:: html+django

{% object_list object_list view %}

* Improved app folder discovery in mktemplate command.

Thanks to Andrew Miller.

24.3
====

* Added the used ``filterset`` to list-view context.

* Added CI testing for supported Python and Django versions. (Python 3.10
onwards; Django 4.2 onwards, including the development branch.)

Thanks to Josh Thomas.

* Added CI build for the documentation.

Thanks to Eduardo Enriquez

24.2
====

Expand Down
5 changes: 4 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ I want easy CRUD views for it, without it taking all day:

# urls.py
from neapolitan.views import CRUDView
from .models import Bookmark

class BookmarkView(CRUDView):
model = Bookmark
Expand All @@ -32,7 +33,9 @@ I want easy CRUD views for it, without it taking all day:
"favourite",
]

urlpatterns = [ ... ] + BookmarkView.get_urls()
urlpatterns = [
*BookmarkView.get_urls(),
]

Neapolitan's ``CRUDView`` provides the standard list, detail,
create, edit, and delete views for a model, as well as the hooks you need to
Expand Down
179 changes: 177 additions & 2 deletions docs/source/crud-view.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,182 @@
CRUDView Reference
==================

.. autoclass:: neapolitan.views.CRUDView
.. py:currentmodule:: neapolitan.views

.. autoclass:: CRUDView

.. automethod:: neapolitan.views.CRUDView.get_context_data
Request Handlers
================

The core of a class-based view are the request handlers — methods that convert
an HTTP request into an HTTP response. The request handlers are the essence of
the **Django view**.

Neapolitan's ``CRUDView`` provides handlers the standard list, detail, create,
edit, and delete views for a model.

List and Detail Views
----------------------

.. automethod:: CRUDView.list

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.list

.. automethod:: CRUDView.detail

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.detail

Create and Update Views
-----------------------

.. automethod:: CRUDView.show_form

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.show_form

.. automethod:: CRUDView.process_form

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.process_form

Delete View
-----------

.. automethod:: CRUDView.confirm_delete

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.confirm_delete

.. automethod:: CRUDView.process_deletion

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.process_deletion


QuerySet and object lookup
==========================

.. automethod:: CRUDView.get_queryset

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_queryset

.. automethod:: CRUDView.get_object

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_object


Form handling
=============

.. automethod:: CRUDView.get_form_class

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_form_class

.. automethod:: CRUDView.get_form

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_form

.. automethod:: CRUDView.form_valid

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.form_valid

.. automethod:: CRUDView.form_invalid

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.form_invalid

.. automethod:: CRUDView.get_success_url

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_success_url

Pagination and filtering
========================

.. automethod:: CRUDView.get_paginate_by

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_paginate_by

.. automethod:: CRUDView.get_paginator

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_paginator

.. automethod:: CRUDView.paginate_queryset

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.paginate_queryset

.. automethod:: CRUDView.get_filterset

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_filterset

Response rendering
==================

.. automethod:: CRUDView.get_context_object_name

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_context_object_name

.. automethod:: CRUDView.get_context_data

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_context_data

.. automethod:: CRUDView.get_template_names

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_template_names

.. automethod:: CRUDView.render_to_response

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.render_to_response

URLs and view callables
=======================

.. automethod:: CRUDView.get_urls

This is the usual entry-point for routing all CRUD URLs in a single pass::

urlpatterns = [
*BookmarkView.get_urls(),
]

Optionally, you may provide an appropriate set of roles in order to limit
the handlers exposed::

urlpatterns = [
*BookmarkView.get_urls(roles={Role.LIST, Role.DETAIL}),
]

Subclasses may wish to override ``get_urls()`` in order to encapsulate such
logic.

.. literalinclude:: ../../src/neapolitan/views.py
:pyobject: CRUDView.get_urls


.. automethod:: CRUDView.as_view

This is the lower-level method used to manually route individual URLs.

It's extends the Django `View.as_view()` method, and should be passed a an
appropriate ``Role`` giving the handlers to be exposed::

path(
"bookmarks/",
BookmarkCRUDView.as_view(role=Role.LIST),
name="bookmark-list",
)
2 changes: 1 addition & 1 deletion src/neapolitan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ class BookmarkView(CRUDView):
Let's go! 🚀
"""

__version__ = "24.2"
__version__ = "24.4"
4 changes: 3 additions & 1 deletion src/neapolitan/management/commands/mktemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.core.management.base import BaseCommand, CommandError
from django.template.loader import TemplateDoesNotExist, get_template
from django.template.engine import Engine
from django.apps import apps

class Command(BaseCommand):
help = "Bootstrap a CRUD template for a model, copying from the active neapolitan default templates."
Expand Down Expand Up @@ -92,7 +93,8 @@ def handle(self, *args, **options):
# Find target directory.
# 1. If f"{app_name}/templates" exists, use that.
# 2. Otherwise, use first project level template dir.
target_dir = f"{app_name}/templates"
app_config = apps.get_app_config(app_name)
target_dir = f"{app_config.path}/templates"
if not Path(target_dir).exists():
try:
target_dir = Engine.get_default().template_dirs[0]
Expand Down
2 changes: 1 addition & 1 deletion src/neapolitan/templates/neapolitan/object_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ <h1 class="sm:flex-auto text-base font-semibold leading-6 text-gray-900">{{ obje
</div>

{% if object_list %}
{% object_list object_list view.get_list_fields %}
{% object_list object_list view %}
{% else %}
<p class="mt-8">There are no {{ object_verbose_name_plural }}. Create one now?</p>
{% endif %}
Expand Down
19 changes: 10 additions & 9 deletions src/neapolitan/templatetags/neapolitan.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
from django import template
from django.urls import reverse

from neapolitan.views import Role

register = template.Library()


def action_links(object):
model_name = object._meta.model_name
def action_links(view, object):
actions = {
"detail": {
"url": reverse(f"{model_name}-detail", kwargs={"pk": object.pk}),
"url": Role.DETAIL.reverse(view, object),
"text": "View",
},
"update": {
"url": reverse(f"{model_name}-update", kwargs={"pk": object.pk}),
"url": Role.UPDATE.reverse(view, object),
"text": "Edit",
},
"delete": {
"url": reverse(f"{model_name}-delete", kwargs={"pk": object.pk}),
"url": Role.DELETE.reverse(view, object),
"text": "Delete",
},
}
Expand Down Expand Up @@ -45,24 +45,25 @@ def iter():


@register.inclusion_tag("neapolitan/partial/list.html")
def object_list(objects, fields):
def object_list(objects, view):
"""
Renders a list of objects with the given fields.

Inclusion tag usage::

{% object_list objects fields %}
{% object_list objects view %}

Template: ``neapolitan/partial/list.html`` — Will render a table of objects
with links to view, edit, and delete views.
"""

fields = view.get_list_fields()
headers = [objects[0]._meta.get_field(f).verbose_name for f in fields]
object_list = [
{
"object": object,
"fields": [{"name": f, "value": str(getattr(object, f))} for f in fields],
"actions": action_links(object),
"actions": action_links(view, object),
}
for object in objects
]
Expand Down
Loading
Loading