Add and display htmx comments to arbitrary Django models.
Project description
Comments
Overview
Enable basic commenting functionality for an arbitrary Django model that contains an AbstractCommentable
mixin class.
from comments.models import AbstractCommentable
# sentinels/models.py
class Sentinel(AbstractCommentable): # arbitrary
title = models.CharField(max_length=50)
...
# comments/models.py
class AbstractCommentable(models.Model): # generic foreign relationships to comments
comments = GenericRelation(Comment, related_query_name="%(app_label)s_%(class)ss")
class Meta:
abstract = True
Premises
Any model e.g. Essay
, Article
, etc... and (not just Sentinel
) can be "commentable". But for purposes of demonstration, we'll use "sentinel" to refer to the arbitrary model that will have its own comments
field.
More specifically, the instances of such sentinel – e.g. Sentinel with id=1, Sentinel with id=2, etc. – need to have their own related comments. This means having the ability to:
- View a list of existing comments on sentinel id=1
- Adding a comment (if logged in) on sentinel id=2
- Deleting a comment (if made by an
author
) - Updating an added comment's
content
(if made by anauthor
) - Toggling visibility of the comment to the public.
All instances of the Sentinel
model therefore will need their own commenting actions. This app produces those actions through urls. See the following shell commands that show the desired url routes per sentinel instance:
# instance is made, e.g. id=1, id=2, etc.
>>> obj = Sentinel.objects.create(title="A sample title")
# distinguish a `Sentinel` model with comments from an `Essay` model with comments using the app_name
>>> from sentinels.urls import app_name # required
>>> obj.add_comment_url # url to add a comment to `A sample title`
Set sentinel namespace
The app_name
namespace can be set/found in the Sentinel
's urls.py:
# sentinels/urls.py
from .views import SentinelListView
app_name = "sentinels" # this is the namespace
urlpatterns = [path("", SentinelListView.as_view(), name="sentinel_list"), ...]
Thus, the namespace
/ app_name
becomes sentinels
and can produce a URL via a reverse function:
from django.urls import reverse
reverse("sentinels:sentinel_list") # results in the url that will call `SentinelListView.as_view()`
Add sentinel properties
Copy and paste an attribute to the Sentinel
model:
# sentinels/models.py
from comments.models import AbstractCommentable
class Sentinel(AbstractCommentable):
...
@cached_property
def add_comment_url(self) -> str:
from .urls import app_name
return reverse(f"{app_name}:hx_comment_adder", kwargs={"pk": self.pk})
Assuming a sentinel instance obj
, the declared property will enable the use of add_comment_url
.
Add sentinel-comment views
Copy and paste the view:
# sentinels/views.py
from comments.views import hx_add_comment_to_target_obj
from comments.models import Comment
from .models import Sentinel
def hx_comment_adder(request: HttpRequest, pk: int) -> TemplateResponse:
obj = Sentinel.objects.get(pk=pk)
return hx_add_comment_to_target_obj(request, obj)
Add sentinel-comment urls
Add the created view function to a url.
# sentinels/urls.py
from .views import hx_comment_adder
app_name = "sentinels"
url_patterns = [path("add_coment/target/<int:pk>", hx_comment_adder, name="hx_comment_adder"), ...]
With this done, the following route, obj.add_comment_url
becomes operational:
from django.urls import reverse
reverse("sentinels:hx_comment_adder", kwargs={"pk":pk})
Sentinel instances will now have access to comment urls
See obj.add_comment_url
as used in a template tag. The form that represents this "add comment" action / url will be loaded in every comment list:
<!-- sentinels/templates/sentinel_detail.html -->
<h1>Title: {{ object.title }}</h1>
{% load comments %} <!-- see templatetags/comments.py -->
{% list_comments %}
Load comment form in sentinel view
The basic view does not yet show a comment form.
When the htmx
-ed <div>
is loaded to the DOM, however, a few things happen because of the insertion of attributes in the add_comment_template
:
<!-- comments/templates/inserter.html -->
...
<section>
<div hx-trigger="load" hx-get="{{form_url}}" hx-target="this" hx-swap="innerHTML">
</div>
</section>
...
{% if inserted %}
{% include './card.html' with comment=inserted %}
{% endif %}
The loading of the <div>
triggers a GET
request to the form_url
aka obj.add_comment_url
. The response is swapped into the DOM, replacing this
blank div with a rendered <form></form>
, i.e. InputCommentModelForm
.
No page refresh was done, courtesy of html-sent-over-the-wire.
Comment added to the top of the sentinel view, without page refresh
Note that in the above inserter
template, an inserted
variable is declared:
...
{% if inserted %}
{% include '../card.html' with comment=inserted %}
{% endif %}
When the fields of the instantiated form is populated and submitted, a POST
request is sent to the same hx_comment_adder
url.
The response targets the entire <section>
because of the form's <submit>
attributes declared via django-crispy-forms but it will replace the entire html fragment above because of swapping "outerHTML" (on the div) response from POST
:
# comments/forms.py
from crispy_forms.layout import Submit
...
Submit(
"submit",
"Submit",
hx_post=submit_url, # i.e. `hx_comment_adder`
hx_target=f"closest section", # see comments/templates/inserter.html
hx_swap="outerHTML",
hx_trigger="click",
)
# comments/views.py
from django.template.response import TemplateResponse
def hx_add_comment_to_target_obj(request: HttpRequest, target_obj: ContentType):
...
if request.method == "POST" and form.is_valid():
return TemplateResponse(
request,
"comment/inserter.html", # see comments/templates/inserter.html
{
"inserted": comment, # newly inserted comment at the top of the list of comments
"form_url": request.path, # reloads the form because of hx-trigger "load"
"label": "Add Comment",
},
)
...
The add_comment_template
is then reset:
- The user can add a new comment since the form is replaced with an empty one;
- The recently
inserted
comment is reflected at the top of the list of comments. - No page refresh is done, against courtesty of htmx.
Repeat the process for other models
- The procedure above was undertaken with respect to the
Sentinel
model. - For another Django app, let's say
articles
with anArticle
model, the same procedure can be followed.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for django-add-comments-0.0.1.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 330c8763efd323c605b033615bcd9e31121ab01c2aa9783fba1a9bb7cd08d896 |
|
MD5 | db54e7c3fbbb82c874f628473b300099 |
|
BLAKE2b-256 | adb156910d7e201db1917e8aea8e35cdbd3292405f592d045a1a885cee527866 |
Hashes for django_add_comments-0.0.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4f3e29396c7229fcf0d2a642ee74f748e8e2bc90f840649e82f0cd996159ad41 |
|
MD5 | 70c0688c81cd96f50b5f6fd05dc69c02 |
|
BLAKE2b-256 | 94ac09520b2ca366625c30a1d18af6cbc80d8ad5db351abe9ebb454c8640c23c |