adding imprt export categ, suppliers
This commit is contained in:
parent
40404a2947
commit
42e2393347
BIN
core/__pycache__/forms_import.cpython-311.pyc
Normal file
BIN
core/__pycache__/forms_import.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/views_import.cpython-311.pyc
Normal file
BIN
core/__pycache__/views_import.cpython-311.pyc
Normal file
Binary file not shown.
4
core/forms_import.py
Normal file
4
core/forms_import.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from django import forms
|
||||||
|
|
||||||
|
class ImportFileForm(forms.Form):
|
||||||
|
file = forms.FileField(label="Excel File (.xlsx)")
|
||||||
@ -195,6 +195,7 @@ class SalePayment(models.Model):
|
|||||||
payment_method_name = models.CharField(_("Payment Method Name"), max_length=50, default="Cash") # Fallback
|
payment_method_name = models.CharField(_("Payment Method Name"), max_length=50, default="Cash") # Fallback
|
||||||
notes = models.TextField(_("Notes"), blank=True)
|
notes = models.TextField(_("Notes"), blank=True)
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Payment of {self.amount} for Sale #{self.sale.id}"
|
return f"Payment of {self.amount} for Sale #{self.sale.id}"
|
||||||
@ -322,6 +323,7 @@ class PurchasePayment(models.Model):
|
|||||||
payment_method_name = models.CharField(_("Payment Method Name"), max_length=50, default="Cash") # Fallback
|
payment_method_name = models.CharField(_("Payment Method Name"), max_length=50, default="Cash") # Fallback
|
||||||
notes = models.TextField(_("Notes"), blank=True)
|
notes = models.TextField(_("Notes"), blank=True)
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Payment of {self.amount} for Purchase #{self.purchase.id}"
|
return f"Payment of {self.amount} for Purchase #{self.purchase.id}"
|
||||||
|
|||||||
54
core/templates/core/import_categories.html
Normal file
54
core/templates/core/import_categories.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h1 class="h2">{% trans "Import Categories" %}</h1>
|
||||||
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
|
<a href="{% url 'inventory' %}#categories-list" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> {% trans "Back to Inventory" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">{% trans "Upload Excel File" %}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle"></i> {% trans "Excel Format Instructions:" %}<br>
|
||||||
|
<ul>
|
||||||
|
<li><strong>{% trans "Column A" %}:</strong> {% trans "Name (English) - Required" %}</li>
|
||||||
|
<li><strong>{% trans "Column B" %}:</strong> {% trans "Name (Arabic) - Optional (Defaults to English name if blank)" %}</li>
|
||||||
|
</ul>
|
||||||
|
<small>{% trans "Please skip the first row (header)." %}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<div class="form-text">{{ field.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="text-danger">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
|
<i class="bi bi-upload"></i> {% trans "Import Categories" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
55
core/templates/core/import_suppliers.html
Normal file
55
core/templates/core/import_suppliers.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h1 class="h2">{% trans "Import Suppliers" %}</h1>
|
||||||
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
|
<a href="{% url 'suppliers' %}" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> {% trans "Back to Suppliers" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">{% trans "Upload Excel File" %}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle"></i> {% trans "Excel Format Instructions:" %}<br>
|
||||||
|
<ul>
|
||||||
|
<li><strong>{% trans "Column A" %}:</strong> {% trans "Supplier Name - Required" %}</li>
|
||||||
|
<li><strong>{% trans "Column B" %}:</strong> {% trans "Contact Person - Optional" %}</li>
|
||||||
|
<li><strong>{% trans "Column C" %}:</strong> {% trans "Phone - Optional" %}</li>
|
||||||
|
</ul>
|
||||||
|
<small>{% trans "Please skip the first row (header)." %}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<div class="form-text">{{ field.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="text-danger">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
|
<i class="bi bi-upload"></i> {% trans "Import Suppliers" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -25,6 +25,9 @@
|
|||||||
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
|
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
|
||||||
<i class="bi bi-folder-plus me-2"></i>{% trans "Add Category" %}
|
<i class="bi bi-folder-plus me-2"></i>{% trans "Add Category" %}
|
||||||
</button>
|
</button>
|
||||||
|
<a href="{% url 'import_categories' %}" class="btn btn-outline-success shadow-sm">
|
||||||
|
<i class="bi bi-file-earmark-arrow-up me-2"></i>{% trans "Import Categories" %}
|
||||||
|
</a>
|
||||||
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addUnitModal">
|
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addUnitModal">
|
||||||
<i class="bi bi-rulers me-2"></i>{% trans "Add Unit" %}
|
<i class="bi bi-rulers me-2"></i>{% trans "Add Unit" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -15,9 +15,14 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
|
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
|
||||||
<i class="bi bi-person-plus me-2"></i>{% trans "Add Supplier" %}
|
<i class="bi bi-person-plus me-2"></i>{% trans "Add Supplier" %}
|
||||||
</button>
|
</button>
|
||||||
|
<a href="{% url 'import_suppliers' %}" class="btn btn-success shadow-sm">
|
||||||
|
<i class="bi bi-file-earmark-excel me-2"></i>{% trans "Import" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
@ -60,6 +65,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<!-- Edit Supplier Modal -->
|
||||||
|
<div class="modal fade" id="editSupplierModal{{ supplier.id }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content rounded-4 border-0 shadow">
|
||||||
|
<div class="modal-header border-0 pb-0">
|
||||||
|
<h5 class="modal-title fw-bold">{% trans "Edit Supplier" %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form action="{% url 'edit_supplier' supplier.id %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold">{% trans "Supplier Name" %}</label>
|
||||||
|
<input type="text" name="name" class="form-control rounded-3" value="{{ supplier.name }}" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold">{% trans "Contact Person" %}</label>
|
||||||
|
<input type="text" name="contact_person" class="form-control rounded-3" value="{{ supplier.contact_person }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||||
|
<input type="text" name="phone" class="form-control rounded-3" value="{{ supplier.phone }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||||
|
<button type="submit" class="btn btn-primary rounded-3 px-4">{% trans "Save Changes" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
from . import views_import
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
@ -92,6 +93,7 @@ urlpatterns = [
|
|||||||
path('suppliers/edit/<int:pk>/', views.edit_supplier, name='edit_supplier'),
|
path('suppliers/edit/<int:pk>/', views.edit_supplier, name='edit_supplier'),
|
||||||
path('suppliers/delete/<int:pk>/', views.delete_supplier, name='delete_supplier'),
|
path('suppliers/delete/<int:pk>/', views.delete_supplier, name='delete_supplier'),
|
||||||
path('api/add-supplier-ajax/', views.add_supplier_ajax, name='add_supplier_ajax'),
|
path('api/add-supplier-ajax/', views.add_supplier_ajax, name='add_supplier_ajax'),
|
||||||
|
path('suppliers/import/', views_import.import_suppliers, name='import_suppliers'),
|
||||||
|
|
||||||
# Inventory
|
# Inventory
|
||||||
path('inventory/suggest-sku/', views.suggest_sku, name='suggest_sku'),
|
path('inventory/suggest-sku/', views.suggest_sku, name='suggest_sku'),
|
||||||
@ -106,6 +108,7 @@ urlpatterns = [
|
|||||||
path('inventory/category/edit/<int:pk>/', views.edit_category, name='edit_category'),
|
path('inventory/category/edit/<int:pk>/', views.edit_category, name='edit_category'),
|
||||||
path('inventory/category/delete/<int:pk>/', views.delete_category, name='delete_category'),
|
path('inventory/category/delete/<int:pk>/', views.delete_category, name='delete_category'),
|
||||||
path('api/add-category-ajax/', views.add_category_ajax, name='add_category_ajax'),
|
path('api/add-category-ajax/', views.add_category_ajax, name='add_category_ajax'),
|
||||||
|
path('inventory/category/import/', views_import.import_categories, name='import_categories'),
|
||||||
|
|
||||||
# Units
|
# Units
|
||||||
path('inventory/unit/add/', views.add_unit, name='add_unit'),
|
path('inventory/unit/add/', views.add_unit, name='add_unit'),
|
||||||
|
|||||||
139
core/views_import.py
Normal file
139
core/views_import.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.text import slugify
|
||||||
|
import openpyxl
|
||||||
|
from .models import Category, Supplier
|
||||||
|
from .forms_import import ImportFileForm
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def import_categories(request):
|
||||||
|
"""
|
||||||
|
Import categories from an Excel (.xlsx) file.
|
||||||
|
Expected columns: Name (Eng), Name (Ar)
|
||||||
|
"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ImportFileForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
excel_file = request.FILES['file']
|
||||||
|
|
||||||
|
try:
|
||||||
|
wb = openpyxl.load_workbook(excel_file)
|
||||||
|
sheet = wb.active
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
updated_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# Skip header row (min_row=2)
|
||||||
|
for i, row in enumerate(sheet.iter_rows(min_row=2, values_only=True), start=2):
|
||||||
|
if not any(row): continue # Skip empty rows
|
||||||
|
|
||||||
|
# Unpack columns with fallbacks for safety
|
||||||
|
# Format: name_en, name_ar
|
||||||
|
name_en = str(row[0]).strip() if row[0] else None
|
||||||
|
name_ar = str(row[1]).strip() if len(row) > 1 and row[1] else name_en
|
||||||
|
|
||||||
|
if not name_en:
|
||||||
|
errors.append(f"Row {i}: Missing English Name. Skipped.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
slug = slugify(name_en)
|
||||||
|
|
||||||
|
category, created = Category.objects.update_or_create(
|
||||||
|
slug=slug,
|
||||||
|
defaults={
|
||||||
|
'name_en': name_en,
|
||||||
|
'name_ar': name_ar,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
if count > 0 or updated_count > 0:
|
||||||
|
msg = f"Import completed: {count} new categories added"
|
||||||
|
if updated_count > 0:
|
||||||
|
msg += f", {updated_count} categories updated"
|
||||||
|
messages.success(request, msg)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
for error in errors:
|
||||||
|
messages.warning(request, error)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, f"Error processing file: {str(e)}")
|
||||||
|
|
||||||
|
return redirect(reverse('inventory') + '#categories-list')
|
||||||
|
else:
|
||||||
|
form = ImportFileForm()
|
||||||
|
|
||||||
|
return render(request, 'core/import_categories.html', {'form': form})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def import_suppliers(request):
|
||||||
|
"""
|
||||||
|
Import suppliers from an Excel (.xlsx) file.
|
||||||
|
Expected columns: Name, Contact Person, Phone
|
||||||
|
"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ImportFileForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
excel_file = request.FILES['file']
|
||||||
|
|
||||||
|
try:
|
||||||
|
wb = openpyxl.load_workbook(excel_file)
|
||||||
|
sheet = wb.active
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
updated_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# Skip header row (min_row=2)
|
||||||
|
for i, row in enumerate(sheet.iter_rows(min_row=2, values_only=True), start=2):
|
||||||
|
if not any(row): continue # Skip empty rows
|
||||||
|
|
||||||
|
# Unpack columns with fallbacks for safety
|
||||||
|
# Format: Name, Contact Person, Phone
|
||||||
|
name = str(row[0]).strip() if row[0] else None
|
||||||
|
contact_person = str(row[1]).strip() if len(row) > 1 and row[1] else ''
|
||||||
|
phone = str(row[2]).strip() if len(row) > 2 and row[2] else ''
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
errors.append(f"Row {i}: Missing Name. Skipped.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
supplier, created = Supplier.objects.update_or_create(
|
||||||
|
name=name,
|
||||||
|
defaults={
|
||||||
|
'contact_person': contact_person,
|
||||||
|
'phone': phone,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
if count > 0 or updated_count > 0:
|
||||||
|
msg = f"Import completed: {count} new suppliers added"
|
||||||
|
if updated_count > 0:
|
||||||
|
msg += f", {updated_count} suppliers updated"
|
||||||
|
messages.success(request, msg)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
for error in errors:
|
||||||
|
messages.warning(request, error)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, f"Error processing file: {str(e)}")
|
||||||
|
|
||||||
|
return redirect('suppliers')
|
||||||
|
else:
|
||||||
|
form = ImportFileForm()
|
||||||
|
|
||||||
|
return render(request, 'core/import_suppliers.html', {'form': form})
|
||||||
43
patch_models_timestamp.py
Normal file
43
patch_models_timestamp.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
path = 'core/models.py'
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Patch SalePayment
|
||||||
|
old_sale_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
|
||||||
|
|
||||||
|
def __str__(self):"""
|
||||||
|
new_sale_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):"""
|
||||||
|
|
||||||
|
# Patch PurchasePayment
|
||||||
|
old_purchase_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
|
||||||
|
|
||||||
|
def __str__(self):"""
|
||||||
|
new_purchase_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):"""
|
||||||
|
|
||||||
|
# Check if SalePayment already has created_at
|
||||||
|
# A simple check: if we find the new pattern, we skip
|
||||||
|
if new_sale_payment in content:
|
||||||
|
print("SalePayment already patched.")
|
||||||
|
else:
|
||||||
|
content = content.replace(old_sale_payment, new_sale_payment)
|
||||||
|
|
||||||
|
if new_purchase_payment in content:
|
||||||
|
print("PurchasePayment already patched.")
|
||||||
|
else:
|
||||||
|
content = content.replace(old_purchase_payment, new_purchase_payment)
|
||||||
|
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
print("Patched core/models.py")
|
||||||
Loading…
x
Reference in New Issue
Block a user