feat(migrations): update client phone field migration
This commit modifies the migration file for altering the client phone field. - **Enhancements & Refinements**: - Removed an unnecessary blank line in the migration class definition - Cleaned up formatting in the `0005_alter_client_phone.py` migration file ```
This commit is contained in:
6 files changed
+184
-92
No files matched your search
@@ -0,0 +1,18 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.finance_dashboard, name="finance_dashboard"),
|
||||
|
||||
# Invoices
|
||||
path("invoices/", views.invoice_list, name="invoice_list"),
|
||||
path("invoices/create/", views.invoice_create, name="invoice_create"),
|
||||
path("invoices/<int:pk>/edit/", views.invoice_edit, name="invoice_edit"),
|
||||
path("invoices/<int:pk>/delete/", views.invoice_delete, name="invoice_delete"),
|
||||
|
||||
# Expenses
|
||||
path("expenses/", views.expense_list, name="expense_list"),
|
||||
path("expenses/create/", views.expense_create, name="expense_create"),
|
||||
path("expenses/<int:pk>/edit/", views.expense_edit, name="expense_edit"),
|
||||
path("expenses/<int:pk>/delete/", views.expense_delete, name="expense_delete"),
|
||||
]
|
||||
+29
-15
@@ -5,6 +5,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from src.models.clients import Client
|
||||
from src.models.finance import Expense, Invoice
|
||||
from src.models.projects import Project
|
||||
from src.models.services import Service
|
||||
|
||||
|
||||
def finance_dashboard(request):
|
||||
@@ -45,22 +46,18 @@ def invoice_create(request):
|
||||
if request.method == "POST":
|
||||
client_id = request.POST.get("client")
|
||||
project_id = request.POST.get("project")
|
||||
invoice_number = request.POST.get("invoice_number")
|
||||
amount = request.POST.get("amount")
|
||||
date_issued = request.POST.get("date_issued")
|
||||
due_date = request.POST.get("due_date")
|
||||
status = request.POST.get("status")
|
||||
|
||||
if client_id and invoice_number and amount:
|
||||
if client_id and project_id and amount and due_date:
|
||||
client = get_object_or_404(Client, id=client_id)
|
||||
project = get_object_or_404(Project, id=project_id) if project_id else None
|
||||
project = get_object_or_404(Project, id=project_id)
|
||||
|
||||
Invoice.objects.create(
|
||||
client=client,
|
||||
project=project,
|
||||
invoice_number=invoice_number,
|
||||
amount=amount,
|
||||
date_issued=date_issued,
|
||||
due_date=due_date,
|
||||
status=status,
|
||||
)
|
||||
@@ -78,21 +75,17 @@ def invoice_edit(request, pk):
|
||||
if request.method == "POST":
|
||||
client_id = request.POST.get("client")
|
||||
project_id = request.POST.get("project")
|
||||
invoice_number = request.POST.get("invoice_number")
|
||||
amount = request.POST.get("amount")
|
||||
date_issued = request.POST.get("date_issued")
|
||||
due_date = request.POST.get("due_date")
|
||||
status = request.POST.get("status")
|
||||
|
||||
if client_id and invoice_number and amount:
|
||||
if client_id and project_id and amount and due_date:
|
||||
client = get_object_or_404(Client, id=client_id)
|
||||
project = get_object_or_404(Project, id=project_id) if project_id else None
|
||||
project = get_object_or_404(Project, id=project_id)
|
||||
|
||||
invoice.client = client
|
||||
invoice.project = project
|
||||
invoice.invoice_number = invoice_number
|
||||
invoice.amount = amount
|
||||
invoice.date_issued = date_issued
|
||||
invoice.due_date = due_date
|
||||
invoice.status = status
|
||||
invoice.save()
|
||||
@@ -127,14 +120,24 @@ def expense_create(request):
|
||||
amount = request.POST.get("amount")
|
||||
date = request.POST.get("date")
|
||||
category = request.POST.get("category")
|
||||
service_id = request.POST.get("service")
|
||||
|
||||
if description and amount and date:
|
||||
service = None
|
||||
if service_id:
|
||||
service = get_object_or_404(Service, id=service_id)
|
||||
|
||||
Expense.objects.create(
|
||||
description=description, amount=amount, date=date, category=category
|
||||
description=description,
|
||||
amount=amount,
|
||||
date=date,
|
||||
category=category,
|
||||
service=service,
|
||||
)
|
||||
return redirect("finance_dashboard")
|
||||
|
||||
return render(request, "finance/expense_form.html")
|
||||
services = Service.objects.all()
|
||||
return render(request, "finance/expense_form.html", {"services": services})
|
||||
|
||||
|
||||
def expense_edit(request, pk):
|
||||
@@ -144,16 +147,27 @@ def expense_edit(request, pk):
|
||||
amount = request.POST.get("amount")
|
||||
date = request.POST.get("date")
|
||||
category = request.POST.get("category")
|
||||
service_id = request.POST.get("service")
|
||||
|
||||
if description and amount and date:
|
||||
service = None
|
||||
if service_id:
|
||||
service = get_object_or_404(Service, id=service_id)
|
||||
|
||||
expense.description = description
|
||||
expense.amount = amount
|
||||
expense.date = date
|
||||
expense.category = category
|
||||
expense.service = service
|
||||
expense.save()
|
||||
return redirect("finance_dashboard")
|
||||
|
||||
return render(request, "finance/expense_form.html", {"expense": expense})
|
||||
services = Service.objects.all()
|
||||
return render(
|
||||
request,
|
||||
"finance/expense_form.html",
|
||||
{"expense": expense, "services": services},
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(["DELETE", "POST"])
|
||||
|
||||
+53
-10
@@ -1,27 +1,70 @@
|
||||
from django import forms
|
||||
|
||||
from src.models.projects import Project, Task
|
||||
|
||||
|
||||
class ProjectForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["name", "client", "description", "start_date", "deadline", "budget", "status"]
|
||||
fields = [
|
||||
"name",
|
||||
"client",
|
||||
"description",
|
||||
"start_date",
|
||||
"deadline",
|
||||
"budget",
|
||||
"status",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(attrs={"class": "input input-bordered w-full", "placeholder": "Project Name"}),
|
||||
"name": forms.TextInput(
|
||||
attrs={
|
||||
"class": "input input-bordered w-full",
|
||||
"placeholder": "Project Name",
|
||||
}
|
||||
),
|
||||
"client": forms.Select(attrs={"class": "select select-bordered w-full"}),
|
||||
"description": forms.Textarea(attrs={"class": "textarea textarea-bordered w-full", "rows": 4, "placeholder": "Project Description"}),
|
||||
"start_date": forms.DateInput(attrs={"class": "input input-bordered w-full", "type": "date"}),
|
||||
"deadline": forms.DateInput(attrs={"class": "input input-bordered w-full", "type": "date"}),
|
||||
"budget": forms.NumberInput(attrs={"class": "input input-bordered w-full", "placeholder": "0.00"}),
|
||||
"description": forms.Textarea(
|
||||
attrs={
|
||||
"class": "textarea textarea-bordered w-full",
|
||||
"rows": 4,
|
||||
"placeholder": "Project Description",
|
||||
}
|
||||
),
|
||||
"start_date": forms.DateInput(
|
||||
attrs={"class": "input input-bordered w-full", "type": "date"}
|
||||
),
|
||||
"deadline": forms.DateInput(
|
||||
attrs={"class": "input input-bordered w-full", "type": "date"}
|
||||
),
|
||||
"budget": forms.NumberInput(
|
||||
attrs={"class": "input input-bordered w-full", "placeholder": "0.00"}
|
||||
),
|
||||
"status": forms.Select(attrs={"class": "select select-bordered w-full"}),
|
||||
}
|
||||
|
||||
|
||||
class TaskForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = ["title", "description", "status", "due_date"]
|
||||
widgets = {
|
||||
"title": forms.TextInput(attrs={"class": "input input-bordered w-full input-sm", "placeholder": "Task Title"}),
|
||||
"description": forms.Textarea(attrs={"class": "textarea textarea-bordered w-full textarea-sm", "rows": 2, "placeholder": "Task Description"}),
|
||||
"status": forms.Select(attrs={"class": "select select-bordered w-full select-sm"}),
|
||||
"due_date": forms.DateInput(attrs={"class": "input input-bordered w-full input-sm", "type": "date"}),
|
||||
"title": forms.TextInput(
|
||||
attrs={
|
||||
"class": "input input-bordered w-full input-sm",
|
||||
"placeholder": "Task Title",
|
||||
}
|
||||
),
|
||||
"description": forms.Textarea(
|
||||
attrs={
|
||||
"class": "textarea textarea-bordered w-full textarea-sm",
|
||||
"rows": 2,
|
||||
"placeholder": "Task Description",
|
||||
}
|
||||
),
|
||||
"status": forms.Select(
|
||||
attrs={"class": "select select-bordered w-full select-sm"}
|
||||
),
|
||||
"due_date": forms.DateInput(
|
||||
attrs={"class": "input input-bordered w-full input-sm", "type": "date"}
|
||||
),
|
||||
}
|
||||
+15
-10
@@ -1,15 +1,20 @@
|
||||
from django.urls import path
|
||||
|
||||
from src.api.projects import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.project_list, name='project_list'),
|
||||
path('create/', views.project_create, name='project_create'),
|
||||
path('<int:pk>/', views.project_detail, name='project_detail'),
|
||||
path('<int:pk>/edit/', views.project_edit, name='project_edit'),
|
||||
path('<int:pk>/delete/', views.project_delete, name='project_delete'),
|
||||
path('<int:project_id>/tasks/create/', views.task_create, name='task_create'),
|
||||
path('tasks/<int:task_id>/update/', views.task_update_status, name='task_update_status'),
|
||||
path('tasks/<int:task_id>/edit/', views.task_edit, name='task_edit'),
|
||||
path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
||||
path('tasks/<int:task_id>/delete/', views.task_delete, name='task_delete'),
|
||||
path("", views.project_list, name="project_list"),
|
||||
path("create/", views.project_create, name="project_create"),
|
||||
path("<int:pk>/", views.project_detail, name="project_detail"),
|
||||
path("<int:pk>/edit/", views.project_edit, name="project_edit"),
|
||||
path("<int:pk>/delete/", views.project_delete, name="project_delete"),
|
||||
path("<int:project_id>/tasks/create/", views.task_create, name="task_create"),
|
||||
path(
|
||||
"tasks/<int:task_id>/update/",
|
||||
views.task_update_status,
|
||||
name="task_update_status",
|
||||
),
|
||||
path("tasks/<int:task_id>/edit/", views.task_edit, name="task_edit"),
|
||||
path("tasks/<int:task_id>/detail/", views.task_detail, name="task_detail"),
|
||||
path("tasks/<int:task_id>/delete/", views.task_delete, name="task_delete"),
|
||||
]
|
||||
+69
-56
@@ -1,88 +1,92 @@
|
||||
from django.shortcuts import render, get_object_or_404, redirect, HttpResponse
|
||||
from django.shortcuts import HttpResponse, get_object_or_404, redirect, render
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from src.models.projects import Project, Task
|
||||
|
||||
from src.api.projects.forms import ProjectForm, TaskForm
|
||||
from src.models.projects import Project, Task
|
||||
|
||||
|
||||
def project_list(request):
|
||||
projects = Project.objects.all().select_related('client')
|
||||
return render(request, 'projects/project_list.html', {'projects': projects})
|
||||
projects = Project.objects.all().select_related("client")
|
||||
return render(request, "projects/project_list.html", {"projects": projects})
|
||||
|
||||
|
||||
def project_create(request):
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
form = ProjectForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('project_list')
|
||||
return redirect("project_list")
|
||||
else:
|
||||
form = ProjectForm()
|
||||
return render(request, 'projects/project_form.html', {'form': form})
|
||||
return render(request, "projects/project_form.html", {"form": form})
|
||||
|
||||
|
||||
def get_kanban_context(project):
|
||||
tasks = project.tasks.all()
|
||||
return {
|
||||
'project': project,
|
||||
'tasks_todo': tasks.filter(status='TODO'),
|
||||
'tasks_in_progress': tasks.filter(status='IN_PROGRESS'),
|
||||
'tasks_review': tasks.filter(status='REVIEW'),
|
||||
'tasks_done': tasks.filter(status='DONE'),
|
||||
"project": project,
|
||||
"tasks_todo": tasks.filter(status="TODO"),
|
||||
"tasks_in_progress": tasks.filter(status="IN_PROGRESS"),
|
||||
"tasks_review": tasks.filter(status="REVIEW"),
|
||||
"tasks_done": tasks.filter(status="DONE"),
|
||||
}
|
||||
|
||||
|
||||
def project_detail(request, pk):
|
||||
project = get_object_or_404(Project, pk=pk)
|
||||
context = get_kanban_context(project)
|
||||
# Add a blank task form for the "Add Task" modal/section
|
||||
context['task_form'] = TaskForm()
|
||||
return render(request, 'projects/project_detail.html', context)
|
||||
context["task_form"] = TaskForm()
|
||||
return render(request, "projects/project_detail.html", context)
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
def task_create(request, project_id):
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
logger.info(f"Task create called for project {project_id}")
|
||||
logger.info(f"POST data: {request.POST}")
|
||||
|
||||
|
||||
project = get_object_or_404(Project, pk=project_id)
|
||||
title = request.POST.get('title', '').strip()
|
||||
status = request.POST.get('status', 'TODO')
|
||||
due_date_str = request.POST.get('due_date', '').strip()
|
||||
|
||||
title = request.POST.get("title", "").strip()
|
||||
status = request.POST.get("status", "TODO")
|
||||
due_date_str = request.POST.get("due_date", "").strip()
|
||||
|
||||
# Parse due_date string to date object
|
||||
due_date = None
|
||||
if due_date_str:
|
||||
try:
|
||||
due_date = datetime.strptime(due_date_str, '%Y-%m-%d').date()
|
||||
due_date = datetime.strptime(due_date_str, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid due_date format: {due_date_str}")
|
||||
|
||||
|
||||
logger.info(f"Title: '{title}', Status: '{status}', Due Date: '{due_date}'")
|
||||
|
||||
|
||||
if not title:
|
||||
logger.error("Task title is empty")
|
||||
return HttpResponse("Task title is required", status=400)
|
||||
|
||||
|
||||
try:
|
||||
task = Task.objects.create(
|
||||
project=project,
|
||||
title=title,
|
||||
status=status,
|
||||
due_date=due_date
|
||||
project=project, title=title, status=status, due_date=due_date
|
||||
)
|
||||
logger.info(f"Task created successfully: {task.id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating task: {e}")
|
||||
return HttpResponse(f"Error creating task: {str(e)}", status=500)
|
||||
|
||||
|
||||
# Return the task HTML directly
|
||||
due_date_html = ''
|
||||
due_date_html = ""
|
||||
if task.due_date:
|
||||
due_date_html = f'''
|
||||
due_date_html = f"""
|
||||
<div class="text-xs text-base-content/60 mt-1 flex items-center gap-1">
|
||||
<span class="icon-[tabler--calendar] size-3"></span>
|
||||
{task.due_date.strftime("%b %d")}
|
||||
</div>'''
|
||||
|
||||
</div>"""
|
||||
|
||||
html = f'''<div class="task-item card bg-base-100 shadow-sm hover:shadow-md p-3 cursor-move"
|
||||
draggable="true"
|
||||
data-task-id="{task.id}"
|
||||
@@ -104,43 +108,46 @@ def task_create(request, project_id):
|
||||
</div>
|
||||
</div>
|
||||
</div>'''
|
||||
|
||||
|
||||
logger.info(f"Returning HTML response (length: {len(html)})")
|
||||
return HttpResponse(html)
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
def task_update_status(request, task_id):
|
||||
task = get_object_or_404(Task, pk=task_id)
|
||||
status = request.POST.get('status')
|
||||
status = request.POST.get("status")
|
||||
if status in dict(Task.STATUS_CHOICES):
|
||||
task.status = status
|
||||
task.save()
|
||||
|
||||
|
||||
project = task.project
|
||||
context = get_kanban_context(project)
|
||||
return render(request, 'projects/partials/kanban_board.html', context)
|
||||
return render(request, "projects/partials/kanban_board.html", context)
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
def task_edit(request, task_id):
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
task = get_object_or_404(Task, pk=task_id)
|
||||
title = request.POST.get('title', '').strip()
|
||||
due_date_str = request.POST.get('due_date', '').strip()
|
||||
|
||||
title = request.POST.get("title", "").strip()
|
||||
due_date_str = request.POST.get("due_date", "").strip()
|
||||
|
||||
# Parse due_date string to date object
|
||||
due_date = None
|
||||
if due_date_str:
|
||||
try:
|
||||
due_date = datetime.strptime(due_date_str, '%Y-%m-%d').date()
|
||||
due_date = datetime.strptime(due_date_str, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid due_date format: {due_date_str}")
|
||||
|
||||
|
||||
if not title:
|
||||
return HttpResponse("Task title is required", status=400)
|
||||
|
||||
|
||||
try:
|
||||
task.title = title
|
||||
task.due_date = due_date
|
||||
@@ -149,16 +156,16 @@ def task_edit(request, task_id):
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating task: {e}")
|
||||
return HttpResponse(f"Error updating task: {str(e)}", status=500)
|
||||
|
||||
|
||||
# Return the updated task HTML
|
||||
due_date_html = ''
|
||||
due_date_html = ""
|
||||
if task.due_date:
|
||||
due_date_html = f'''
|
||||
due_date_html = f"""
|
||||
<div class="text-xs text-base-content/60 mt-1 flex items-center gap-1">
|
||||
<span class="icon-[tabler--calendar] size-3"></span>
|
||||
{task.due_date.strftime("%b %d")}
|
||||
</div>'''
|
||||
|
||||
</div>"""
|
||||
|
||||
html = f'''<div class="task-item card bg-base-100 shadow-sm hover:shadow-md p-3 cursor-move"
|
||||
draggable="true"
|
||||
data-task-id="{task.id}"
|
||||
@@ -174,18 +181,20 @@ def task_edit(request, task_id):
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-32">
|
||||
<li><a class="edit-task-btn" data-task-id="{task.id}" data-task-title="{task.title}" data-task-due-date="{task.due_date.strftime('%Y-%m-%d') if task.due_date else ''}">Edit</a></li>
|
||||
<li><a class="edit-task-btn" data-task-id="{task.id}" data-task-title="{task.title}" data-task-due-date="{task.due_date.strftime("%Y-%m-%d") if task.due_date else ""}">Edit</a></li>
|
||||
<li><a class="delete-task-btn" data-task-id="{task.id}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>'''
|
||||
|
||||
|
||||
return HttpResponse(html)
|
||||
|
||||
|
||||
def task_detail(request, task_id):
|
||||
task = get_object_or_404(Task, pk=task_id)
|
||||
return render(request, 'projects/partials/task_item.html', {'task': task})
|
||||
return render(request, "projects/partials/task_item.html", {"task": task})
|
||||
|
||||
|
||||
@require_http_methods(["DELETE", "POST"])
|
||||
def task_delete(request, task_id):
|
||||
@@ -193,16 +202,20 @@ def task_delete(request, task_id):
|
||||
task.delete()
|
||||
return HttpResponse("")
|
||||
|
||||
|
||||
def project_edit(request, pk):
|
||||
project = get_object_or_404(Project, pk=pk)
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
form = ProjectForm(request.POST, instance=project)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('project_detail', pk=project.pk)
|
||||
return redirect("project_detail", pk=project.pk)
|
||||
else:
|
||||
form = ProjectForm(instance=project)
|
||||
return render(request, 'projects/project_form.html', {'form': form, 'project': project})
|
||||
return render(
|
||||
request, "projects/project_form.html", {"form": form, "project": project}
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(["DELETE", "POST"])
|
||||
def project_delete(request, pk):
|
||||
@@ -210,4 +223,4 @@ def project_delete(request, pk):
|
||||
project.delete()
|
||||
if request.htmx:
|
||||
return HttpResponse("")
|
||||
return redirect('project_list')
|
||||
return redirect("project_list")
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("src", "0004_alter_invoice_due_date"),
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user