update tasks
This commit is contained in:
parent
44f540bae5
commit
e10b5f64ea
Binary file not shown.
17
inventory/management/commands/test.py
Normal file
17
inventory/management/commands/test.py
Normal file
@ -0,0 +1,17 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.mail import send_mail
|
||||
from allauth.account.models import EmailConfirmation
|
||||
from inventory.tasks import send_email
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
user = User.objects.last()
|
||||
print(user.email)
|
||||
# 2. Force email confirmation
|
||||
# email = user.emailaddress_set.first()
|
||||
confirmation = EmailConfirmation.create(user.email)
|
||||
confirmation.send()
|
||||
|
||||
12
inventory/management/commands/test_task_process_running.py
Normal file
12
inventory/management/commands/test_task_process_running.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
# from background_task.models import Task
|
||||
# from background_task import background
|
||||
# from django_q.tasks import async_task
|
||||
from inventory.tasks import send_email
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
send_email('ismail.mosa@gmail.com', 'teset1@gmail.com', 'test', 'test')
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
# Generated by Django 5.1.7 on 2025-03-27 20:55
|
||||
# Generated by Django 5.1.7 on 2025-05-04 10:42
|
||||
|
||||
import datetime
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import inventory.mixins
|
||||
import inventory.models
|
||||
import phonenumber_field.modelfields
|
||||
@ -442,7 +444,7 @@ class Migration(migrations.Migration):
|
||||
name='Email',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('object_id', models.UUIDField()),
|
||||
('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')),
|
||||
('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')),
|
||||
('subject', models.TextField(blank=True, null=True, verbose_name='Subject')),
|
||||
@ -491,7 +493,7 @@ class Migration(migrations.Migration):
|
||||
name='Notes',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('object_id', models.UUIDField()),
|
||||
('note', models.TextField(verbose_name='Note')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
@ -698,7 +700,7 @@ class Migration(migrations.Migration):
|
||||
('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')),
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||
('address', models.CharField(max_length=200, verbose_name='Address')),
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
|
||||
@ -751,4 +753,33 @@ class Migration(migrations.Migration):
|
||||
'unique_together': {('car', 'exterior', 'interior')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_data', models.JSONField(blank=True, null=True)),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0.01)])),
|
||||
('currency', models.CharField(default='SAR', max_length=3)),
|
||||
('payment_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('status', models.CharField(choices=[('initiated', 'initiated'), ('pending', 'Pending'), ('completed', 'Completed'), ('paid', 'Paid'), ('failed', 'Failed'), ('refunded', 'Refunded'), ('cancelled', 'Cancelled')], default='pending', max_length=10)),
|
||||
('payment_method', models.CharField(choices=[('credit_card', 'Credit Card'), ('debit_card', 'Debit Card'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer'), ('crypto', 'Cryptocurrency'), ('other', 'Other')], max_length=20)),
|
||||
('transaction_id', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('invoice_number', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('order_reference', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('gateway_response', models.JSONField(blank=True, null=True)),
|
||||
('gateway_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_recurring', models.BooleanField(default=False)),
|
||||
('billing_email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||
('billing_address', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Payment Histories',
|
||||
'ordering': ['-payment_date'],
|
||||
'indexes': [models.Index(fields=['transaction_id'], name='inventory_p_transac_9469f3_idx'), models.Index(fields=['user'], name='inventory_p_user_id_c31626_idx'), models.Index(fields=['status'], name='inventory_p_status_abcb77_idx'), models.Index(fields=['payment_date'], name='inventory_p_payment_b3068c_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-24 16:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='notes',
|
||||
name='object_id',
|
||||
field=models.PositiveBigIntegerField(),
|
||||
),
|
||||
]
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-24 16:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0002_alter_notes_object_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='email',
|
||||
name='object_id',
|
||||
field=models.PositiveBigIntegerField(),
|
||||
),
|
||||
]
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-24 16:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0003_alter_email_object_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='email',
|
||||
name='object_id',
|
||||
field=models.UUIDField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notes',
|
||||
name='object_id',
|
||||
field=models.UUIDField(),
|
||||
),
|
||||
]
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-24 16:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0004_alter_email_object_id_alter_notes_object_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='email',
|
||||
name='object_id',
|
||||
field=models.PositiveIntegerField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notes',
|
||||
name='object_id',
|
||||
field=models.PositiveIntegerField(),
|
||||
),
|
||||
]
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-24 17:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0005_alter_email_object_id_alter_notes_object_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='email',
|
||||
name='object_id',
|
||||
field=models.UUIDField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notes',
|
||||
name='object_id',
|
||||
field=models.UUIDField(),
|
||||
),
|
||||
]
|
||||
@ -1,47 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-29 13:33
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0006_alter_email_object_id_alter_notes_object_id'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PaymentHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_data', models.JSONField(blank=True, null=True)),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0.01)])),
|
||||
('currency', models.CharField(default='SAR', max_length=3)),
|
||||
('payment_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('status', models.CharField(choices=[('initiated', 'initiated'), ('pending', 'Pending'), ('completed', 'Completed'), ('paid', 'Paid'), ('failed', 'Failed'), ('refunded', 'Refunded'), ('cancelled', 'Cancelled')], default='pending', max_length=10)),
|
||||
('payment_method', models.CharField(choices=[('credit_card', 'Credit Card'), ('debit_card', 'Debit Card'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer'), ('crypto', 'Cryptocurrency'), ('other', 'Other')], max_length=20)),
|
||||
('transaction_id', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('invoice_number', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('order_reference', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('gateway_response', models.JSONField(blank=True, null=True)),
|
||||
('gateway_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_recurring', models.BooleanField(default=False)),
|
||||
('billing_email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||
('billing_address', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Payment Histories',
|
||||
'ordering': ['-payment_date'],
|
||||
'indexes': [models.Index(fields=['transaction_id'], name='inventory_p_transac_9469f3_idx'), models.Index(fields=['user'], name='inventory_p_user_id_c31626_idx'), models.Index(fields=['status'], name='inventory_p_status_abcb77_idx'), models.Index(fields=['payment_date'], name='inventory_p_payment_b3068c_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-01 13:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0007_paymenthistory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vendor',
|
||||
name='address',
|
||||
field=models.CharField(default='', max_length=200, verbose_name='Address'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@ -145,7 +145,8 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
||||
|
||||
# Create COA accounts, background task
|
||||
create_coa_accounts(instance.pk)
|
||||
create_settings(instance.pk)
|
||||
|
||||
# create_settings(instance.pk)
|
||||
# create_accounts_for_make(instance.pk)
|
||||
|
||||
@receiver(post_save, sender=models.Dealer)
|
||||
|
||||
1080
inventory/tasks.py
1080
inventory/tasks.py
File diff suppressed because it is too large
Load Diff
@ -43,12 +43,16 @@ urlpatterns = [
|
||||
# template_name="account/request_login_code.html"
|
||||
# ),
|
||||
# ),
|
||||
# Tasks
|
||||
|
||||
path('tasks/', views.task_list, name='task_list'),
|
||||
# path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
||||
# Dashboards
|
||||
# path("user/<int:pk>/settings/", views.UserSettingsView.as_view(), name="user_settings"),
|
||||
path("pricing/", views.pricing_page, name="pricing_page"),
|
||||
path("submit_plan/", views.submit_plan, name="submit_plan"),
|
||||
path('payment-callback/', views.payment_callback, name='payment_callback'),
|
||||
#
|
||||
#
|
||||
path("dealers/<int:pk>/settings/", views.DealerSettingsView, name="dealer_settings"),
|
||||
path("dealers/assign-car-makes/", views.assign_car_makes, name="assign_car_makes"),
|
||||
path("dashboards/manager/", views.ManagerDashboard.as_view(), name="manager_dashboard"),
|
||||
|
||||
@ -14,6 +14,7 @@ from urllib.parse import urlparse, urlunparse
|
||||
|
||||
#####################################################################
|
||||
|
||||
from background_task.models import Task
|
||||
from django.db.models.deletion import RestrictedError
|
||||
|
||||
# Django
|
||||
@ -7702,3 +7703,16 @@ def payment_callback(request):
|
||||
history.save()
|
||||
message = request.GET.get('message')
|
||||
return render(request, "payment_failed.html", {"message": message})
|
||||
|
||||
|
||||
# Background Tasks
|
||||
def task_list(request):
|
||||
# Get all tasks ordered by creation time
|
||||
tasks = Task.objects.all()
|
||||
|
||||
# Add pagination
|
||||
paginator = Paginator(tasks, 10) # Show 10 tasks per page
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
return render(request, 'tasks/task_list.html', {'page_obj': page_obj})
|
||||
158
templates/tasks/task_list.html
Normal file
158
templates/tasks/task_list.html
Normal file
@ -0,0 +1,158 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">Background Tasks</h1>
|
||||
|
||||
<!-- Task Count and Refresh Button -->
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="badge bg-primary">Total Tasks: {{ page_obj.paginator.count }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'task_list' %}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-arrow-clockwise"></i> Refresh
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Task Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm fs-9 mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Task Name</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Scheduled Run</th>
|
||||
<th>Last Run</th>
|
||||
<th>Attempts</th>
|
||||
<th>Priority</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for task in page_obj %}
|
||||
<tr>
|
||||
<td>{{ task.id }}</td>
|
||||
<td>
|
||||
<span class="d-inline-block text-truncate" style="max-width: 150px;">
|
||||
{{ task.task_name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if task.locked_at %}
|
||||
<span class="badge bg-warning text-dark">Running</span>
|
||||
{% elif task.last_run_at and task.has_error %}
|
||||
<span class="badge bg-danger">Failed</span>
|
||||
{% elif task.last_run_at %}
|
||||
<span class="badge bg-success">Completed</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info text-dark">Pending</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ task.created_at|date:"Y-m-d H:i" }}</td>
|
||||
<td>{{ task.run_at|date:"Y-m-d H:i" }}</td>
|
||||
<td>
|
||||
{% if task.last_run_at %}
|
||||
{{ task.last_run_at|date:"Y-m-d H:i" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ task.attempts }}</td>
|
||||
<td>{{ task.priority }}</td>
|
||||
{% comment %} <td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-sm btn-phoenix-primary" data-bs-toggle="modal"
|
||||
data-bs-target="#taskDetailModal"
|
||||
data-task-id="{{ task.id }}">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td> {% endcomment %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="9" class="text-center">No tasks found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.paginator.num_pages > 1 %}
|
||||
<nav aria-label="Task pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1">« First</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">
|
||||
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last »</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Task Detail Modal -->
|
||||
<div class="modal fade" id="taskDetailModal" tabindex="-1" aria-labelledby="taskDetailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="taskDetailModalLabel">Task Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="taskDetailsContent">
|
||||
Loading task details...
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Load task details in modal
|
||||
document.getElementById('taskDetailModal').addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const taskId = button.getAttribute('data-task-id');
|
||||
const modal = this;
|
||||
|
||||
fetch(`/tasks/${taskId}/detail/`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
modal.querySelector('#taskDetailsContent').innerHTML = html;
|
||||
})
|
||||
.catch(error => {
|
||||
modal.querySelector('#taskDetailsContent').innerHTML =
|
||||
`<div class="alert alert-danger">Error loading task details: ${error}</div>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user