Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Examples with `_codec` show how to use a custom codec. Examples with `_import_ho
- `custom_components` - Shows how you can use custom components
- `props` - Shows some advanced props usage
- `custom_elements` - Shows how you can use [custom HTML elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
- 'django_htmx_pyjsx' - How to use pyjsx togther with Htmx and Django
33 changes: 33 additions & 0 deletions examples/django_htmx_pyjsx_todo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Todo Webapp

A simple todo webapp built with Django, HTMX, and PyJSX.

## Features

- Create new todos
- Display todos dynamically using HTMX

## Installation
0. Install uv
```
sudo snap install astro-uv
```

1. Install dependencies using uv:
```
uv sync
```

2. Run migrations:
```
uv run python manage.py migrate
```

3. Start the development server:
```
uv run python manage.py runserver
```

## Usage

Visit `http://localhost:8000` to access the todo app.
25 changes: 25 additions & 0 deletions examples/django_htmx_pyjsx_todo/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
# Set up PyJSX before anything else
import pyjsx.auto_setup

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo_project.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == "__main__":
main()
21 changes: 21 additions & 0 deletions examples/django_htmx_pyjsx_todo/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[project]
name = "todo-app"
version = "0.1.0"
description = "A simple todo webapp with Django, HTMX, and PyJSX"
authors = [{ name = "Your Name", email = "[email protected]" }]
requires-python = "~=3.12"
readme = "README.md"
dependencies = [
"django~=5.0",
"python_jsx ; python_version >= '3.12' and python_version <= '3.14'",
"django-htmx>=1.17.0,<2",
]

[tool.uv]

[tool.uv.sources]
python_jsx = { git = "https://github.com/tomasr8/pyjsx.git" }

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
19 changes: 19 additions & 0 deletions examples/django_htmx_pyjsx_todo/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo App</title>
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col">
{% include "header.html" %}
<main class="flex-grow">
{% block content %}
{% endblock %}
</main>
{% include "footer.html" %}
</body>
</html>
5 changes: 5 additions & 0 deletions examples/django_htmx_pyjsx_todo/templates/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<footer class="bg-gray-100 border-t border-gray-200 py-4 mt-8">
<div class="container mx-auto px-4 text-center text-gray-600">
<p>&copy; 2023 Todo App. All rights reserved.</p>
</div>
</footer>
2 changes: 2 additions & 0 deletions examples/django_htmx_pyjsx_todo/templates/header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<header class="bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-md h-20">
</header>
25 changes: 25 additions & 0 deletions examples/django_htmx_pyjsx_todo/templates/todo_app/todo_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4 text-center text-gray-800">Todo List</h1>

<!-- Form to create new todo -->
<form hx-post="{% url 'create_todo' %}" hx-target="#todo-list" hx-swap="innerHTML" class="mb-4">
{% csrf_token %}
<div class="flex">
<input type="text" name="title" placeholder="Enter a new todo"
class="flex-grow p-3 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<button type="submit"
class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-6 rounded-r-lg transition duration-300">
Add Todo
</button>
</div>
</form>

<!-- Todo list Initial rendering only-->
<div id="todo-list">
{{ todos_list|safe }}
</div>
</div>
{% endblock %}
1 change: 1 addition & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import pyjsx.auto_setup
7 changes: 7 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib import admin
from .models import Todo

@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
list_display = ("title", "created_at")
search_fields = ("title",)
5 changes: 5 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig

class TodoAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "todo_app"
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.2.6 on 2025-09-15 15:12

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name="Todo",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("title", models.CharField(max_length=200)),
("created_at", models.DateTimeField(auto_now_add=True)),
("completed", models.BooleanField(default=False)),
],
),
]
Empty file.
9 changes: 9 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.db import models

class Todo(models.Model):
title = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
completed = models.BooleanField(default=False)

def __str__(self):
return self.title
25 changes: 25 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/todo_components.px
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# coding: jsx
from pyjsx import jsx, JSX
from django.db.models import QuerySet
from todo_app.models import Todo

def Todo(title: str, completed: bool = False, **rest) -> JSX:
"""A component that represents a single todo item"""
status_class = "line-through text-gray-500" if completed else "text-gray-800"
return (
<div class="bg-white p-4 mb-3 rounded-lg shadow flex items-center transition-all duration-200 hover:shadow-md">
<span class={f"text-lg {status_class}"}>{title}</span>
</div>
)

def TodoList(todos: QuerySet[Todo], **rest) -> JSX:
"""A component that renders a list of todos"""
if not todos:
return <p class="text-gray-500 text-center py-4">No todos yet.</p>

return (
<div>
{[<Todo title={todo.title} completed={todo.completed} /> for todo in todos]}
</div>
)

7 changes: 7 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.urls import path
from . import views

urlpatterns = [
path("", views.todo_list, name="todo_list"),
path("create/", views.create_todo, name="create_todo"),
]
22 changes: 22 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_app/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.shortcuts import render
from django.http import HttpResponse
from .models import Todo
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_protect
from .todo_components import TodoList

# Http Views
@csrf_protect
def todo_list(request):
todos = Todo.objects.all().order_by("-created_at")
return render(request, "todo_app/todo_list.html", {"todos_list": TodoList(todos)})

@require_http_methods(["POST"])
@csrf_protect
def create_todo(request):
title = request.POST.get("title")
if title:
Todo.objects.create(title=title)
# For HTMX, we'll return the updated list
todos = Todo.objects.all().order_by("-created_at")
return HttpResponse(TodoList(todos))
Empty file.
16 changes: 16 additions & 0 deletions examples/django_htmx_pyjsx_todo/todo_project/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for todo_project project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo_project.settings")

application = get_asgi_application()
Loading