Auto commit: 2026-03-13T11:47:52.842Z
This commit is contained in:
parent
027f4dd35b
commit
ba1bd529a8
Binary file not shown.
Binary file not shown.
@ -22,6 +22,15 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger me-2">Regenerate</button>
|
<button type="submit" class="btn btn-sm btn-outline-danger me-2">Regenerate</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<select id="layout-select" class="form-select form-select-sm d-inline-block w-auto me-2" onchange="changeLayout(this.value)">
|
||||||
|
<option value="organic">Organic Layout</option>
|
||||||
|
<option value="hierarchical">Hierarchical Layout</option>
|
||||||
|
</select>
|
||||||
|
<div class="btn-group btn-group-sm me-2" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="zoomIn()" title="Zoom In">+</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="zoomOut()" title="Zoom Out">-</button>
|
||||||
|
</div>
|
||||||
<button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>
|
<button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -127,6 +136,26 @@
|
|||||||
'General': '#f5f5f5' // Grey
|
'General': '#f5f5f5' // Grey
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const categoryEmojis = {
|
||||||
|
'Strategy': '🎯',
|
||||||
|
'Product': '📦',
|
||||||
|
'Market': '🌍',
|
||||||
|
'Operations': '⚙️',
|
||||||
|
'Finance': '💰',
|
||||||
|
'General': '💡'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getNodeEmoji(category) {
|
||||||
|
let cat = category || 'General';
|
||||||
|
for (let key in categoryEmojis) {
|
||||||
|
if (cat.toLowerCase().includes(key.toLowerCase())) {
|
||||||
|
return categoryEmojis[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '💡'; // default emoji
|
||||||
|
}
|
||||||
|
|
||||||
function getNodeColor(category) {
|
function getNodeColor(category) {
|
||||||
let cat = category || 'General';
|
let cat = category || 'General';
|
||||||
let color = '#e0e0e0'; // Default light grey
|
let color = '#e0e0e0'; // Default light grey
|
||||||
@ -148,7 +177,7 @@
|
|||||||
{% for node in nodes %}
|
{% for node in nodes %}
|
||||||
{
|
{
|
||||||
id: {{ node.pk }},
|
id: {{ node.pk }},
|
||||||
label: "{{ node.title|escapejs }}",
|
label: getNodeEmoji("{{ node.category|escapejs }}") + " " + "{{ node.title|escapejs }}",
|
||||||
title: "<strong>{{ node.title|escapejs }}</strong><br>{{ node.summary|escapejs }}<br><em>Category: {{ node.category|escapejs }}</em>",
|
title: "<strong>{{ node.title|escapejs }}</strong><br>{{ node.summary|escapejs }}<br><em>Category: {{ node.category|escapejs }}</em>",
|
||||||
summary: "{{ node.summary|escapejs }}",
|
summary: "{{ node.summary|escapejs }}",
|
||||||
category: "{{ node.category|escapejs }}",
|
category: "{{ node.category|escapejs }}",
|
||||||
@ -234,10 +263,22 @@
|
|||||||
// Node clicked
|
// Node clicked
|
||||||
const nodeId = params.nodes[0];
|
const nodeId = params.nodes[0];
|
||||||
const node = data.nodes.get(nodeId);
|
const node = data.nodes.get(nodeId);
|
||||||
|
// remove emoji from label for editing
|
||||||
|
const cleanLabel = node.label.replace(/^.*? /, '');
|
||||||
detailsPanel.innerHTML = `
|
detailsPanel.innerHTML = `
|
||||||
<h6 class="mb-2 fw-bold text-dark">${node.label}</h6>
|
<div id="node-view-${nodeId}">
|
||||||
|
<h6 class="mb-2 fw-bold text-dark">${cleanLabel}</h6>
|
||||||
<span class="badge bg-secondary mb-2 bg-opacity-75">${node.category}</span>
|
<span class="badge bg-secondary mb-2 bg-opacity-75">${node.category}</span>
|
||||||
<p class="small mb-0 text-dark"><strong>Summary:</strong><br>${node.summary}</p>
|
<p class="small mb-2 text-dark"><strong>Summary:</strong><br>${node.summary}</p>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="editNodeUI(${nodeId})">Edit</button>
|
||||||
|
</div>
|
||||||
|
<div id="node-edit-${nodeId}" class="d-none">
|
||||||
|
<input type="text" id="edit-node-title-${nodeId}" class="form-control form-control-sm mb-2" value="${cleanLabel}">
|
||||||
|
<input type="text" id="edit-node-category-${nodeId}" class="form-control form-control-sm mb-2" value="${node.category}">
|
||||||
|
<textarea id="edit-node-summary-${nodeId}" class="form-control form-control-sm mb-2" rows="3">${node.summary}</textarea>
|
||||||
|
<button class="btn btn-sm btn-primary me-1" onclick="saveNode(${nodeId}, event)">Save</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="cancelEditNode(${nodeId})">Cancel</button>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
} else if (params.edges.length > 0) {
|
} else if (params.edges.length > 0) {
|
||||||
// Edge clicked
|
// Edge clicked
|
||||||
@ -260,6 +301,75 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function editNodeUI(nodeId) {
|
||||||
|
document.getElementById(`node-view-${nodeId}`).classList.add('d-none');
|
||||||
|
document.getElementById(`node-edit-${nodeId}`).classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEditNode(nodeId) {
|
||||||
|
document.getElementById(`node-edit-${nodeId}`).classList.add('d-none');
|
||||||
|
document.getElementById(`node-view-${nodeId}`).classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveNode(nodeId, event) {
|
||||||
|
const title = document.getElementById(`edit-node-title-${nodeId}`).value;
|
||||||
|
const category = document.getElementById(`edit-node-category-${nodeId}`).value;
|
||||||
|
const summary = document.getElementById(`edit-node-summary-${nodeId}`).value;
|
||||||
|
|
||||||
|
const saveBtn = event.target;
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
saveBtn.innerText = 'Saving...';
|
||||||
|
|
||||||
|
let csrfToken = '';
|
||||||
|
const csrfInput = document.querySelector('[name=csrfmiddlewaretoken]');
|
||||||
|
if (csrfInput) {
|
||||||
|
csrfToken = csrfInput.value;
|
||||||
|
} else {
|
||||||
|
// fallback, get from form if present elsewhere
|
||||||
|
console.warn("CSRF token not found via input name. Ensure it exists on the page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = "{% url 'edit_node' project.pk 0 %}".replace('/0/', '/' + nodeId + '/');
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ title: title, category: category, summary: summary })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(res => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
const node = res.node;
|
||||||
|
const emoji = getNodeEmoji(node.category);
|
||||||
|
const color = getNodeColor(node.category);
|
||||||
|
data.nodes.update({
|
||||||
|
id: node.id,
|
||||||
|
label: emoji + " " + node.title,
|
||||||
|
title: `<strong>${node.title}</strong><br>${node.summary}<br><em>Category: ${node.category}</em>`,
|
||||||
|
summary: node.summary,
|
||||||
|
category: node.category,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
|
||||||
|
// re-render details
|
||||||
|
network.emit('click', { nodes: [nodeId], edges: [] });
|
||||||
|
} else {
|
||||||
|
alert('Error saving node: ' + res.message);
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerText = 'Save';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Error saving node');
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerText = 'Save';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// AI Chat Handler
|
// AI Chat Handler
|
||||||
document.getElementById('ai-submit').addEventListener('click', function() {
|
document.getElementById('ai-submit').addEventListener('click', function() {
|
||||||
const inputField = document.getElementById('ai-input');
|
const inputField = document.getElementById('ai-input');
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap
|
from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap, edit_node
|
||||||
from .ai_views import ai_chat
|
from .ai_views import ai_chat
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -9,5 +9,7 @@ urlpatterns = [
|
|||||||
path("projects/<int:pk>/", project_detail, name="project_detail"),
|
path("projects/<int:pk>/", project_detail, name="project_detail"),
|
||||||
path("projects/<int:pk>/regenerate/", regenerate_mindmap, name="regenerate_mindmap"),
|
path("projects/<int:pk>/regenerate/", regenerate_mindmap, name="regenerate_mindmap"),
|
||||||
path("projects/<int:pk>/ai/", ai_chat, name="ai_chat"),
|
path("projects/<int:pk>/ai/", ai_chat, name="ai_chat"),
|
||||||
|
path('projects/<int:pk>/node/<int:node_id>/edit/', edit_node, name='edit_node'),
|
||||||
|
|
||||||
path("signup/", signup, name="signup"),
|
path("signup/", signup, name="signup"),
|
||||||
]
|
]
|
||||||
@ -55,3 +55,34 @@ def regenerate_mindmap(request, pk):
|
|||||||
project.connections.all().delete()
|
project.connections.all().delete()
|
||||||
generate_initial_mindmap(project)
|
generate_initial_mindmap(project)
|
||||||
return redirect('project_detail', pk=project.pk)
|
return redirect('project_detail', pk=project.pk)
|
||||||
|
import json
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def edit_node(request, pk, node_id):
|
||||||
|
project = get_object_or_404(Project, pk=pk, user=request.user)
|
||||||
|
try:
|
||||||
|
node = project.nodes.get(pk=node_id)
|
||||||
|
data = json.loads(request.body)
|
||||||
|
title = data.get('title', '').strip()
|
||||||
|
summary = data.get('summary', '').strip()
|
||||||
|
category = data.get('category', '').strip()
|
||||||
|
|
||||||
|
if title:
|
||||||
|
node.title = title
|
||||||
|
if summary:
|
||||||
|
node.summary = summary
|
||||||
|
if category:
|
||||||
|
node.category = category
|
||||||
|
|
||||||
|
node.save()
|
||||||
|
return JsonResponse({'status': 'success', 'node': {
|
||||||
|
'id': node.pk,
|
||||||
|
'title': node.title,
|
||||||
|
'summary': node.summary,
|
||||||
|
'category': node.category
|
||||||
|
}})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
|
||||||
|
|||||||
106
update_html.py
Normal file
106
update_html.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
path = "core/templates/core/project_detail.html"
|
||||||
|
with open(path, "r") as f:
|
||||||
|
html = f.read()
|
||||||
|
|
||||||
|
# 1. Add top bar buttons (Layout, Zoom, etc)
|
||||||
|
buttons_to_add = """
|
||||||
|
<select id="layout-select" class="form-select form-select-sm d-inline-block w-auto me-2" onchange="changeLayout(this.value)">
|
||||||
|
<option value="organic">Organic Layout</option>
|
||||||
|
<option value="hierarchical">Hierarchical Layout</option>
|
||||||
|
</select>
|
||||||
|
<div class="btn-group btn-group-sm me-2" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="zoomIn()" title="Zoom In">+</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="zoomOut()" title="Zoom Out">-</button>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
html = html.replace('<button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>',
|
||||||
|
buttons_to_add + '\n <button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>')
|
||||||
|
|
||||||
|
# 2. Add emoji logic
|
||||||
|
emoji_script = """
|
||||||
|
const categoryEmojis = {
|
||||||
|
'Strategy': '🎯',
|
||||||
|
'Product': '📦',
|
||||||
|
'Market': '🌍',
|
||||||
|
'Operations': '⚙️',
|
||||||
|
'Finance': '💰',
|
||||||
|
'General': '💡'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getNodeEmoji(category) {
|
||||||
|
let cat = category || 'General';
|
||||||
|
for (let key in categoryEmojis) {
|
||||||
|
if (cat.toLowerCase().includes(key.toLowerCase())) {
|
||||||
|
return categoryEmojis[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '💡'; // default emoji
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
html = html.replace('function getNodeColor(category) {', emoji_script + '\n function getNodeColor(category) {')
|
||||||
|
|
||||||
|
# update node array to include emoji
|
||||||
|
html = html.replace('label: "{{ node.title|escapejs }}",', 'label: getNodeEmoji("{{ node.category|escapejs }}") + " " + "{{ node.title|escapejs }}",')
|
||||||
|
|
||||||
|
# 3. Add script functions for zoom and layout
|
||||||
|
scripts_to_add = """
|
||||||
|
function zoomIn() {
|
||||||
|
if (network) {
|
||||||
|
const scale = network.getScale() * 1.5;
|
||||||
|
network.moveTo({ scale: scale, animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomOut() {
|
||||||
|
if (network) {
|
||||||
|
const scale = network.getScale() / 1.5;
|
||||||
|
network.moveTo({ scale: scale, animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeLayout(layoutType) {
|
||||||
|
if (layoutType === 'hierarchical') {
|
||||||
|
network.setOptions({
|
||||||
|
layout: {
|
||||||
|
hierarchical: {
|
||||||
|
enabled: true,
|
||||||
|
direction: 'UD',
|
||||||
|
sortMethod: 'directed',
|
||||||
|
nodeSpacing: 200,
|
||||||
|
levelSeparation: 150
|
||||||
|
}
|
||||||
|
},
|
||||||
|
physics: {
|
||||||
|
hierarchicalRepulsion: {
|
||||||
|
nodeDistance: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
network.setOptions({
|
||||||
|
layout: { hierarchical: { enabled: false } },
|
||||||
|
physics: {
|
||||||
|
solver: 'forceAtlas2Based',
|
||||||
|
forceAtlas2Based: {
|
||||||
|
gravitationalConstant: -100,
|
||||||
|
centralGravity: 0.01,
|
||||||
|
springLength: 150,
|
||||||
|
springConstant: 0.08
|
||||||
|
},
|
||||||
|
maxVelocity: 50,
|
||||||
|
minVelocity: 0.1,
|
||||||
|
timestep: 0.5,
|
||||||
|
stabilization: { iterations: 150 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
hTml = html.replace('// Export Map function', scripts_to_add + '\n // Export Map function')
|
||||||
|
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write(html)
|
||||||
|
print("Updated HTML!")
|
||||||
114
update_js.py
Normal file
114
update_js.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
with open("core/templates/core/project_detail.html", "r") as f:
|
||||||
|
html = f.read()
|
||||||
|
|
||||||
|
# Replace the node clicked block
|
||||||
|
old_block = """
|
||||||
|
const nodeId = params.nodes[0];
|
||||||
|
const node = data.nodes.get(nodeId);
|
||||||
|
detailsPanel.innerHTML = `
|
||||||
|
<h6 class="mb-2 fw-bold text-dark">${node.label}</h6>
|
||||||
|
<span class="badge bg-secondary mb-2 bg-opacity-75">${node.category}</span>
|
||||||
|
<p class="small mb-0 text-dark"><strong>Summary:</strong><br>${node.summary}</p>
|
||||||
|
`;
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_block = """
|
||||||
|
const nodeId = params.nodes[0];
|
||||||
|
const node = data.nodes.get(nodeId);
|
||||||
|
// remove emoji from label for editing
|
||||||
|
const cleanLabel = node.label.replace(/^.*? /, '');
|
||||||
|
detailsPanel.innerHTML = `
|
||||||
|
<div id="node-view-${nodeId}">
|
||||||
|
<h6 class="mb-2 fw-bold text-dark">${cleanLabel}</h6>
|
||||||
|
<span class="badge bg-secondary mb-2 bg-opacity-75">${node.category}</span>
|
||||||
|
<p class="small mb-2 text-dark"><strong>Summary:</strong><br>${node.summary}</p>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="editNodeUI(${nodeId})">Edit</button>
|
||||||
|
</div>
|
||||||
|
<div id="node-edit-${nodeId}" class="d-none">
|
||||||
|
<input type="text" id="edit-node-title-${nodeId}" class="form-control form-control-sm mb-2" value="${cleanLabel}">
|
||||||
|
<input type="text" id="edit-node-category-${nodeId}" class="form-control form-control-sm mb-2" value="${node.category}">
|
||||||
|
<textarea id="edit-node-summary-${nodeId}" class="form-control form-control-sm mb-2" rows="3">${node.summary}</textarea>
|
||||||
|
<button class="btn btn-sm btn-primary me-1" onclick="saveNode(${nodeId}, event)">Save</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="cancelEditNode(${nodeId})">Cancel</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
"""
|
||||||
|
|
||||||
|
html = html.replace(old_block, new_block)
|
||||||
|
|
||||||
|
new_scripts = """
|
||||||
|
function editNodeUI(nodeId) {
|
||||||
|
document.getElementById(`node-view-${nodeId}`).classList.add('d-none');
|
||||||
|
document.getElementById(`node-edit-${nodeId}`).classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEditNode(nodeId) {
|
||||||
|
document.getElementById(`node-edit-${nodeId}`).classList.add('d-none');
|
||||||
|
document.getElementById(`node-view-${nodeId}`).classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveNode(nodeId, event) {
|
||||||
|
const title = document.getElementById(`edit-node-title-${nodeId}`).value;
|
||||||
|
const category = document.getElementById(`edit-node-category-${nodeId}`).value;
|
||||||
|
const summary = document.getElementById(`edit-node-summary-${nodeId}`).value;
|
||||||
|
|
||||||
|
const saveBtn = event.target;
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
saveBtn.innerText = 'Saving...';
|
||||||
|
|
||||||
|
let csrfToken = '';
|
||||||
|
const csrfInput = document.querySelector('[name=csrfmiddlewaretoken]');
|
||||||
|
if (csrfInput) {
|
||||||
|
csrfToken = csrfInput.value;
|
||||||
|
} else {
|
||||||
|
// fallback, get from form if present elsewhere
|
||||||
|
console.warn("CSRF token not found via input name. Ensure it exists on the page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = "{% url 'edit_node' project.pk 0 %}".replace('/0/', '/' + nodeId + '/');
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ title: title, category: category, summary: summary })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(res => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
const node = res.node;
|
||||||
|
const emoji = getNodeEmoji(node.category);
|
||||||
|
const color = getNodeColor(node.category);
|
||||||
|
data.nodes.update({
|
||||||
|
id: node.id,
|
||||||
|
label: emoji + " " + node.title,
|
||||||
|
title: `<strong>${node.title}</strong><br>${node.summary}<br><em>Category: ${node.category}</em>`,
|
||||||
|
summary: node.summary,
|
||||||
|
category: node.category,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
|
||||||
|
// re-render details
|
||||||
|
network.emit('click', { nodes: [nodeId], edges: [] });
|
||||||
|
} else {
|
||||||
|
alert('Error saving node: ' + res.message);
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerText = 'Save';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Error saving node');
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerText = 'Save';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
html = html.replace(' // AI Chat Handler', new_scripts + '\n // AI Chat Handler')
|
||||||
|
|
||||||
|
with open("core/templates/core/project_detail.html", "w") as f:
|
||||||
|
f.write(html)
|
||||||
19
update_urls.py
Normal file
19
update_urls.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
with open("core/urls.py", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
'from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap',
|
||||||
|
'from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap, edit_node'
|
||||||
|
)
|
||||||
|
|
||||||
|
new_path = " path('projects/<int:pk>/node/<int:node_id>/edit/', edit_node, name='edit_node'),\n"
|
||||||
|
content = content.replace(
|
||||||
|
' path("projects/<int:pk>/ai/", ai_chat, name="ai_chat"),',
|
||||||
|
' path("projects/<int:pk>/ai/", ai_chat, name="ai_chat"),\n' + new_path
|
||||||
|
)
|
||||||
|
|
||||||
|
with open("core/urls.py", "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
37
update_views.py
Normal file
37
update_views.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
with open("core/views.py", "a") as f:
|
||||||
|
f.write("""
|
||||||
|
import json
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def edit_node(request, pk, node_id):
|
||||||
|
project = get_object_or_404(Project, pk=pk, user=request.user)
|
||||||
|
try:
|
||||||
|
node = project.nodes.get(pk=node_id)
|
||||||
|
data = json.loads(request.body)
|
||||||
|
title = data.get('title', '').strip()
|
||||||
|
summary = data.get('summary', '').strip()
|
||||||
|
category = data.get('category', '').strip()
|
||||||
|
|
||||||
|
if title:
|
||||||
|
node.title = title
|
||||||
|
if summary:
|
||||||
|
node.summary = summary
|
||||||
|
if category:
|
||||||
|
node.category = category
|
||||||
|
|
||||||
|
node.save()
|
||||||
|
return JsonResponse({'status': 'success', 'node': {
|
||||||
|
'id': node.pk,
|
||||||
|
'title': node.title,
|
||||||
|
'summary': node.summary,
|
||||||
|
'category': node.category
|
||||||
|
}})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
|
||||||
|
""")
|
||||||
|
print("Updated views.py!")
|
||||||
Loading…
x
Reference in New Issue
Block a user