FIXME created for quick tagging
All checks were successful
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 3s
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 4s
All checks were successful
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 3s
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 4s
This commit is contained in:
@@ -27,7 +27,7 @@ spec:
|
|||||||
mountPath: /data
|
mountPath: /data
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: git.baumann.gr/adebaumann/labhelper:0.055
|
image: git.baumann.gr/adebaumann/labhelper:0.056
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
|
|||||||
145
boxes/templates/boxes/fixme.html
Normal file
145
boxes/templates/boxes/fixme.html
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Fixme - LabHelper{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><i class="fas fa-exclamation-triangle"></i> Fixme</h1>
|
||||||
|
<p class="breadcrumb">Find and fix things missing tags for specific facets</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<h2><i class="fas fa-tags"></i> Select a Facet</h2>
|
||||||
|
|
||||||
|
{% if facets %}
|
||||||
|
<form method="get" action="{% url 'fixme' %}" class="facet-selector">
|
||||||
|
<select name="facet_id" id="facet-select" onchange="this.form.submit()" class="form-control">
|
||||||
|
<option value="">-- Choose a facet --</option>
|
||||||
|
{% for facet in facets %}
|
||||||
|
<option value="{{ facet.id }}" {% if selected_facet and selected_facet.id == facet.id %}selected{% endif %}>
|
||||||
|
{{ facet.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<noscript>
|
||||||
|
<button type="submit" class="btn btn-sm">
|
||||||
|
<i class="fas fa-search"></i> Show Missing Things
|
||||||
|
</button>
|
||||||
|
</noscript>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: #888;">No facets found. Please create some facets first.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if selected_facet %}
|
||||||
|
<div class="section">
|
||||||
|
<h2><i class="fas fa-exclamation-circle"></i> Things Missing "{{ selected_facet.name }}" Tags</h2>
|
||||||
|
|
||||||
|
{% if missing_things %}
|
||||||
|
<form method="post" action="{% url 'fixme' %}" id="fixme-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="facet_id" value="{{ selected_facet.id }}">
|
||||||
|
|
||||||
|
<div class="tags-selection" style="margin-bottom: 20px;">
|
||||||
|
<h3><i class="fas fa-plus-circle"></i> Add Tags:</h3>
|
||||||
|
{% if selected_facet.tags.all %}
|
||||||
|
{% for tag in selected_facet.tags.all %}
|
||||||
|
<label style="display: inline-block; margin-right: 15px; margin-bottom: 10px;">
|
||||||
|
<input type="checkbox" name="tag_ids" value="{{ tag.id }}"
|
||||||
|
{% if selected_facet.cardinality == 'single' %}onclick="uncheckOtherTags(this)"{% endif %}>
|
||||||
|
<span style="background: {{ tag.facet.color }}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px;">
|
||||||
|
{{ tag.name }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
{% if selected_facet.cardinality == 'single' %}
|
||||||
|
<p style="color: #888; font-size: 12px; margin-top: 10px;">
|
||||||
|
<i class="fas fa-info-circle"></i> This facet allows only one tag per thing.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p style="color: #888;">No tags available for this facet. Please create some tags first.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="things-list" style="margin-bottom: 20px;">
|
||||||
|
<h3><i class="fas fa-box"></i> Things to Update:</h3>
|
||||||
|
{% for thing in missing_things %}
|
||||||
|
<div style="background: #f8f9fa; padding: 10px; margin: 5px 0; border-radius: 5px; border-left: 4px solid #667eea;">
|
||||||
|
<label style="display: block; cursor: pointer; width: 100%;">
|
||||||
|
<input type="checkbox" name="thing_ids" value="{{ thing.id }}" style="margin-right: 10px;">
|
||||||
|
<strong>{{ thing.name }}</strong>
|
||||||
|
<span style="color: #888; margin-left: 10px;">(Box: {{ thing.box.id }})</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button type="submit" class="btn">
|
||||||
|
<i class="fas fa-save"></i> Add Selected Tags to Selected Things
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'fixme' %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-times"></i> Clear Selection
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: #28a745;">
|
||||||
|
<i class="fas fa-check-circle"></i> All things have tags for "{{ selected_facet.name }}" facet!
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'fixme' %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left"></i> Back to Facet Selection
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
function uncheckOtherTags(checkbox) {
|
||||||
|
// For single cardinality facets, uncheck all other checkboxes
|
||||||
|
var checkboxes = document.querySelectorAll('input[name="tag_ids"]');
|
||||||
|
checkboxes.forEach(function(cb) {
|
||||||
|
if (cb !== checkbox) {
|
||||||
|
cb.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add select all/none functionality for things
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var fixmeForm = document.getElementById('fixme-form');
|
||||||
|
if (fixmeForm) {
|
||||||
|
var thingsList = fixmeForm.querySelector('.things-list');
|
||||||
|
if (thingsList) {
|
||||||
|
var header = document.createElement('div');
|
||||||
|
header.style.marginBottom = '10px';
|
||||||
|
header.innerHTML = `
|
||||||
|
<small>
|
||||||
|
<a href="#" onclick="selectAllThings(); return false;">Select All</a> |
|
||||||
|
<a href="#" onclick="deselectAllThings(); return false;">Deselect All</a>
|
||||||
|
</small>
|
||||||
|
`;
|
||||||
|
thingsList.insertBefore(header, thingsList.firstChild.nextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectAllThings() {
|
||||||
|
var checkboxes = document.querySelectorAll('input[name="thing_ids"]');
|
||||||
|
checkboxes.forEach(function(cb) { cb.checked = true; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectAllThings() {
|
||||||
|
var checkboxes = document.querySelectorAll('input[name="thing_ids"]');
|
||||||
|
checkboxes.forEach(function(cb) { cb.checked = false; });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
@@ -2,7 +2,7 @@ import bleach
|
|||||||
import markdown
|
import markdown
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Q
|
from django.db.models import Q, Prefetch
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
|
||||||
@@ -402,3 +402,49 @@ def resources_list(request):
|
|||||||
return render(request, 'boxes/resources_list.html', {
|
return render(request, 'boxes/resources_list.html', {
|
||||||
'resources': resources,
|
'resources': resources,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def fixme(request):
|
||||||
|
"""Page to find and fix things missing tags for specific facets."""
|
||||||
|
facets = Facet.objects.all().prefetch_related('tags')
|
||||||
|
|
||||||
|
selected_facet = None
|
||||||
|
missing_things = []
|
||||||
|
|
||||||
|
if request.method == 'GET' and 'facet_id' in request.GET:
|
||||||
|
try:
|
||||||
|
selected_facet = Facet.objects.get(pk=request.GET['facet_id'])
|
||||||
|
# Find things that don't have any tag from this facet
|
||||||
|
missing_things = Thing.objects.exclude(
|
||||||
|
tags__facet=selected_facet
|
||||||
|
).select_related('box', 'box__box_type').prefetch_related('tags')
|
||||||
|
except Facet.DoesNotExist:
|
||||||
|
selected_facet = None
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
facet_id = request.POST.get('facet_id')
|
||||||
|
tag_ids = request.POST.getlist('tag_ids')
|
||||||
|
thing_ids = request.POST.getlist('thing_ids')
|
||||||
|
|
||||||
|
if facet_id and tag_ids and thing_ids:
|
||||||
|
facet = get_object_or_404(Facet, pk=facet_id)
|
||||||
|
tags = Tag.objects.filter(id__in=tag_ids, facet=facet)
|
||||||
|
things = Thing.objects.filter(id__in=thing_ids)
|
||||||
|
|
||||||
|
for thing in things:
|
||||||
|
if facet.cardinality == Facet.Cardinality.SINGLE:
|
||||||
|
# Remove existing tags from this facet
|
||||||
|
thing.tags.remove(*thing.tags.filter(facet=facet))
|
||||||
|
# Add new tags
|
||||||
|
for tag in tags:
|
||||||
|
if tag.facet == facet:
|
||||||
|
thing.tags.add(tag)
|
||||||
|
|
||||||
|
return redirect('fixme')
|
||||||
|
|
||||||
|
return render(request, 'boxes/fixme.html', {
|
||||||
|
'facets': facets,
|
||||||
|
'selected_facet': selected_facet,
|
||||||
|
'missing_things': missing_things,
|
||||||
|
})
|
||||||
|
|||||||
@@ -411,6 +411,7 @@
|
|||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<a href="/box-management/"><i class="fas fa-boxes"></i> Box Management</a>
|
<a href="/box-management/"><i class="fas fa-boxes"></i> Box Management</a>
|
||||||
<a href="/resources/"><i class="fas fa-folder-open"></i> Resources</a>
|
<a href="/resources/"><i class="fas fa-folder-open"></i> Resources</a>
|
||||||
|
<a href="/fixme/"><i class="fas fa-exclamation-triangle"></i> Fixme</a>
|
||||||
<a href="/admin/"><i class="fas fa-cog"></i> Admin</a>
|
<a href="/admin/"><i class="fas fa-cog"></i> Admin</a>
|
||||||
<form method="post" action="{% url 'logout' %}" style="padding: 0; margin: 0; background: none; border: none;">
|
<form method="post" action="{% url 'logout' %}" style="padding: 0; margin: 0; background: none; border: none;">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from boxes.views import (
|
|||||||
edit_box,
|
edit_box,
|
||||||
edit_box_type,
|
edit_box_type,
|
||||||
edit_thing,
|
edit_thing,
|
||||||
|
fixme,
|
||||||
index,
|
index,
|
||||||
resources_list,
|
resources_list,
|
||||||
search,
|
search,
|
||||||
@@ -56,6 +57,7 @@ urlpatterns = [
|
|||||||
path('search/', search, name='search'),
|
path('search/', search, name='search'),
|
||||||
path('search/api/', search_api, name='search_api'),
|
path('search/api/', search_api, name='search_api'),
|
||||||
path('resources/', resources_list, name='resources_list'),
|
path('resources/', resources_list, name='resources_list'),
|
||||||
|
path('fixme/', fixme, name='fixme'),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user