import
This commit is contained in:
@@ -243,10 +243,16 @@ class IndustryAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ['name', 'slug', 'description']
|
search_fields = ['name', 'slug', 'description']
|
||||||
readonly_fields = ['created_at', 'updated_at']
|
readonly_fields = ['created_at', 'updated_at']
|
||||||
inlines = [IndustrySectorInline]
|
inlines = [IndustrySectorInline]
|
||||||
|
actions = ['delete_selected'] # Enable bulk delete
|
||||||
|
change_list_template = 'admin/igny8_core_auth/industry/change_list.html'
|
||||||
|
|
||||||
def get_sectors_count(self, obj):
|
def get_sectors_count(self, obj):
|
||||||
return obj.sectors.filter(is_active=True).count()
|
return obj.sectors.filter(is_active=True).count()
|
||||||
get_sectors_count.short_description = 'Active Sectors'
|
get_sectors_count.short_description = 'Active Sectors'
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
"""Allow deletion for superusers and developers"""
|
||||||
|
return request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer())
|
||||||
|
|
||||||
|
|
||||||
@admin.register(IndustrySector)
|
@admin.register(IndustrySector)
|
||||||
@@ -255,6 +261,12 @@ class IndustrySectorAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ['is_active', 'industry']
|
list_filter = ['is_active', 'industry']
|
||||||
search_fields = ['name', 'slug', 'description']
|
search_fields = ['name', 'slug', 'description']
|
||||||
readonly_fields = ['created_at', 'updated_at']
|
readonly_fields = ['created_at', 'updated_at']
|
||||||
|
actions = ['delete_selected'] # Enable bulk delete
|
||||||
|
change_list_template = 'admin/igny8_core_auth/industrysector/change_list.html'
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
"""Allow deletion for superusers and developers"""
|
||||||
|
return request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer())
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SeedKeyword)
|
@admin.register(SeedKeyword)
|
||||||
@@ -264,6 +276,8 @@ class SeedKeywordAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ['is_active', 'industry', 'sector', 'intent']
|
list_filter = ['is_active', 'industry', 'sector', 'intent']
|
||||||
search_fields = ['keyword']
|
search_fields = ['keyword']
|
||||||
readonly_fields = ['created_at', 'updated_at']
|
readonly_fields = ['created_at', 'updated_at']
|
||||||
|
actions = ['delete_selected'] # Enable bulk delete
|
||||||
|
change_list_template = 'admin/igny8_core_auth/seedkeyword/change_list.html'
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Keyword Info', {
|
('Keyword Info', {
|
||||||
@@ -276,6 +290,10 @@ class SeedKeywordAdmin(admin.ModelAdmin):
|
|||||||
'fields': ('created_at', 'updated_at')
|
'fields': ('created_at', 'updated_at')
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
"""Allow deletion for superusers and developers"""
|
||||||
|
return request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer())
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ class SeedKeyword(models.Model):
|
|||||||
db_table = 'igny8_seed_keywords'
|
db_table = 'igny8_seed_keywords'
|
||||||
unique_together = [['keyword', 'industry', 'sector']]
|
unique_together = [['keyword', 'industry', 'sector']]
|
||||||
verbose_name = 'Seed Keyword'
|
verbose_name = 'Seed Keyword'
|
||||||
verbose_name_plural = 'Seed Keywords'
|
verbose_name_plural = 'Global Keywords Database'
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['keyword']),
|
models.Index(fields=['keyword']),
|
||||||
models.Index(fields=['industry', 'sector']),
|
models.Index(fields=['industry', 'sector']),
|
||||||
|
|||||||
@@ -1316,3 +1316,219 @@ class AuthViewSet(viewsets.GenericViewSet):
|
|||||||
message='Password has been reset successfully',
|
message='Password has been reset successfully',
|
||||||
request=request
|
request=request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CSV Import/Export Views for Admin
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
from django.http import HttpResponse, JsonResponse
|
||||||
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def industry_csv_template(request):
|
||||||
|
"""Download CSV template for Industry import"""
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="industry_template.csv"'
|
||||||
|
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['name', 'description', 'is_active'])
|
||||||
|
writer.writerow(['Technology', 'Technology industry', 'true'])
|
||||||
|
writer.writerow(['Healthcare', 'Healthcare and medical services', 'true'])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def industry_csv_import(request):
|
||||||
|
"""Import industries from CSV"""
|
||||||
|
if not request.FILES.get('csv_file'):
|
||||||
|
return JsonResponse({'success': False, 'error': 'No CSV file provided'}, status=400)
|
||||||
|
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8')
|
||||||
|
io_string = io.StringIO(decoded_file)
|
||||||
|
reader = csv.DictReader(io_string)
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
updated = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
|
for row_num, row in enumerate(reader, start=2):
|
||||||
|
try:
|
||||||
|
is_active = row.get('is_active', 'true').lower() in ['true', '1', 'yes']
|
||||||
|
slug = slugify(row['name'])
|
||||||
|
|
||||||
|
industry, created_flag = Industry.objects.update_or_create(
|
||||||
|
name=row['name'],
|
||||||
|
defaults={
|
||||||
|
'slug': slug,
|
||||||
|
'description': row.get('description', ''),
|
||||||
|
'is_active': is_active
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created_flag:
|
||||||
|
created += 1
|
||||||
|
else:
|
||||||
|
updated += 1
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Row {row_num}: {str(e)}")
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'success': True,
|
||||||
|
'created': created,
|
||||||
|
'updated': updated,
|
||||||
|
'errors': errors
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def industrysector_csv_template(request):
|
||||||
|
"""Download CSV template for IndustrySector import"""
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="industrysector_template.csv"'
|
||||||
|
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['name', 'industry', 'description', 'is_active'])
|
||||||
|
writer.writerow(['Software Development', 'Technology', 'Software and app development', 'true'])
|
||||||
|
writer.writerow(['Healthcare IT', 'Healthcare', 'Healthcare information technology', 'true'])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def industrysector_csv_import(request):
|
||||||
|
"""Import industry sectors from CSV"""
|
||||||
|
if not request.FILES.get('csv_file'):
|
||||||
|
return JsonResponse({'success': False, 'error': 'No CSV file provided'}, status=400)
|
||||||
|
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8')
|
||||||
|
io_string = io.StringIO(decoded_file)
|
||||||
|
reader = csv.DictReader(io_string)
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
updated = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
|
for row_num, row in enumerate(reader, start=2):
|
||||||
|
try:
|
||||||
|
is_active = row.get('is_active', 'true').lower() in ['true', '1', 'yes']
|
||||||
|
slug = slugify(row['name'])
|
||||||
|
|
||||||
|
# Find industry by name
|
||||||
|
try:
|
||||||
|
industry = Industry.objects.get(name=row['industry'])
|
||||||
|
except Industry.DoesNotExist:
|
||||||
|
errors.append(f"Row {row_num}: Industry '{row['industry']}' not found")
|
||||||
|
continue
|
||||||
|
|
||||||
|
sector, created_flag = IndustrySector.objects.update_or_create(
|
||||||
|
name=row['name'],
|
||||||
|
industry=industry,
|
||||||
|
defaults={
|
||||||
|
'slug': slug,
|
||||||
|
'description': row.get('description', ''),
|
||||||
|
'is_active': is_active
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created_flag:
|
||||||
|
created += 1
|
||||||
|
else:
|
||||||
|
updated += 1
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Row {row_num}: {str(e)}")
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'success': True,
|
||||||
|
'created': created,
|
||||||
|
'updated': updated,
|
||||||
|
'errors': errors
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def seedkeyword_csv_template(request):
|
||||||
|
"""Download CSV template for SeedKeyword import"""
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="seedkeyword_template.csv"'
|
||||||
|
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['keyword', 'industry', 'sector', 'volume', 'difficulty', 'intent', 'is_active'])
|
||||||
|
writer.writerow(['python programming', 'Technology', 'Software Development', '10000', '45', 'Informational', 'true'])
|
||||||
|
writer.writerow(['medical software', 'Healthcare', 'Healthcare IT', '5000', '60', 'Commercial', 'true'])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def seedkeyword_csv_import(request):
|
||||||
|
"""Import seed keywords from CSV"""
|
||||||
|
if not request.FILES.get('csv_file'):
|
||||||
|
return JsonResponse({'success': False, 'error': 'No CSV file provided'}, status=400)
|
||||||
|
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8')
|
||||||
|
io_string = io.StringIO(decoded_file)
|
||||||
|
reader = csv.DictReader(io_string)
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
updated = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for row_num, row in enumerate(reader, start=2):
|
||||||
|
try:
|
||||||
|
is_active = row.get('is_active', 'true').lower() in ['true', '1', 'yes']
|
||||||
|
|
||||||
|
# Find industry and sector by name
|
||||||
|
try:
|
||||||
|
industry = Industry.objects.get(name=row['industry'])
|
||||||
|
except Industry.DoesNotExist:
|
||||||
|
errors.append(f"Row {row_num}: Industry '{row['industry']}' not found")
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
sector = IndustrySector.objects.get(name=row['sector'], industry=industry)
|
||||||
|
except IndustrySector.DoesNotExist:
|
||||||
|
errors.append(f"Row {row_num}: Sector '{row['sector']}' not found in industry '{row['industry']}'")
|
||||||
|
continue
|
||||||
|
|
||||||
|
keyword, created_flag = SeedKeyword.objects.update_or_create(
|
||||||
|
keyword=row['keyword'],
|
||||||
|
industry=industry,
|
||||||
|
sector=sector,
|
||||||
|
defaults={
|
||||||
|
'volume': int(row.get('volume', 0)),
|
||||||
|
'difficulty': int(row.get('difficulty', 0)),
|
||||||
|
'intent': row.get('intent', 'Informational'),
|
||||||
|
'is_active': is_active
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created_flag:
|
||||||
|
created += 1
|
||||||
|
else:
|
||||||
|
updated += 1
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Row {row_num}: {str(e)}")
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'success': True,
|
||||||
|
'created': created,
|
||||||
|
'updated': updated,
|
||||||
|
'errors': errors
|
||||||
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ ROOT_URLCONF = 'igny8_core.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [BASE_DIR / 'igny8_core' / 'templates'],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{% extends "admin/change_list.html" %}
|
||||||
|
{% load i18n admin_urls static %}
|
||||||
|
|
||||||
|
{% block object-tools-items %}
|
||||||
|
<li>
|
||||||
|
<a href="#" class="addlink" onclick="document.getElementById('csv-import-form').style.display='block'; return false;">
|
||||||
|
Import from CSV
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="csv-import-form" style="display:none; background: #f8f8f8; padding: 20px; margin: 20px 0; border: 1px solid #ddd;">
|
||||||
|
<h2>Import Industries from CSV</h2>
|
||||||
|
<form method="post" enctype="multipart/form-data" action="{% url 'admin_industry_csv_import' %}" id="csv-upload-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
<label for="csv_file">Select CSV file:</label>
|
||||||
|
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>CSV Format:</strong> name, description, is_active<br>
|
||||||
|
<em>Note: Slug will be auto-generated from name</em><br>
|
||||||
|
<a href="{% url 'admin_industry_csv_template' %}" download>Download template with examples</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="button">Upload and Import</button>
|
||||||
|
<button type="button" class="button" onclick="document.getElementById('csv-import-form').style.display='none'">Cancel</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<div id="import-results"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('csv-upload-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(this);
|
||||||
|
var resultsDiv = document.getElementById('import-results');
|
||||||
|
resultsDiv.innerHTML = '<p>Importing...</p>';
|
||||||
|
|
||||||
|
fetch('{% url "admin_industry_csv_import" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: green;">✓ Import successful!<br>' +
|
||||||
|
'Created: ' + data.created + '<br>' +
|
||||||
|
'Updated: ' + data.updated + '<br>' +
|
||||||
|
(data.errors.length > 0 ? 'Errors: ' + data.errors.join('<br>') : '') +
|
||||||
|
'</p>';
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + data.error + '</p>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + error + '</p>';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{% extends "admin/change_list.html" %}
|
||||||
|
{% load i18n admin_urls static %}
|
||||||
|
|
||||||
|
{% block object-tools-items %}
|
||||||
|
<li>
|
||||||
|
<a href="#" class="addlink" onclick="document.getElementById('csv-import-form').style.display='block'; return false;">
|
||||||
|
Import from CSV
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="csv-import-form" style="display:none; background: #f8f8f8; padding: 20px; margin: 20px 0; border: 1px solid #ddd;">
|
||||||
|
<h2>Import Industry Sectors from CSV</h2>
|
||||||
|
<form method="post" enctype="multipart/form-data" action="{% url 'admin_industrysector_csv_import' %}" id="csv-upload-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
<label for="csv_file">Select CSV file:</label>
|
||||||
|
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>CSV Format:</strong> name, industry, description, is_active<br>
|
||||||
|
<em>Note: Use industry name (not slug). Slug will be auto-generated from name</em><br>
|
||||||
|
<a href="{% url 'admin_industrysector_csv_template' %}" download>Download template with examples</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="button">Upload and Import</button>
|
||||||
|
<button type="button" class="button" onclick="document.getElementById('csv-import-form').style.display='none'">Cancel</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<div id="import-results"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('csv-upload-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(this);
|
||||||
|
var resultsDiv = document.getElementById('import-results');
|
||||||
|
resultsDiv.innerHTML = '<p>Importing...</p>';
|
||||||
|
|
||||||
|
fetch('{% url "admin_industrysector_csv_import" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: green;">✓ Import successful!<br>' +
|
||||||
|
'Created: ' + data.created + '<br>' +
|
||||||
|
'Updated: ' + data.updated + '<br>' +
|
||||||
|
(data.errors.length > 0 ? 'Errors: ' + data.errors.join('<br>') : '') +
|
||||||
|
'</p>';
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + data.error + '</p>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + error + '</p>';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{% extends "admin/change_list.html" %}
|
||||||
|
{% load i18n admin_urls static %}
|
||||||
|
|
||||||
|
{% block object-tools-items %}
|
||||||
|
<li>
|
||||||
|
<a href="#" class="addlink" onclick="document.getElementById('csv-import-form').style.display='block'; return false;">
|
||||||
|
Import from CSV
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="csv-import-form" style="display:none; background: #f8f8f8; padding: 20px; margin: 20px 0; border: 1px solid #ddd;">
|
||||||
|
<h2>Import Global Keywords from CSV</h2>
|
||||||
|
<form method="post" enctype="multipart/form-data" action="{% url 'admin_seedkeyword_csv_import' %}" id="csv-upload-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
<label for="csv_file">Select CSV file:</label>
|
||||||
|
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>CSV Format:</strong> keyword, industry, sector, volume, difficulty, intent, is_active<br>
|
||||||
|
<em>Note: Use industry and sector names (not slugs)</em><br>
|
||||||
|
<strong>Intent values:</strong> Informational, Commercial, Transactional, Navigational<br>
|
||||||
|
<a href="{% url 'admin_seedkeyword_csv_template' %}" download>Download template with examples</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="button">Upload and Import</button>
|
||||||
|
<button type="button" class="button" onclick="document.getElementById('csv-import-form').style.display='none'">Cancel</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<div id="import-results"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('csv-upload-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(this);
|
||||||
|
var resultsDiv = document.getElementById('import-results');
|
||||||
|
resultsDiv.innerHTML = '<p>Importing...</p>';
|
||||||
|
|
||||||
|
fetch('{% url "admin_seedkeyword_csv_import" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: green;">✓ Import successful!<br>' +
|
||||||
|
'Created: ' + data.created + '<br>' +
|
||||||
|
'Updated: ' + data.updated + '<br>' +
|
||||||
|
(data.errors.length > 0 ? 'Errors: ' + data.errors.join('<br>') : '') +
|
||||||
|
'</p>';
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + data.error + '</p>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + error + '</p>';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
@@ -21,8 +21,20 @@ from drf_spectacular.views import (
|
|||||||
SpectacularRedocView,
|
SpectacularRedocView,
|
||||||
SpectacularSwaggerView,
|
SpectacularSwaggerView,
|
||||||
)
|
)
|
||||||
|
from igny8_core.auth.views import (
|
||||||
|
industry_csv_template, industry_csv_import,
|
||||||
|
industrysector_csv_template, industrysector_csv_import,
|
||||||
|
seedkeyword_csv_template, seedkeyword_csv_import
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
# CSV Import/Export for admin - MUST come before admin/ to avoid being caught by admin.site.urls
|
||||||
|
path('admin/igny8_core_auth/industry/csv-template/', industry_csv_template, name='admin_industry_csv_template'),
|
||||||
|
path('admin/igny8_core_auth/industry/csv-import/', industry_csv_import, name='admin_industry_csv_import'),
|
||||||
|
path('admin/igny8_core_auth/industrysector/csv-template/', industrysector_csv_template, name='admin_industrysector_csv_template'),
|
||||||
|
path('admin/igny8_core_auth/industrysector/csv-import/', industrysector_csv_import, name='admin_industrysector_csv_import'),
|
||||||
|
path('admin/igny8_core_auth/seedkeyword/csv-template/', seedkeyword_csv_template, name='admin_seedkeyword_csv_template'),
|
||||||
|
path('admin/igny8_core_auth/seedkeyword/csv-import/', seedkeyword_csv_import, name='admin_seedkeyword_csv_import'),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('api/v1/auth/', include('igny8_core.auth.urls')), # Auth endpoints
|
path('api/v1/auth/', include('igny8_core.auth.urls')), # Auth endpoints
|
||||||
path('api/v1/planner/', include('igny8_core.modules.planner.urls')),
|
path('api/v1/planner/', include('igny8_core.modules.planner.urls')),
|
||||||
|
|||||||
Reference in New Issue
Block a user