Compare commits

..

3 Commits

Author SHA1 Message Date
rucki 645cf94056 Fix API item default kind 2026-09-12 11:50:29 +02:00
rucki be76e91e89 Disable browser autocomplete for due time 2026-09-11 18:29:00 +02:00
rucki 854b0035ec Allow deploy branch selection 2026-09-11 17:50:38 +02:00
7 changed files with 58 additions and 6 deletions
+3
View File
@@ -183,3 +183,6 @@ cython_debug/
# Local my2dos project API config # Local my2dos project API config
.my2dos .my2dos
# Local deployment branch override
.deploy-branch
+1 -1
View File
@@ -22,7 +22,7 @@
{% if item.visibility == 'team' %}<option value="team" selected>{% trans "Team" %}{% if item.team %}: {{ item.team.name }}{% endif %}</option>{% endif %} {% if item.visibility == 'team' %}<option value="team" selected>{% trans "Team" %}{% if item.team %}: {{ item.team.name }}{% endif %}</option>{% endif %}
<option value="public" {% if item.visibility == 'public' %}selected{% endif %}>{% trans "Public" %}</option> <option value="public" {% if item.visibility == 'public' %}selected{% endif %}>{% trans "Public" %}</option>
</select></div> </select></div>
{% if item.kind == 'todo' %}<div class="col-md-3"><input class="form-control" type="date" name="due_date" value="{{ item.due_at|date:'Y-m-d' }}" title="{% trans 'Due date' %}"></div><div class="col-md-3"><input class="form-control" name="due_time" list="due-times-{{ item.id }}" value="{{ item.due_at|date:'H:i' }}" placeholder="{% trans 'Time optional' %}" title="{% trans 'Time optional' %}"><datalist id="due-times-{{ item.id }}">{% for option in due_time_options %}<option value="{{ option.value }}"{% if option.label %} label="{{ option.label }}"{% endif %}>{% endfor %}</datalist></div>{% endif %} {% if item.kind == 'todo' %}<div class="col-md-3"><input class="form-control" type="date" name="due_date" value="{{ item.due_at|date:'Y-m-d' }}" title="{% trans 'Due date' %}"></div><div class="col-md-3"><input class="form-control" name="due_time" list="due-times-{{ item.id }}" value="{{ item.due_at|date:'H:i' }}" placeholder="{% trans 'Time optional' %}" title="{% trans 'Time optional' %}" autocomplete="off"><datalist id="due-times-{{ item.id }}">{% for option in due_time_options %}<option value="{{ option.value }}"{% if option.label %} label="{{ option.label }}"{% endif %}>{% endfor %}</datalist></div>{% endif %}
<div class="col-md-4"><input class="form-control" type="file" name="files" multiple x-ref="fileInput" @change="previewFile($event)"></div> <div class="col-md-4"><input class="form-control" type="file" name="files" multiple x-ref="fileInput" @change="previewFile($event)"></div>
</div> </div>
{% if item.kind == 'link' %}<input class="form-control" type="url" name="url" value="{{ item.url }}" placeholder="URL">{% endif %} {% if item.kind == 'link' %}<input class="form-control" type="url" name="url" value="{{ item.url }}" placeholder="URL">{% endif %}
+1 -1
View File
@@ -24,7 +24,7 @@
{% include 'core/_format_toolbar.html' %} {% include 'core/_format_toolbar.html' %}
<div class="row g-2"> <div class="row g-2">
<div class="col-md-4"><select class="form-select" name="visibility"><option value="private" {% if item.visibility == 'private' %}selected{% endif %}>{% trans "Private" %}</option>{% if item.visibility == 'team' %}<option value="team" selected>{% trans "Team" %}{% if item.team %}: {{ item.team.name }}{% endif %}</option>{% endif %}<option value="public" {% if item.visibility == 'public' %}selected{% endif %}>{% trans "Public" %}</option></select></div> <div class="col-md-4"><select class="form-select" name="visibility"><option value="private" {% if item.visibility == 'private' %}selected{% endif %}>{% trans "Private" %}</option>{% if item.visibility == 'team' %}<option value="team" selected>{% trans "Team" %}{% if item.team %}: {{ item.team.name }}{% endif %}</option>{% endif %}<option value="public" {% if item.visibility == 'public' %}selected{% endif %}>{% trans "Public" %}</option></select></div>
{% if item.kind == 'todo' %}<div class="col-md-3"><input class="form-control" type="date" name="due_date" value="{{ item.due_at|date:'Y-m-d' }}" title="{% trans 'Due date' %}"></div><div class="col-md-3"><input class="form-control" name="due_time" list="due-times-{{ item.id }}" value="{{ item.due_at|date:'H:i' }}" placeholder="{% trans 'Time optional' %}" title="{% trans 'Time optional' %}"><datalist id="due-times-{{ item.id }}">{% for option in due_time_options %}<option value="{{ option.value }}"{% if option.label %} label="{{ option.label }}"{% endif %}>{% endfor %}</datalist></div>{% endif %} {% if item.kind == 'todo' %}<div class="col-md-3"><input class="form-control" type="date" name="due_date" value="{{ item.due_at|date:'Y-m-d' }}" title="{% trans 'Due date' %}"></div><div class="col-md-3"><input class="form-control" name="due_time" list="due-times-{{ item.id }}" value="{{ item.due_at|date:'H:i' }}" placeholder="{% trans 'Time optional' %}" title="{% trans 'Time optional' %}" autocomplete="off"><datalist id="due-times-{{ item.id }}">{% for option in due_time_options %}<option value="{{ option.value }}"{% if option.label %} label="{{ option.label }}"{% endif %}>{% endfor %}</datalist></div>{% endif %}
<div class="col-md-4"><input class="form-control" type="file" name="files" multiple x-ref="fileInput" @change="previewFile($event)"></div> <div class="col-md-4"><input class="form-control" type="file" name="files" multiple x-ref="fileInput" @change="previewFile($event)"></div>
</div> </div>
{% if item.kind == 'link' %}<input class="form-control" type="url" name="url" value="{{ item.url }}" placeholder="URL">{% endif %} {% if item.kind == 'link' %}<input class="form-control" type="url" name="url" value="{{ item.url }}" placeholder="URL">{% endif %}
+1
View File
@@ -10,6 +10,7 @@
<h1 class="h5 m-0">{% if collection %}{% blocktrans with title=collection.title %}Create note for {{ title }}{% endblocktrans %}{% elif requested_kind == 'todo' %}{% trans "Create task" %}{% elif requested_kind == 'link' %}{% trans "Create link" %}{% elif requested_kind == 'journal' %}{% trans "Create journal entry" %}{% else %}{% trans "Create note" %}{% endif %}</h1> <h1 class="h5 m-0">{% if collection %}{% blocktrans with title=collection.title %}Create note for {{ title }}{% endblocktrans %}{% elif requested_kind == 'todo' %}{% trans "Create task" %}{% elif requested_kind == 'link' %}{% trans "Create link" %}{% elif requested_kind == 'journal' %}{% trans "Create journal entry" %}{% else %}{% trans "Create note" %}{% endif %}</h1>
<div class="d-flex gap-2"><button class="btn btn-success px-3 py-2" title="{% trans 'Save' %}">✓</button><a class="btn btn-danger px-3 py-2" href="{{ next_url }}" title="{% trans 'Cancel' %}">×</a></div> <div class="d-flex gap-2"><button class="btn btn-success px-3 py-2" title="{% trans 'Save' %}">✓</button><a class="btn btn-danger px-3 py-2" href="{{ next_url }}" title="{% trans 'Cancel' %}">×</a></div>
</div> </div>
<div class="small text-muted">{% trans "Type:" %} {% if requested_kind == 'todo' %}{% trans "Task" %}{% elif requested_kind == 'link' %}{% trans "Link" %}{% elif requested_kind == 'journal' %}{% trans "Journal" %}{% else %}{% trans "Note" %}{% endif %}</div>
<div class="mobile-tag-picker"> <div class="mobile-tag-picker">
<div class="small text-muted mb-1">{% trans "Add tags" %}</div> <div class="small text-muted mb-1">{% trans "Add tags" %}</div>
<div class="d-flex flex-wrap gap-2 p-2 bg-light rounded"> <div class="d-flex flex-wrap gap-2 p-2 bg-light rounded">
+26
View File
@@ -245,6 +245,8 @@ class DefaultItemKindTests(TestCase):
def test_new_item_editor_uses_user_default(self): def test_new_item_editor_uses_user_default(self):
response = self.client.get(reverse('new_item')) response = self.client.get(reverse('new_item'))
self.assertEqual(response.context['requested_kind'], Item.Kind.TODO) self.assertEqual(response.context['requested_kind'], Item.Kind.TODO)
self.assertContains(response, 'Typ:')
self.assertContains(response, 'Aufgabe')
class DueDateCommandTests(TestCase): class DueDateCommandTests(TestCase):
@@ -1122,6 +1124,30 @@ class ApiItemFilterTests(TestCase):
self.assertEqual(len(items), 1) self.assertEqual(len(items), 1)
self.assertEqual(items[0]['id'], second.id) self.assertEqual(items[0]['id'], second.id)
def test_api_create_ignores_user_default_kind(self):
UserPreference.objects.create(user=self.user, default_item_kind=Item.Kind.TODO)
response = self.client.post(
reverse('api_items'), {'content': 'API note #help'},
HTTP_X_API_KEY=self.api_key.token,
)
self.assertEqual(response.status_code, 201)
item = Item.objects.get(content='API note #help')
self.assertEqual(item.kind, Item.Kind.NOTE)
def test_api_create_still_accepts_explicit_kind(self):
UserPreference.objects.create(user=self.user, default_item_kind=Item.Kind.NOTE)
response = self.client.post(
reverse('api_items'), {'content': 'API task #help', 'kind': Item.Kind.TODO},
HTTP_X_API_KEY=self.api_key.token,
)
self.assertEqual(response.status_code, 201)
item = Item.objects.get(content='API task #help')
self.assertEqual(item.kind, Item.Kind.TODO)
class CopyButtonTests(TestCase): class CopyButtonTests(TestCase):
def test_item_has_copy_button_with_original_text(self): def test_item_has_copy_button_with_original_text(self):
+1 -2
View File
@@ -1556,8 +1556,7 @@ def api_items(request):
data = json.loads(request.body or '{}') if request.content_type == 'application/json' else request.POST data = json.loads(request.body or '{}') if request.content_type == 'application/json' else request.POST
raw = data.get('content', '') raw = data.get('content', '')
raw, command_due_at, _ = parse_due_command(raw) raw, command_due_at, _ = parse_due_command(raw)
prefs, created = UserPreference.objects.get_or_create(user=user) kind, visibility, content, url = parse_quick_content(raw, Item.Kind.NOTE)
kind, visibility, content, url = parse_quick_content(raw, prefs.default_item_kind)
due_at = data['due_at'] if 'due_at' in data else command_due_at due_at = data['due_at'] if 'due_at' in data else command_due_at
item = Item.objects.create(workspace=current_workspace(user, request.api_key), owner=user, kind=data.get('kind') or kind, visibility=data.get('visibility') or visibility, content=content, url=data.get('url') or url, due_at=due_at) item = Item.objects.create(workspace=current_workspace(user, request.api_key), owner=user, kind=data.get('kind') or kind, visibility=data.get('visibility') or visibility, content=content, url=data.get('url') or url, due_at=due_at)
item.sync_metadata() item.sync_metadata()
+25 -2
View File
@@ -7,6 +7,8 @@ set -euo pipefail
# #
# Optional: # Optional:
# DEPLOY_REMOTE=origin DEPLOY_BRANCH=main ./deploy.sh # DEPLOY_REMOTE=origin DEPLOY_BRANCH=main ./deploy.sh
# ./deploy.sh workspace-tenancy
# echo workspace-tenancy > .deploy-branch
# SKIP_PIP=1 ./deploy.sh # SKIP_PIP=1 ./deploy.sh
# SKIP_COLLECTSTATIC=1 ./deploy.sh # SKIP_COLLECTSTATIC=1 ./deploy.sh
# DEPLOY_RESTART_CMD="..." ./deploy.sh # DEPLOY_RESTART_CMD="..." ./deploy.sh
@@ -14,7 +16,19 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
REMOTE="${DEPLOY_REMOTE:-origin}" REMOTE="${DEPLOY_REMOTE:-origin}"
BRANCH="${DEPLOY_BRANCH:-$(git branch --show-current 2>/dev/null || echo main)}" BRANCH_SOURCE="current branch"
if [[ $# -gt 0 && -n "${1:-}" ]]; then
BRANCH="$1"
BRANCH_SOURCE="command line"
elif [[ -n "${DEPLOY_BRANCH:-}" ]]; then
BRANCH="$DEPLOY_BRANCH"
BRANCH_SOURCE="DEPLOY_BRANCH"
elif [[ -f ".deploy-branch" && -n "$(tr -d '[:space:]' < .deploy-branch)" ]]; then
BRANCH="$(tr -d '[:space:]' < .deploy-branch)"
BRANCH_SOURCE=".deploy-branch"
else
BRANCH="$(git branch --show-current 2>/dev/null || echo main)"
fi
if [[ -x ".venv/bin/python" ]]; then if [[ -x ".venv/bin/python" ]]; then
PY=".venv/bin/python" PY=".venv/bin/python"
@@ -28,9 +42,18 @@ else
fi fi
echo "==> Deploy $(pwd)" echo "==> Deploy $(pwd)"
echo "==> Branch: ${BRANCH} (${BRANCH_SOURCE})"
echo "==> Python: $($PY --version)" echo "==> Python: $($PY --version)"
echo "==> Git Pull: ${REMOTE}/${BRANCH}" echo "==> Git Update: ${REMOTE}/${BRANCH}"
git fetch "$REMOTE" "$BRANCH" git fetch "$REMOTE" "$BRANCH"
CURRENT_BRANCH="$(git branch --show-current 2>/dev/null || true)"
if [[ "$CURRENT_BRANCH" != "$BRANCH" ]]; then
if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
git checkout "$BRANCH"
else
git checkout -b "$BRANCH" --track "$REMOTE/$BRANCH"
fi
fi
git pull --ff-only "$REMOTE" "$BRANCH" git pull --ff-only "$REMOTE" "$BRANCH"
if [[ "${SKIP_PIP:-0}" != "1" ]]; then if [[ "${SKIP_PIP:-0}" != "1" ]]; then