Skip to content

Commit

Permalink
Merge pull request #20 from adinhodovic/add-favorite
Browse files Browse the repository at this point in the history
feat: Add favorite button
  • Loading branch information
adinhodovic committed Feb 27, 2024
2 parents e40b8a3 + a2777c4 commit 02ac5b1
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 13 deletions.
10 changes: 9 additions & 1 deletion django_admin_shellx/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.contrib import admin
from django.urls import path

from django_admin_shellx.views import TerminalView
from django_admin_shellx.views import TerminalView, toggle_favorite

from .models import TerminalCommand

Expand All @@ -13,6 +13,14 @@ class TerminalCommandAdmin(admin.ModelAdmin):
def get_urls(self):

urls = super().get_urls()
urls.insert(
0,
path(
"toggle_favorite/<int:pk>/",
self.admin_site.admin_view(toggle_favorite),
name="django_admin_shellx_terminalcommand_toggle_favorite",
),
)
urls.insert(
0,
path(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@
<link rel="stylesheet"
href="{% static 'django_admin_shellx/output/terminal.css' %}" />
{% endblock extrastyle %}

{% block extrahead %}
{{ block.super }}
{{ ws_port|json_script:"ws_port" }}
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/class-tools.js"></script>
<script type="module"
src="{% static 'django_admin_shellx/output/terminal.js' %}"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
{% endblock extrahead %}

{% block bodyclass %}
{{ block.super }} dashboard
{% endblock bodyclass %}

{% block nav-breadcrumbs %}
{% endblock nav-breadcrumbs %}

{% block nav-sidebar %}
{% endblock nav-sidebar %}

{% block content %}
{{ block.super }}
<div class="flex justify-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<tr>
<th>Command</th>
<th>Prompt</th>
<th>Favorite</th>
<th>Created By</th>
<th>Execution Count</th>
<th>Actions</th>
Expand All @@ -13,22 +12,39 @@
<tr>
<td class="font-bold">{{ command.command }}</td>
<td>{{ command.prompt }}</td>
<td>{{ command.favorite }}</td>
<td>{{ command.created_by }}</td>
<td class="font-bold">{{ command.execution_count }}</td>
<td>
<div class="flex gap-2">
<button class="btn btn-sm btn-neutral"
<button class="btn btn-sm btn-outline"
onclick="location.href='{% url 'admin:django_admin_shellx_terminalcommand_change' command.id %}'">
Edit
<div class="tooltip" data-tip="Edit Command">
<i class="fa fa-edit"></i>
</div>
</button>
<button class="btn btn-sm btn-neutral"
<button class="btn btn-sm btn-outline"
id="djw_copy_command"
data-command="{{ command.command }}">Copy</button>
data-command="{{ command.command }}">
<div class="tooltip" data-tip="Copy Command">
<i class="fa fa-clipboard"></i>
</div>
</button>
<button data-prompt="{{ command.prompt }}"
class="btn btn-sm btn-neutral"
class="btn btn-sm btn-outline"
id="djw_execute_command"
data-command="{{ command.command }}">Execute</button>
data-command="{{ command.command }}">
<div class="tooltip" data-tip="Execute Command">
<i class="fa fa-terminal"></i>
</div>
</button>
<button class="btn btn-sm btn-outline"
hx-get="{% url 'admin:django_admin_shellx_terminalcommand_toggle_favorite' pk=command.id %}"
hx-swap="innerHTML">
<div class="tooltip" data-tip="Favorite Command">
<i id="djw_favorite_icon"
class="fa fa-star {% if command.favorite %}text-yellow-400{% endif %}"></i>
</div>
</button>
</div>
</td>
</tr>
Expand Down
29 changes: 29 additions & 0 deletions django_admin_shellx/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from django.contrib import admin
from django.contrib.admin.models import LogEntry
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.html import format_html, mark_safe
from django.views.generic.base import TemplateView

from .models import TerminalCommand
Expand Down Expand Up @@ -84,3 +89,27 @@ def get_context_data(self, **kwargs):
context["log_entries"] = log_entries

return context


@login_required
def toggle_favorite(request, pk):
instance = get_object_or_404(TerminalCommand, pk=pk)

super_user_required = getattr(settings, "DJANGO_ADMIN_SHELLX_SUPERUSER_ONLY", True)
if super_user_required and not request.user.is_superuser:
raise PermissionDenied("Only superuser can toggle favorite")

if request.method == "GET":
# Toggle the 'favorite' field
instance.favorite = not instance.favorite
instance.save()

color = "text-yellow-500" if instance.favorite else ""
return HttpResponse(
format_html( # pyright: ignore [reportArgumentType]
"<div class='tooltip' data-tip='Favorite Command'><i id='djw_favorite_icon' class='fa fa-star {}'></i></div>",
mark_safe(color),
)
)

return JsonResponse({"status": "error", "message": "Only GET requests are allowed"})
20 changes: 18 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,30 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "django-admin-shellx"
version = "0.1.0"
version = "0.2.0"
description = "A Django Admin Shell"
authors = ["Adin Hodovic <hodovicadin@gmail.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/adinhodovic/django-admin-shellx"
repository = "https://github.com/adinhodovic/django-admin-shellx"
documentation = "https://github.com/adinhodovic/django-admin-shellx"
keywords = ["django", "admin"]
keywords = ["django", "admin", "terminal", "shell", "xterm.js"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
exclude = [
"django_admin_shellx/static/django_admin_shellx/css/terminal.css",
"django_admin_shellx/static/django_admin_shellx/js/terminal.js",
Expand Down Expand Up @@ -114,6 +129,7 @@ blank_line_after_tag = "load,extends"
close_void_tags = true
format_css = true
format_js = true
preserve_blank_line = true
# TODO: remove T002 when fixed https://github.com/Riverside-Healthcare/djLint/issues/687
ignore = "H006,H030,H031,T002"
include = "H017,H035"
Expand Down
45 changes: 45 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,48 @@ def test_list_user_commands(admin_client):
assert res.context["commands"]
assert len(res.context["commands"]) == 1
assert res.context["commands"][0].command == tc.command


def test_toggle_favorite(admin_client):
tc = TerminalCommandFactory()
res = admin_client.get(
reverse(
"admin:django_admin_shellx_terminalcommand_toggle_favorite",
kwargs={"pk": tc.id},
)
)

assert res.status_code == 200
assert "text-yellow-500" in str(res.content)
tc.refresh_from_db()
assert tc.favorite


def test_toggle_favorite_user(user_client):
user_client.user.is_staff = True
user_client.user.save()
tc = TerminalCommandFactory()
res = user_client.get(
reverse(
"admin:django_admin_shellx_terminalcommand_toggle_favorite",
kwargs={"pk": tc.id},
)
)

assert res.status_code == 403


def test_toggle_favorite_anonymous(client):
tc = TerminalCommandFactory()
res = client.get(
reverse(
"admin:django_admin_shellx_terminalcommand_toggle_favorite",
kwargs={"pk": tc.id},
)
)

assert res.status_code == 302
assert (
res.url
== "/admin/login/?next=/admin/django_admin_shellx/terminalcommand/toggle_favorite/1/"
)
12 changes: 10 additions & 2 deletions tests/test_websockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,16 @@ async def test_websocket_send_command(settings, superuser_logged_in):
# Expecting 4 messages from shell
response = await communicator.receive_from()
response += await communicator.receive_from()
response += await communicator.receive_from()
response += await communicator.receive_from()
# TODO(adinhodovic): This is a hack, we should wait for the response to be complete, which is
# tricky due to terminal output
try:
response += await communicator.receive_from()
except TimeoutError:
pass
try:
response += await communicator.receive_from()
except TimeoutError:
pass

assert "LICENSE" in response

Expand Down

0 comments on commit 02ac5b1

Please sign in to comment.