A lot of new views
Color picker fields Renamed pixel.png in order not to upset uBlock...
This commit is contained in:
parent
65f34b32eb
commit
2e8bbdd8a7
56 changed files with 1657 additions and 241 deletions
|
@ -1,5 +1,6 @@
|
|||
from core.classes.cron import Cronjob
|
||||
from core.helpers.auth import clear_login_log, clear_ratelimits
|
||||
from core.helpers.cron import clear_cron_log
|
||||
|
||||
CRONDEFINITIONS = []
|
||||
CRONFUNCTIONS = {}
|
||||
|
@ -27,3 +28,10 @@ ratelimit_cron = Cronjob("core.clear_ratelimits", "* * * * *")
|
|||
|
||||
CRONFUNCTIONS["core.clear_ratelimits"] = clear_ratelimits
|
||||
CRONDEFINITIONS.append(ratelimit_cron)
|
||||
|
||||
### Remove old entries from the cron execution log
|
||||
|
||||
cronlog_cron = Cronjob("core.clear_cron_log", "* * * * *")
|
||||
|
||||
CRONFUNCTIONS["core.clear_cron_log"] = clear_cron_log
|
||||
CRONDEFINITIONS.append(cronlog_cron)
|
|
@ -0,0 +1,2 @@
|
|||
from core.fields.base import LongCharField
|
||||
from core.fields.color import ColorField
|
7
core/fields/color.py
Normal file
7
core/fields/color.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from core.fields.base import LongCharField
|
||||
from core.widgets.color import ColorPickerWidget
|
||||
|
||||
class ColorField(LongCharField):
|
||||
def formfield(self, **kwargs):
|
||||
kwargs['widget'] = ColorPickerWidget
|
||||
return super().formfield(**kwargs)
|
|
@ -1,9 +1,14 @@
|
|||
from django.forms import ModelForm, CharField, BooleanField, ImageField
|
||||
from django.forms import ModelForm, CharField, BooleanField, ImageField, ModelChoiceField, ModelMultipleChoiceField, BooleanField
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
from django_countries.fields import CountryField
|
||||
from internationalflavor.vat_number.forms import VATNumberFormField
|
||||
|
||||
from core.models.local import Currency
|
||||
from core.models.brands import Brand
|
||||
|
||||
class AdminEditForm(ModelForm):
|
||||
display_name = CharField(required=False, label=_('Internal Display Name'))
|
||||
|
@ -25,3 +30,23 @@ class AdminCreateForm(ModelForm):
|
|||
class Meta:
|
||||
model = get_user_model()
|
||||
fields = ('first_name', 'last_name', "display_name", "email", 'mobile', "role", "image")
|
||||
|
||||
class ClientForm(ModelForm):
|
||||
company = CharField(required=False, label=_('Company Name'))
|
||||
mobile = PhoneNumberField(required=False, label=_('Mobile Number'))
|
||||
address1 = CharField(label=_('Address'))
|
||||
address2 = CharField(label=_('Address'))
|
||||
zip = CharField(label=_('ZIP'))
|
||||
city = CharField(label=_('City'))
|
||||
state = CharField(label=_('State'))
|
||||
country = CountryField()
|
||||
vat_id = VATNumberFormField(label=_('VAT Number'))
|
||||
company_id = CharField(label=_('Company Registration Number'))
|
||||
default_currency = ModelChoiceField(Currency.objects.all(), label=_("Default Currency"))
|
||||
brands = ModelMultipleChoiceField(Brand.objects.all(), label=_("Associated Brands"))
|
||||
marketing_opt_in = BooleanField(label=_("Opted in to marketing messages"))
|
||||
pgp_key = CharField(label=_("GPG encryption key"))
|
||||
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
fields = ('first_name', 'last_name', "company", "email", 'mobile')
|
|
@ -1,13 +1,25 @@
|
|||
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||
from django_celery_results.models import TaskResult
|
||||
|
||||
def setup_cron():
|
||||
from django.utils import timezone
|
||||
|
||||
from dbsettings.functions import getValue
|
||||
|
||||
def setup_cron(frequency=int(getValue("core.cron.frequency", 5))):
|
||||
schedule, created = IntervalSchedule.objects.get_or_create(
|
||||
every=5,
|
||||
every=frequency,
|
||||
period=IntervalSchedule.SECONDS,
|
||||
)
|
||||
|
||||
PeriodicTask.objects.get_or_create(
|
||||
interval=schedule,
|
||||
cron = PeriodicTask.objects.get_or_create(
|
||||
name='Expephacron',
|
||||
task='cron',
|
||||
)
|
||||
)[0]
|
||||
|
||||
cron.interval = schedule
|
||||
|
||||
cron.save()
|
||||
|
||||
def clear_cron_log(maxage=int(getValue("core.cron.log.retention", 86400))):
|
||||
timestamp = timezone.now() - timezone.timedelta(seconds=maxage)
|
||||
TaskResult.objects.filter(date_done__lt=timestamp).delete()
|
|
@ -6,3 +6,4 @@ from core.models.cron import *
|
|||
from core.models.products import *
|
||||
from core.models.billable import *
|
||||
from core.models.services import *
|
||||
from core.models.invoices import *
|
|
@ -2,12 +2,14 @@ from django.db.models import Model, ImageField
|
|||
|
||||
from core.fields.base import LongCharField
|
||||
from core.helpers.files import generate_storage_filename
|
||||
from core.fields.color import ColorField
|
||||
|
||||
from internationalflavor.vat_number.models import VATNumberField
|
||||
from django_countries.fields import CountryField
|
||||
|
||||
class Brand(Model):
|
||||
name = LongCharField(null=True, blank=True)
|
||||
color = ColorField()
|
||||
logo = ImageField(null=True, blank=True, upload_to=generate_storage_filename)
|
||||
address1 = LongCharField()
|
||||
address2 = LongCharField(null=True, blank=True)
|
||||
|
@ -17,3 +19,6 @@ class Brand(Model):
|
|||
country = CountryField()
|
||||
vat_id = VATNumberField(null=True, blank=True)
|
||||
company_id = LongCharField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
|
@ -12,19 +12,22 @@ from core.fields.base import LongCharField
|
|||
class BaseFile(PolymorphicModel):
|
||||
filename = LongCharField()
|
||||
|
||||
def __str__(self):
|
||||
return self.filename
|
||||
|
||||
class ImageFile(BaseFile):
|
||||
rawfile = ImageField(upload_to=generate_storage_filename)
|
||||
|
||||
@property
|
||||
def get_file(self):
|
||||
return self.image
|
||||
return self.rawfile
|
||||
|
||||
class File(BaseFile):
|
||||
rawfile = FileField(upload_to=generate_storage_filename)
|
||||
|
||||
@property
|
||||
def get_file(self):
|
||||
return self.file
|
||||
return self.rawfile
|
||||
|
||||
class FileAssociation(Model):
|
||||
file = ForeignKey(BaseFile, CASCADE)
|
||||
|
|
24
core/models/invoices.py
Normal file
24
core/models/invoices.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from django.db.models import Model, ForeignKey, CASCADE, PositiveIntegerField, TextField, DecimalField, BooleanField, DateField, SET_NULL, PROTECT
|
||||
|
||||
from core.fields.base import LongCharField
|
||||
from core.models.services import Service
|
||||
from core.models.profiles import ClientProfile
|
||||
from core.models.local import Currency
|
||||
|
||||
class Invoice(Model):
|
||||
client = ForeignKey(ClientProfile, on_delete=CASCADE)
|
||||
number = LongCharField()
|
||||
created = DateField()
|
||||
due = DateField()
|
||||
payment_method = LongCharField()
|
||||
currency = ForeignKey(Currency, on_delete=PROTECT)
|
||||
|
||||
class InvoiceItem(Model):
|
||||
invoice = ForeignKey(Invoice, on_delete=CASCADE)
|
||||
sort = PositiveIntegerField()
|
||||
name = LongCharField()
|
||||
description = TextField(blank=True, null=True)
|
||||
price = DecimalField(max_digits=32, decimal_places=2)
|
||||
discount = DecimalField(max_digits=32, decimal_places=2)
|
||||
taxable = BooleanField()
|
||||
service = ForeignKey(Service, on_delete=SET_NULL, null=True)
|
|
@ -19,6 +19,9 @@ class Currency(Model):
|
|||
def get_base(cls):
|
||||
return cls.objects.get(base=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{name} ({code})"
|
||||
|
||||
class TaxPolicy(Model):
|
||||
name = LongCharField(blank=True)
|
||||
default_rate = DecimalField(default=0, max_digits=10, decimal_places=5)
|
||||
|
@ -29,8 +32,14 @@ class TaxPolicy(Model):
|
|||
return rule.tax_rate if not rule.reverse_charge else 0
|
||||
return rule.tax_rate
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class TaxRule(Model):
|
||||
policy = ForeignKey(TaxPolicy, on_delete=CASCADE)
|
||||
destination_country = CountryField()
|
||||
tax_rate = DecimalField(max_digits=10, decimal_places=5)
|
||||
reverse_charge = BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.destination_country)
|
|
@ -1,5 +1,6 @@
|
|||
from core.models.billable import CycleChoices
|
||||
from core.fields.base import LongCharField
|
||||
from core.fields.color import ColorField
|
||||
|
||||
from django.db.models import Model, IntegerChoices, PositiveIntegerField, DecimalField, ForeignKey, CASCADE, CharField, TextField, ManyToManyField
|
||||
|
||||
|
@ -11,6 +12,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class ProductGroup(Model):
|
||||
name = LongCharField()
|
||||
color = ColorField()
|
||||
|
||||
class Product(Model):
|
||||
name = LongCharField()
|
||||
|
|
|
@ -10,9 +10,11 @@ from core.helpers.files import generate_storage_filename
|
|||
from core.models.local import Currency
|
||||
from core.models.brands import Brand
|
||||
from core.fields.base import LongCharField
|
||||
from core.fields.color import ColorField
|
||||
|
||||
class ClientGroup(Model):
|
||||
name = LongCharField()
|
||||
color = ColorField()
|
||||
|
||||
class Profile(PolymorphicModel):
|
||||
user = OneToOneField(get_user_model(), CASCADE)
|
||||
|
|
|
@ -3,81 +3,8 @@ import importlib
|
|||
from django.conf import settings
|
||||
from django.urls import path
|
||||
|
||||
from core.views import (
|
||||
DashboardView,
|
||||
LoginView,
|
||||
OTPSelectorView,
|
||||
LogoutView,
|
||||
OTPValidatorView,
|
||||
PWResetView,
|
||||
PWRequestView,
|
||||
RateLimitedView,
|
||||
BackendNotImplementedView,
|
||||
AdminListView,
|
||||
AdminDeleteView,
|
||||
AdminEditView,
|
||||
AdminCreateView,
|
||||
DBSettingsListView,
|
||||
DBSettingsEditView,
|
||||
DBSettingsDeleteView,
|
||||
DBSettingsCreateView,
|
||||
BrandCreateView,
|
||||
BrandDeleteView,
|
||||
BrandEditView,
|
||||
BrandListView,
|
||||
RateLimitCreateView,
|
||||
RateLimitDeleteView,
|
||||
RateLimitEditView,
|
||||
RateLimitListView,
|
||||
)
|
||||
|
||||
URLPATTERNS = []
|
||||
|
||||
# Auth URLs
|
||||
|
||||
URLPATTERNS.append(path('login/', LoginView.as_view(), name="login"))
|
||||
URLPATTERNS.append(path('login/otp/select/', OTPSelectorView.as_view(), name="otpselector"))
|
||||
URLPATTERNS.append(path('login/otp/validate/', OTPValidatorView.as_view(), name="otpvalidator"))
|
||||
URLPATTERNS.append(path('logout/', LogoutView.as_view(), name="logout"))
|
||||
URLPATTERNS.append(path('login/reset/', PWRequestView.as_view(), name="pwrequest"))
|
||||
URLPATTERNS.append(path('login/reset/<pk>/', PWResetView.as_view(), name="pwreset"))
|
||||
URLPATTERNS.append(path('login/ratelimit/', RateLimitedView.as_view(), name="ratelimited"))
|
||||
|
||||
# Base Backend URLs
|
||||
|
||||
URLPATTERNS.append(path('admin/', DashboardView.as_view(), name="dashboard"))
|
||||
URLPATTERNS.append(path('admin/oops/', BackendNotImplementedView.as_view(), name="backendni"))
|
||||
|
||||
# Backend Database Settings URLs
|
||||
|
||||
URLPATTERNS.append(path("admin/dbsettings/", DBSettingsListView.as_view(), name="dbsettings"))
|
||||
URLPATTERNS.append(path("admin/dbsettings/<pk>/delete/", DBSettingsDeleteView.as_view(), name="dbsettings_delete"))
|
||||
URLPATTERNS.append(path("admin/dbsettings/<pk>/edit/", DBSettingsEditView.as_view(), name="dbsettings_edit"))
|
||||
URLPATTERNS.append(path("admin/dbsettings/create/", DBSettingsCreateView.as_view(), name="dbsettings_create"))
|
||||
|
||||
# Backend User Administration URLs
|
||||
|
||||
URLPATTERNS.append(path('admin/profiles/', AdminListView.as_view(), name="admins"))
|
||||
URLPATTERNS.append(path("admin/profiles/<pk>/delete/", AdminDeleteView.as_view(), name="admins_delete"))
|
||||
URLPATTERNS.append(path("admin/profiles/<pk>/edit/", AdminEditView.as_view(), name="admins_edit"))
|
||||
URLPATTERNS.append(path("admin/profiles/create/", AdminCreateView.as_view(), name="admins_create"))
|
||||
|
||||
# Brand Administration URLs
|
||||
|
||||
URLPATTERNS.append(path('admin/brands/', BrandListView.as_view(), name="brands"))
|
||||
URLPATTERNS.append(path("admin/brands/<pk>/delete/", BrandDeleteView.as_view(), name="brands_delete"))
|
||||
URLPATTERNS.append(path("admin/brands/<pk>/edit/", BrandEditView.as_view(), name="brands_edit"))
|
||||
URLPATTERNS.append(path("admin/brands/create/", BrandCreateView.as_view(), name="brands_create"))
|
||||
|
||||
# Rate Limit Administration URLs
|
||||
|
||||
URLPATTERNS.append(path('admin/firewall/', RateLimitListView.as_view(), name="ratelimits"))
|
||||
URLPATTERNS.append(path("admin/firewall/<pk>/delete/", RateLimitDeleteView.as_view(), name="ratelimits_delete"))
|
||||
URLPATTERNS.append(path("admin/firewall/<pk>/edit/", RateLimitEditView.as_view(), name="ratelimits_edit"))
|
||||
URLPATTERNS.append(path("admin/firewall/create/", RateLimitCreateView.as_view(), name="ratelimits_create"))
|
||||
|
||||
# External Module URLs
|
||||
|
||||
for module in settings.EXPEPHALON_MODULES:
|
||||
try:
|
||||
mou = importlib.import_module(f"{module}.urls")
|
||||
|
|
|
@ -17,9 +17,9 @@ navigations["backend_main"].add_section(dashboard_section)
|
|||
|
||||
clients_section = NavSection("Clients", "")
|
||||
|
||||
client_list_item = NavItem("List Clients", "fa-user-tag", "backendni")
|
||||
client_add_item = NavItem("Add Client", "fa-user-edit", "backendni")
|
||||
client_groups_item = NavItem("Client Groups", "fa-users", "backendni")
|
||||
client_list_item = NavItem("List Clients", "fa-user-tag", "clients")
|
||||
client_add_item = NavItem("Add Client", "fa-user-edit", "clients_create")
|
||||
client_groups_item = NavItem("Client Groups", "fa-users", "clientgroups")
|
||||
client_leads_item = NavItem("Leads", "fa-blender-phone", "backendni")
|
||||
|
||||
clients_section.add_item(client_list_item)
|
||||
|
@ -45,8 +45,8 @@ navigations["backend_main"].add_section(quotes_section)
|
|||
|
||||
billing_section = NavSection("Billing", "")
|
||||
|
||||
invoice_list_item = NavItem("List Invoices", "fa-file-invoice-dollar", "backendni")
|
||||
invoice_create_item = NavItem("Create Invoice", "fa-plus-square", "backendni")
|
||||
invoice_list_item = NavItem("List Invoices", "fa-file-invoice-dollar", "invoices")
|
||||
invoice_create_item = NavItem("Create Invoice", "fa-plus-square", "invoices_create")
|
||||
billable_list_item = NavItem("List Billable Items", "fa-hand-holding-usd", "backendni")
|
||||
billable_create_item = NavItem("Create Billable Item", "fa-plus-square", "backendni")
|
||||
list_transaction_item = NavItem("Transaction List", "fa-funnel-dollar", "backendni")
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from core.modules.urls import URLPATTERNS as urlpatterns
|
9
core/urls/__init__.py
Normal file
9
core/urls/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from core.modules.urls import URLPATTERNS as modulepatterns
|
||||
from core.urls.auth import urlpatterns as authpatterns
|
||||
from core.urls.backend import urlpatterns as backendpatterns
|
||||
|
||||
from django.urls import path
|
||||
|
||||
corepatterns = authpatterns + backendpatterns
|
||||
|
||||
urlpatterns = corepatterns + modulepatterns
|
13
core/urls/auth.py
Normal file
13
core/urls/auth.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.auth import LoginView, OTPSelectorView, LogoutView, OTPValidatorView, PWResetView, PWRequestView, RateLimitedView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('login/', LoginView.as_view(), name="login"))
|
||||
urlpatterns.append(path('login/otp/select/', OTPSelectorView.as_view(), name="otpselector"))
|
||||
urlpatterns.append(path('login/otp/validate/', OTPValidatorView.as_view(), name="otpvalidator"))
|
||||
urlpatterns.append(path('logout/', LogoutView.as_view(), name="logout"))
|
||||
urlpatterns.append(path('login/reset/', PWRequestView.as_view(), name="pwrequest"))
|
||||
urlpatterns.append(path('login/reset/<pk>/', PWResetView.as_view(), name="pwreset"))
|
||||
urlpatterns.append(path('login/ratelimit/', RateLimitedView.as_view(), name="ratelimited"))
|
15
core/urls/backend/__init__.py
Normal file
15
core/urls/backend/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend import DashboardView, BackendNotImplementedView
|
||||
from core.urls.backend.admins import urlpatterns as adminpatterns
|
||||
from core.urls.backend.clients import urlpatterns as clientpatterns
|
||||
from core.urls.backend.dbsettings import urlpatterns as dbsettingspatterns
|
||||
from core.urls.backend.brands import urlpatterns as brandpatterns
|
||||
from core.urls.backend.firewall import urlpatterns as firewallpatterns
|
||||
from core.urls.backend.invoices import urlpatterns as invoicepatterns
|
||||
from core.urls.backend.clientgroups import urlpatterns as clientgrouppatterns
|
||||
|
||||
urlpatterns = adminpatterns + clientpatterns + dbsettingspatterns + brandpatterns + firewallpatterns + invoicepatterns + clientgrouppatterns
|
||||
|
||||
urlpatterns.append(path('admin/', DashboardView.as_view(), name="dashboard"))
|
||||
urlpatterns.append(path('admin/oops/', BackendNotImplementedView.as_view(), name="backendni"))
|
10
core/urls/backend/admins.py
Normal file
10
core/urls/backend/admins.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.profiles import AdminListView, AdminDeleteView, AdminEditView, AdminCreateView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('admin/profiles/', AdminListView.as_view(), name="admins"))
|
||||
urlpatterns.append(path("admin/profiles/<pk>/delete/", AdminDeleteView.as_view(), name="admins_delete"))
|
||||
urlpatterns.append(path("admin/profiles/<pk>/edit/", AdminEditView.as_view(), name="admins_edit"))
|
||||
urlpatterns.append(path("admin/profiles/create/", AdminCreateView.as_view(), name="admins_create"))
|
10
core/urls/backend/brands.py
Normal file
10
core/urls/backend/brands.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.brands import BrandCreateView, BrandDeleteView, BrandEditView, BrandListView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('admin/brands/', BrandListView.as_view(), name="brands"))
|
||||
urlpatterns.append(path("admin/brands/<pk>/delete/", BrandDeleteView.as_view(), name="brands_delete"))
|
||||
urlpatterns.append(path("admin/brands/<pk>/edit/", BrandEditView.as_view(), name="brands_edit"))
|
||||
urlpatterns.append(path("admin/brands/create/", BrandCreateView.as_view(), name="brands_create"))
|
10
core/urls/backend/clientgroups.py
Normal file
10
core/urls/backend/clientgroups.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.profiles import ClientGroupListView, ClientGroupDeleteView, ClientGroupEditView, ClientGroupCreateView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('admin/clientgroups/', ClientGroupListView.as_view(), name="clientgroups"))
|
||||
urlpatterns.append(path("admin/clientgroups/<pk>/delete/", ClientGroupDeleteView.as_view(), name="clientgroups_delete"))
|
||||
urlpatterns.append(path("admin/clientgroups/<pk>/edit/", ClientGroupEditView.as_view(), name="clientgroups_edit"))
|
||||
urlpatterns.append(path("admin/clientgroups/create/", ClientGroupCreateView.as_view(), name="clientgroups_create"))
|
10
core/urls/backend/clients.py
Normal file
10
core/urls/backend/clients.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.profiles import ClientListView, ClientDeleteView, ClientEditView, ClientCreateView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('admin/clients/', ClientListView.as_view(), name="clients"))
|
||||
urlpatterns.append(path("admin/clients/<pk>/delete/", ClientDeleteView.as_view(), name="clients_delete"))
|
||||
urlpatterns.append(path("admin/clients/<pk>/edit/", ClientEditView.as_view(), name="clients_edit"))
|
||||
urlpatterns.append(path("admin/clients/create/", ClientCreateView.as_view(), name="clients_create"))
|
10
core/urls/backend/dbsettings.py
Normal file
10
core/urls/backend/dbsettings.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.dbsettings import DBSettingsListView, DBSettingsEditView, DBSettingsDeleteView, DBSettingsCreateView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path("admin/dbsettings/", DBSettingsListView.as_view(), name="dbsettings"))
|
||||
urlpatterns.append(path("admin/dbsettings/<pk>/delete/", DBSettingsDeleteView.as_view(), name="dbsettings_delete"))
|
||||
urlpatterns.append(path("admin/dbsettings/<pk>/edit/", DBSettingsEditView.as_view(), name="dbsettings_edit"))
|
||||
urlpatterns.append(path("admin/dbsettings/create/", DBSettingsCreateView.as_view(), name="dbsettings_create"))
|
10
core/urls/backend/firewall.py
Normal file
10
core/urls/backend/firewall.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.firewall import RateLimitCreateView, RateLimitDeleteView, RateLimitEditView, RateLimitListView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('admin/firewall/', RateLimitListView.as_view(), name="ratelimits"))
|
||||
urlpatterns.append(path("admin/firewall/<pk>/delete/", RateLimitDeleteView.as_view(), name="ratelimits_delete"))
|
||||
urlpatterns.append(path("admin/firewall/<pk>/edit/", RateLimitEditView.as_view(), name="ratelimits_edit"))
|
||||
urlpatterns.append(path("admin/firewall/create/", RateLimitCreateView.as_view(), name="ratelimits_create"))
|
10
core/urls/backend/invoices.py
Normal file
10
core/urls/backend/invoices.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from core.views.backend.invoices import InvoiceCreateView, InvoiceDeleteView, InvoiceEditView, InvoiceListView
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.append(path('admin/invoices/', InvoiceListView.as_view(), name="invoices"))
|
||||
urlpatterns.append(path("admin/invoices/<pk>/delete/", InvoiceDeleteView.as_view(), name="invoices_delete"))
|
||||
urlpatterns.append(path("admin/invoices/<pk>/edit/", InvoiceEditView.as_view(), name="invoices_edit"))
|
||||
urlpatterns.append(path("admin/invoices/create/", InvoiceCreateView.as_view(), name="invoices_create"))
|
|
@ -1,16 +1,4 @@
|
|||
from django.shortcuts import render
|
||||
from django.views.generic import TemplateView
|
||||
from django.conf import settings
|
||||
|
||||
from core.views.dbsettings import *
|
||||
from core.views.auth import *
|
||||
from core.views.profiles import *
|
||||
from core.views.generic import *
|
||||
from core.views.brands import *
|
||||
from core.views.firewall import *
|
||||
from core.mixins.auth import AdminMixin
|
||||
|
||||
# Create your views here.
|
||||
from core.views.backend import *
|
||||
|
||||
class IndexView(TemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_FRONTEND}/index.html"
|
||||
|
@ -19,19 +7,3 @@ class IndexView(TemplateView):
|
|||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Home"
|
||||
return context
|
||||
|
||||
class DashboardView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Dashboard"
|
||||
return context
|
||||
|
||||
class BackendNotImplementedView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/notimplemented.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Oops!"
|
||||
return context
|
||||
|
|
|
@ -24,7 +24,7 @@ class RateLimitedView(TemplateView):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
limit = IPLimit.objects.filter(ip=get_client_ip(request), end__gte=timezone.now()).first()
|
||||
messages.error(request, f"Sorry, your IP has been blocked, so you cannot login at the moment.{f' Reason: {limit.reason}' if limit.reason else ''} Please try again after {str(iplimit.end)}, or contact support if you need help getting into your account.")
|
||||
messages.error(request, f"Sorry, your IP has been blocked, so you cannot login at the moment.{f' Reason: {limit.reason}' if limit.reason else ''} Please try again after {str(limit.end)}, or contact support if you need help getting into your account.")
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
except:
|
||||
return redirect("login")
|
||||
|
@ -38,7 +38,8 @@ class AuthView(FormView):
|
|||
period = timezone.now() - timezone.timedelta(seconds=int(getValue("core.auth.ratelimit.period", 600)))
|
||||
failures = LoginLog.objects.filter(ip=get_client_ip(request), success=False, timestamp__gte=period)
|
||||
if len(failures) >= int(getValue("core.auth.ratelimit.attempts", 5)):
|
||||
IPLimit.objects.create(ip=get_client_ip(request), end=timezone.now() + timezone.timedelta(seconds=getValue("core.auth.ratelimit.block", 3600)), reason="Too many failed login attempts.")
|
||||
IPLimit.objects.create(ip=get_client_ip(request), end=timezone.now() + timezone.timedelta(seconds=int(getValue("core.auth.ratelimit.block", 3600))), reason="Too many failed login attempts.")
|
||||
failures.delete()
|
||||
return redirect("ratelimited")
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
|
27
core/views/backend/__init__.py
Normal file
27
core/views/backend/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from django.shortcuts import render
|
||||
from django.views.generic import TemplateView
|
||||
from django.conf import settings
|
||||
|
||||
from core.views.backend.dbsettings import *
|
||||
from core.views.auth import *
|
||||
from core.views.backend.profiles import *
|
||||
from core.views.generic import *
|
||||
from core.views.backend.brands import *
|
||||
from core.views.backend.firewall import *
|
||||
from core.views.backend.invoices import *
|
||||
|
||||
class DashboardView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Dashboard"
|
||||
return context
|
||||
|
||||
class BackendNotImplementedView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/notimplemented.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Oops!"
|
||||
return context
|
|
@ -38,7 +38,7 @@ class RateLimitCreateView(BackendCreateView):
|
|||
template_name = f"{settings.EXPEPHALON_BACKEND}/firewall/create.html"
|
||||
model = IPLimit
|
||||
success_url = reverse_lazy("ratelimits")
|
||||
fields = ["name", "logo", "address1", "address2", "zip", "city", "state", "country", "vat_id", "company_id"]
|
||||
fields = ["ip", "end", "reason"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
46
core/views/backend/invoices.py
Normal file
46
core/views/backend/invoices.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from core.models.invoices import Invoice, InvoiceItem
|
||||
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
|
||||
class InvoiceListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/invoices/index.html"
|
||||
model = Invoice
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Brand Settings"
|
||||
return context
|
||||
|
||||
class InvoiceEditView(BackendUpdateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/invoices/update.html"
|
||||
model = Invoice
|
||||
success_url = reverse_lazy("brands")
|
||||
fields = ["name", "logo", "address1", "address2", "zip", "city", "state", "country", "vat_id", "company_id"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Edit Brand"
|
||||
return context
|
||||
|
||||
class InvoiceDeleteView(BackendDeleteView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/invoices/delete.html"
|
||||
model = Invoice
|
||||
success_url = reverse_lazy("brands")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Delete Brand"
|
||||
return context
|
||||
|
||||
class InvoiceCreateView(BackendCreateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/invoices/create.html"
|
||||
model = Invoice
|
||||
success_url = reverse_lazy("brands")
|
||||
fields = ["name", "logo", "address1", "address2", "zip", "city", "state", "country", "vat_id", "company_id"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Create Brand"
|
||||
return context
|
238
core/views/backend/profiles.py
Normal file
238
core/views/backend/profiles.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib import messages
|
||||
|
||||
from core.models import AdminProfile, ClientProfile, ClientGroup
|
||||
from core.forms.profiles import AdminEditForm, AdminCreateForm, ClientForm
|
||||
from core.views.generic import BackendFormView, BackendListView, BackendDeleteView, BackendUpdateView, BackendCreateView
|
||||
from core.helpers.auth import request_password
|
||||
|
||||
### AdminProfiles
|
||||
|
||||
class AdminListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/index.html"
|
||||
model = AdminProfile
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Administrator Users"
|
||||
return context
|
||||
|
||||
class AdminEditView(BackendFormView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/update.html"
|
||||
form_class = AdminEditForm
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Edit Administrator"
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
admin = get_user_model().objects.get(id=self.kwargs["pk"])
|
||||
assert type(admin.profile) == AdminProfile
|
||||
initial["first_name"] = admin.first_name
|
||||
initial["last_name"] = admin.last_name
|
||||
initial["email"] = admin.username
|
||||
initial["mobile"] = admin.profile.mobile
|
||||
initial["role"] = admin.profile.role
|
||||
initial["display_name"] = admin.profile.display_name
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
admin = get_user_model().objects.get(id=self.kwargs["pk"])
|
||||
admin.first_name = form.cleaned_data["first_name"]
|
||||
admin.last_name = form.cleaned_data["last_name"]
|
||||
admin.username = form.cleaned_data["email"]
|
||||
admin.email = form.cleaned_data["email"]
|
||||
admin.profile.mobile = form.cleaned_data["mobile"]
|
||||
admin.profile.role = form.cleaned_data["role"]
|
||||
admin.profile.display_name = form.cleaned_data["display_name"]
|
||||
|
||||
if form.cleaned_data["image"] or form.cleaned_data["remove_image"]:
|
||||
admin.profile.image = form.cleaned_data["image"]
|
||||
|
||||
admin.profile.save()
|
||||
admin.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
class AdminDeleteView(BackendDeleteView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/delete.html"
|
||||
model = get_user_model()
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Delete Administrator"
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
admin = super().get_object(queryset=queryset)
|
||||
assert type(admin.profile) == AdminProfile
|
||||
return admin
|
||||
|
||||
class AdminCreateView(BackendFormView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/create.html"
|
||||
form_class = AdminCreateForm
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Create Administrator"
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
admin = get_user_model()()
|
||||
admin.first_name = form.cleaned_data["first_name"]
|
||||
admin.last_name = form.cleaned_data["last_name"]
|
||||
admin.username = form.cleaned_data["email"]
|
||||
admin.email = form.cleaned_data["email"]
|
||||
|
||||
profile = AdminProfile()
|
||||
profile.user = admin
|
||||
profile.mobile = form.cleaned_data["mobile"]
|
||||
profile.role = form.cleaned_data["role"]
|
||||
profile.display_name = form.cleaned_data["display_name"]
|
||||
profile.image = form.cleaned_data["image"]
|
||||
|
||||
admin.save()
|
||||
profile.save()
|
||||
|
||||
request_password(admin)
|
||||
|
||||
messages.success(self.request, f"User {admin.get_full_name} was successfully created. They should receive an email to set their password shortly.")
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
### ClientProfiles
|
||||
|
||||
class ClientListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clients/index.html"
|
||||
model = ClientProfile
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Clients"
|
||||
return context
|
||||
|
||||
class ClientEditView(BackendFormView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clients/update.html"
|
||||
form_class = ClientForm
|
||||
success_url = reverse_lazy("clients")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Edit Client"
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
client = get_user_model().objects.get(id=self.kwargs["pk"])
|
||||
assert type(client.profile) == ClientProfile
|
||||
initial["first_name"] = client.first_name
|
||||
initial["last_name"] = client.last_name
|
||||
initial["email"] = client.username
|
||||
initial["mobile"] = client.profile.mobile
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
client = get_user_model().objects.get(id=self.kwargs["pk"])
|
||||
client.first_name = form.cleaned_data["first_name"]
|
||||
client.last_name = form.cleaned_data["last_name"]
|
||||
client.username = form.cleaned_data["email"]
|
||||
client.email = form.cleaned_data["email"]
|
||||
client.profile.mobile = form.cleaned_data["mobile"]
|
||||
|
||||
client.profile.save()
|
||||
client.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
class ClientDeleteView(BackendDeleteView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clients/delete.html"
|
||||
model = get_user_model()
|
||||
success_url = reverse_lazy("clients")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Delete Client"
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
admin = super().get_object(queryset=queryset)
|
||||
assert type(admin.profile) == ClientProfile
|
||||
return admin
|
||||
|
||||
class ClientCreateView(BackendFormView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clients/create.html"
|
||||
form_class = ClientForm
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Create Client"
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
client = get_user_model()()
|
||||
client.first_name = form.cleaned_data["first_name"]
|
||||
client.last_name = form.cleaned_data["last_name"]
|
||||
client.username = form.cleaned_data["email"]
|
||||
client.email = form.cleaned_data["email"]
|
||||
|
||||
profile = ClientProfile()
|
||||
profile.user = client
|
||||
profile.mobile = form.cleaned_data["mobile"]
|
||||
|
||||
client.save()
|
||||
profile.save()
|
||||
|
||||
request_password(client)
|
||||
|
||||
messages.success(self.request, f"Client {client.get_full_name} was successfully created. They should receive an email to set their password shortly.")
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
### ClientGroups
|
||||
|
||||
class ClientGroupListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clientgroups/index.html"
|
||||
model = ClientGroup
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Client Group Settings"
|
||||
return context
|
||||
|
||||
class ClientGroupEditView(BackendUpdateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clientgroups/update.html"
|
||||
model = ClientGroup
|
||||
success_url = reverse_lazy("clientgroups")
|
||||
fields = ["name", "color"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Edit Client Group"
|
||||
return context
|
||||
|
||||
class ClientGroupDeleteView(BackendDeleteView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clientgroups/delete.html"
|
||||
model = ClientGroup
|
||||
success_url = reverse_lazy("clientgroups")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Delete Client Group"
|
||||
return context
|
||||
|
||||
class ClientGroupCreateView(BackendCreateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/clientgroups/create.html"
|
||||
model = ClientGroup
|
||||
success_url = reverse_lazy("clientgroups")
|
||||
fields = ["name", "color"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Create Client Group"
|
||||
return context
|
|
@ -1,105 +0,0 @@
|
|||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib import messages
|
||||
|
||||
from core.models import AdminProfile
|
||||
from core.forms.profiles import AdminEditForm, AdminCreateForm
|
||||
from core.views.generic import BackendFormView, BackendListView, BackendDeleteView
|
||||
from core.helpers.auth import request_password
|
||||
|
||||
class AdminListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/index.html"
|
||||
model = AdminProfile
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Administrator Users"
|
||||
return context
|
||||
|
||||
class AdminEditView(BackendFormView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/update.html"
|
||||
form_class = AdminEditForm
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Edit Administrator"
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
admin = get_user_model().objects.get(id=self.kwargs["pk"])
|
||||
assert type(admin.profile) == AdminProfile
|
||||
initial["first_name"] = admin.first_name
|
||||
initial["last_name"] = admin.last_name
|
||||
initial["email"] = admin.username
|
||||
initial["mobile"] = admin.profile.mobile
|
||||
initial["role"] = admin.profile.role
|
||||
initial["display_name"] = admin.profile.display_name
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
admin = get_user_model().objects.get(id=self.kwargs["pk"])
|
||||
admin.first_name = form.cleaned_data["first_name"]
|
||||
admin.last_name = form.cleaned_data["last_name"]
|
||||
admin.username = form.cleaned_data["email"]
|
||||
admin.email = form.cleaned_data["email"]
|
||||
admin.profile.mobile = form.cleaned_data["mobile"]
|
||||
admin.profile.role = form.cleaned_data["role"]
|
||||
admin.profile.display_name = form.cleaned_data["display_name"]
|
||||
|
||||
if form.cleaned_data["image"] or form.cleaned_data["remove_image"]:
|
||||
admin.profile.image = form.cleaned_data["image"]
|
||||
|
||||
admin.profile.save()
|
||||
admin.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
class AdminDeleteView(BackendDeleteView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/delete.html"
|
||||
model = get_user_model()
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Delete Administrator"
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
admin = super().get_object(queryset=queryset)
|
||||
assert type(admin.profile) == AdminProfile
|
||||
return admin
|
||||
|
||||
class AdminCreateView(BackendFormView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/create.html"
|
||||
form_class = AdminCreateForm
|
||||
success_url = reverse_lazy("admins")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Create Administrator"
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
admin = get_user_model()()
|
||||
admin.first_name = form.cleaned_data["first_name"]
|
||||
admin.last_name = form.cleaned_data["last_name"]
|
||||
admin.username = form.cleaned_data["email"]
|
||||
admin.email = form.cleaned_data["email"]
|
||||
|
||||
profile = AdminProfile()
|
||||
profile.user = admin
|
||||
profile.mobile = form.cleaned_data["mobile"]
|
||||
profile.role = form.cleaned_data["role"]
|
||||
profile.display_name = form.cleaned_data["display_name"]
|
||||
profile.image = form.cleaned_data["image"]
|
||||
|
||||
admin.save()
|
||||
profile.save()
|
||||
|
||||
request_password(admin)
|
||||
|
||||
messages.success(self.request, f"User {admin.get_full_name} was successfully created. They should receive an email to set their password shortly.")
|
||||
|
||||
return super().form_valid(form)
|
0
core/widgets/__init__.py
Normal file
0
core/widgets/__init__.py
Normal file
19
core/widgets/color.py
Normal file
19
core/widgets/color.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from django.forms import TextInput
|
||||
from django.conf import settings
|
||||
from django.templatetags.static import static
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
class ColorPickerWidget(TextInput):
|
||||
class Media:
|
||||
css = {
|
||||
"all": (
|
||||
static("backend/css/colorPicker.css")
|
||||
)
|
||||
}
|
||||
js = (
|
||||
static("backend/scripts/jquery.colorPicker.js")
|
||||
)
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
rendered = super().render(name, value, attrs=attrs, renderer=renderer)
|
||||
return rendered + mark_safe(f'<script type="text/javascript">$("#id_{name}").colorPicker();</script>')
|
34
static/backend/css/colorPicker.css
Normal file
34
static/backend/css/colorPicker.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
div.colorPicker-picker {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
padding: 0 !important;
|
||||
border: 1px solid #ccc;
|
||||
background: url(../arrow.gif) no-repeat top right;
|
||||
cursor: pointer;
|
||||
line-height: 16px;
|
||||
font-size:0.75em;
|
||||
font-weight:bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.colorPicker-palette {
|
||||
width: 110px;
|
||||
position: absolute;
|
||||
border: 1px solid #598FEF;
|
||||
background-color: #EFEFEF;
|
||||
padding: 2px;
|
||||
z-index: 9999;
|
||||
}
|
||||
div.colorPicker_hexWrap {width: 100%; float:left }
|
||||
div.colorPicker_hexWrap label {font-size: 95%; color: #2F2F2F; margin: 5px 2px; width: 25%}
|
||||
div.colorPicker_hexWrap input {margin: 5px 2px; padding: 0; font-size: 95%; border: 1px solid #000; width: 65%; }
|
||||
|
||||
div.colorPicker-swatch {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border: 1px solid #000;
|
||||
margin: 2px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
line-height: 12px;
|
||||
}
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
352
static/backend/scripts/jquery.colorPicker.js
Normal file
352
static/backend/scripts/jquery.colorPicker.js
Normal file
|
@ -0,0 +1,352 @@
|
|||
/**
|
||||
* Really Simple Color Picker in jQuery
|
||||
*
|
||||
* Licensed under the MIT (MIT-LICENSE.txt) licenses.
|
||||
*
|
||||
* Copyright (c) 2008-2012
|
||||
* Lakshan Perera (www.laktek.com) & Daniel Lacy (daniellacy.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
/**
|
||||
* Create a couple private variables.
|
||||
**/
|
||||
var selectorOwner,
|
||||
activePalette,
|
||||
cItterate = 0,
|
||||
templates = {
|
||||
control : $('<div class="colorPicker-picker"> </div>'),
|
||||
palette : $('<div id="colorPicker_palette" class="colorPicker-palette" />'),
|
||||
swatch : $('<div class="colorPicker-swatch"> </div>'),
|
||||
hexLabel: $('<label for="colorPicker_hex">Hex</label>'),
|
||||
hexField: $('<input type="text" id="colorPicker_hex" />')
|
||||
},
|
||||
transparent = "transparent",
|
||||
lastColor;
|
||||
|
||||
/**
|
||||
* Create our colorPicker function
|
||||
**/
|
||||
$.fn.colorPicker = function (options) {
|
||||
|
||||
return this.each(function () {
|
||||
// Setup time. Clone new elements from our templates, set some IDs, make shortcuts, jazzercise.
|
||||
var element = $(this),
|
||||
opts = $.extend({}, $.fn.colorPicker.defaults, options),
|
||||
defaultColor = $.fn.colorPicker.toHex(
|
||||
(element.val().length > 0) ? element.val() : opts.pickerDefault
|
||||
),
|
||||
newControl = templates.control.clone(),
|
||||
newPalette = templates.palette.clone().attr('id', 'colorPicker_palette-' + cItterate),
|
||||
newHexLabel = templates.hexLabel.clone(),
|
||||
newHexField = templates.hexField.clone(),
|
||||
paletteId = newPalette[0].id,
|
||||
swatch, controlText;
|
||||
|
||||
|
||||
/**
|
||||
* Build a color palette.
|
||||
**/
|
||||
$.each(opts.colors, function (i) {
|
||||
swatch = templates.swatch.clone();
|
||||
|
||||
if (opts.colors[i] === transparent) {
|
||||
swatch.addClass(transparent).text('X');
|
||||
$.fn.colorPicker.bindPalette(newHexField, swatch, transparent);
|
||||
} else {
|
||||
swatch.css("background-color", "#" + this);
|
||||
$.fn.colorPicker.bindPalette(newHexField, swatch);
|
||||
}
|
||||
swatch.appendTo(newPalette);
|
||||
});
|
||||
|
||||
|
||||
newHexLabel.attr('for', 'colorPicker_hex-' + cItterate);
|
||||
|
||||
newHexField.attr({
|
||||
'id' : 'colorPicker_hex-' + cItterate,
|
||||
'value' : defaultColor
|
||||
});
|
||||
|
||||
newHexField.bind("keydown", function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
var hexColor = $.fn.colorPicker.toHex($(this).val());
|
||||
$.fn.colorPicker.changeColor(hexColor ? hexColor : element.val());
|
||||
}
|
||||
if (event.keyCode === 27) {
|
||||
$.fn.colorPicker.hidePalette();
|
||||
}
|
||||
});
|
||||
|
||||
newHexField.bind("keyup", function (event) {
|
||||
var hexColor = $.fn.colorPicker.toHex($(event.target).val());
|
||||
$.fn.colorPicker.previewColor(hexColor ? hexColor : element.val());
|
||||
});
|
||||
|
||||
$('<div class="colorPicker_hexWrap" />').append(newHexLabel).appendTo(newPalette);
|
||||
|
||||
newPalette.find('.colorPicker_hexWrap').append(newHexField);
|
||||
if (opts.showHexField === false) {
|
||||
newHexField.hide();
|
||||
newHexLabel.hide();
|
||||
}
|
||||
|
||||
$("body").append(newPalette);
|
||||
|
||||
newPalette.hide();
|
||||
|
||||
|
||||
/**
|
||||
* Build replacement interface for original color input.
|
||||
**/
|
||||
newControl.css("background-color", defaultColor);
|
||||
|
||||
newControl.bind("click", function () {
|
||||
if( element.is( ':not(:disabled)' ) ) {
|
||||
$.fn.colorPicker.togglePalette($('#' + paletteId), $(this));
|
||||
}
|
||||
});
|
||||
|
||||
if( options && options.onColorChange ) {
|
||||
newControl.data('onColorChange', options.onColorChange);
|
||||
} else {
|
||||
newControl.data('onColorChange', function() {} );
|
||||
}
|
||||
|
||||
if (controlText = element.data('text'))
|
||||
newControl.html(controlText)
|
||||
|
||||
element.after(newControl);
|
||||
|
||||
element.bind("change", function () {
|
||||
element.next(".colorPicker-picker").css(
|
||||
"background-color", $.fn.colorPicker.toHex($(this).val())
|
||||
);
|
||||
});
|
||||
|
||||
element.val(defaultColor);
|
||||
|
||||
// Hide the original input.
|
||||
if (element[0].tagName.toLowerCase() === 'input') {
|
||||
try {
|
||||
element.attr('type', 'hidden')
|
||||
} catch(err) { // oldIE doesn't allow changing of input.type
|
||||
element.css('visibility', 'hidden').css('position', 'absolute')
|
||||
}
|
||||
} else {
|
||||
element.hide();
|
||||
}
|
||||
|
||||
cItterate++;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Extend colorPicker with... all our functionality.
|
||||
**/
|
||||
$.extend(true, $.fn.colorPicker, {
|
||||
/**
|
||||
* Return a Hex color, convert an RGB value and return Hex, or return false.
|
||||
*
|
||||
* Inspired by http://code.google.com/p/jquery-color-utils
|
||||
**/
|
||||
toHex : function (color) {
|
||||
// If we have a standard or shorthand Hex color, return that value.
|
||||
if (color.match(/[0-9A-F]{6}|[0-9A-F]{3}$/i)) {
|
||||
return (color.charAt(0) === "#") ? color : ("#" + color);
|
||||
|
||||
// Alternatively, check for RGB color, then convert and return it as Hex.
|
||||
} else if (color.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/)) {
|
||||
var c = ([parseInt(RegExp.$1, 10), parseInt(RegExp.$2, 10), parseInt(RegExp.$3, 10)]),
|
||||
pad = function (str) {
|
||||
if (str.length < 2) {
|
||||
for (var i = 0, len = 2 - str.length; i < len; i++) {
|
||||
str = '0' + str;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
if (c.length === 3) {
|
||||
var r = pad(c[0].toString(16)),
|
||||
g = pad(c[1].toString(16)),
|
||||
b = pad(c[2].toString(16));
|
||||
|
||||
return '#' + r + g + b;
|
||||
}
|
||||
|
||||
// Otherwise we wont do anything.
|
||||
} else {
|
||||
return false;
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether user clicked on the selector or owner.
|
||||
**/
|
||||
checkMouse : function (event, paletteId) {
|
||||
var selector = activePalette,
|
||||
selectorParent = $(event.target).parents("#" + selector.attr('id')).length;
|
||||
|
||||
if (event.target === $(selector)[0] || event.target === selectorOwner[0] || selectorParent > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.fn.colorPicker.hidePalette();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the color palette modal.
|
||||
**/
|
||||
hidePalette : function () {
|
||||
$(document).unbind("mousedown", $.fn.colorPicker.checkMouse);
|
||||
|
||||
$('.colorPicker-palette').hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the color palette modal.
|
||||
**/
|
||||
showPalette : function (palette) {
|
||||
var hexColor = selectorOwner.prev("input").val();
|
||||
|
||||
palette.css({
|
||||
top: selectorOwner.offset().top + (selectorOwner.outerHeight()),
|
||||
left: selectorOwner.offset().left
|
||||
});
|
||||
|
||||
$("#color_value").val(hexColor);
|
||||
|
||||
palette.show();
|
||||
|
||||
$(document).bind("mousedown", $.fn.colorPicker.checkMouse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle visibility of the colorPicker palette.
|
||||
**/
|
||||
togglePalette : function (palette, origin) {
|
||||
// selectorOwner is the clicked .colorPicker-picker.
|
||||
if (origin) {
|
||||
selectorOwner = origin;
|
||||
}
|
||||
|
||||
activePalette = palette;
|
||||
|
||||
if (activePalette.is(':visible')) {
|
||||
$.fn.colorPicker.hidePalette();
|
||||
|
||||
} else {
|
||||
$.fn.colorPicker.showPalette(palette);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the input with a newly selected color.
|
||||
**/
|
||||
changeColor : function (value) {
|
||||
selectorOwner.css("background-color", value);
|
||||
selectorOwner.prev("input").val(value).change();
|
||||
|
||||
$.fn.colorPicker.hidePalette();
|
||||
|
||||
selectorOwner.data('onColorChange').call(selectorOwner, $(selectorOwner).prev("input").attr("id"), value);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Preview the input with a newly selected color.
|
||||
**/
|
||||
previewColor : function (value) {
|
||||
selectorOwner.css("background-color", value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind events to the color palette swatches.
|
||||
*/
|
||||
bindPalette : function (paletteInput, element, color) {
|
||||
color = color ? color : $.fn.colorPicker.toHex(element.css("background-color"));
|
||||
|
||||
element.bind({
|
||||
click : function (ev) {
|
||||
lastColor = color;
|
||||
|
||||
$.fn.colorPicker.changeColor(color);
|
||||
},
|
||||
mouseover : function (ev) {
|
||||
lastColor = paletteInput.val();
|
||||
|
||||
$(this).css("border-color", "#598FEF");
|
||||
|
||||
paletteInput.val(color);
|
||||
|
||||
$.fn.colorPicker.previewColor(color);
|
||||
},
|
||||
mouseout : function (ev) {
|
||||
$(this).css("border-color", "#000");
|
||||
|
||||
paletteInput.val(selectorOwner.css("background-color"));
|
||||
|
||||
paletteInput.val(lastColor);
|
||||
|
||||
$.fn.colorPicker.previewColor(lastColor);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Default colorPicker options.
|
||||
*
|
||||
* These are publibly available for global modification using a setting such as:
|
||||
*
|
||||
* $.fn.colorPicker.defaults.colors = ['151337', '111111']
|
||||
*
|
||||
* They can also be applied on a per-bound element basis like so:
|
||||
*
|
||||
* $('#element1').colorPicker({pickerDefault: 'efefef', transparency: true});
|
||||
* $('#element2').colorPicker({pickerDefault: '333333', colors: ['333333', '111111']});
|
||||
*
|
||||
**/
|
||||
$.fn.colorPicker.defaults = {
|
||||
// colorPicker default selected color.
|
||||
pickerDefault : "FFFFFF",
|
||||
|
||||
// Default color set.
|
||||
colors : [
|
||||
'000000', '993300', '333300', '000080', '333399', '333333', '800000', 'FF6600',
|
||||
'808000', '008000', '008080', '0000FF', '666699', '808080', 'FF0000', 'FF9900',
|
||||
'99CC00', '339966', '33CCCC', '3366FF', '800080', '999999', 'FF00FF', 'FFCC00',
|
||||
'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', 'FF99CC', 'FFCC99',
|
||||
'FFFF99', 'CCFFFF', '99CCFF', 'FFFFFF'
|
||||
],
|
||||
|
||||
// If we want to simply add more colors to the default set, use addColors.
|
||||
addColors : [],
|
||||
|
||||
// Show hex field
|
||||
showHexField: true
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -207,7 +207,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.png" %}" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ella-Rose Henry</div>
|
||||
|
@ -229,7 +229,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.png" %}" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ruben Tillman</div>
|
||||
|
@ -251,7 +251,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.png" %}" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Vinnie Wagstaff</div>
|
||||
|
@ -273,7 +273,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.png" %}" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ella-Rose Henry</div>
|
||||
|
@ -295,7 +295,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.png" %}" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ruben Tillman</div>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<a href="{% url "brands" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<a href="{% url "brands" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<a href="{% url "brands" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
|
@ -51,7 +51,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
57
templates/backend/clientgroups/create.html
Normal file
57
templates/backend/clientgroups/create.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Client Groups - Create Client Group
|
||||
<div class="page-title-subheading">Create a new client group
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Client Group" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client Group
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Create Client Group
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST" enctype="multipart/form-data" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "clientgroups" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
57
templates/backend/clientgroups/delete.html
Normal file
57
templates/backend/clientgroups/delete.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-database">
|
||||
</i>
|
||||
</div>
|
||||
<div>Client Groups - Delete Client Group
|
||||
<div class="page-title-subheading">Delete a client group from the system
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Brand" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client Group
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Deleting {{ object.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
Are you sure you wish to delete {{ object.name }}?
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "clientgroups" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
59
templates/backend/clientgroups/index.html
Normal file
59
templates/backend/clientgroups/index.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Client Groups
|
||||
<div class="page-title-subheading">Create, edit and delete client groups
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<a href="{% url "clientgroups_create" %}" type="button" data-toggle="tooltip" title="New Client Group" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client Group
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Active Client Groups
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<div class="card mb-3 widget-chart widget-chart2 text-left w-100">
|
||||
<table class="mb-0 table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in object_list %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
<td><a href="{% url "clientgroups_edit" group.id %}"><i class="fas fa-edit" title="Edit Client Group"></i></a> <a href="{% url "clientgroups_delete" group.id %}"><i style="color: darkred;" class="fas fa-trash-alt" title="Delete Client Group"></i></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
57
templates/backend/clientgroups/update.html
Normal file
57
templates/backend/clientgroups/update.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Client Groups - Edit Client Group
|
||||
<div class="page-title-subheading">Edit client group name
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Client Group" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client Group
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Editing {{ object.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST" enctype="multipart/form-data" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "clientgroups" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
57
templates/backend/clients/create.html
Normal file
57
templates/backend/clients/create.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Clients - Create Client
|
||||
<div class="page-title-subheading">Create a new client
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Client" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Create Client
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST" enctype="multipart/form-data" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "clients" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
57
templates/backend/clients/delete.html
Normal file
57
templates/backend/clients/delete.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-database">
|
||||
</i>
|
||||
</div>
|
||||
<div>Clients - Delete Client
|
||||
<div class="page-title-subheading">Delete a client from the system
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Brand" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Deleting {{ object.get_full_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
Are you sure you wish to delete {{ object.get_full_name }}? This will irrevocably delete the client and any associated information. You can also disable the client without deleting their data!
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "clients" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
61
templates/backend/clients/index.html
Normal file
61
templates/backend/clients/index.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Clients
|
||||
<div class="page-title-subheading">Create, edit and delete clients
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<a href="{% url "clients_create" %}" type="button" data-toggle="tooltip" title="New Client" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Active Clients
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<div class="card mb-3 widget-chart widget-chart2 text-left w-100">
|
||||
<table class="mb-0 table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Country</th>
|
||||
<th>Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for client in object_list %}
|
||||
<tr>
|
||||
<td>{% if client.profile.company %}{{ client.profile.company }}{% else %}{{ client.get_full_name }}{% endif %}</td>
|
||||
<td>{{ client.profile.country }}</td>
|
||||
<td><a href="{% url "clients_edit" client.id %}"><i class="fas fa-edit" title="Edit Client"></i></a> <a href="{% url "clients_delete" client.id %}"><i style="color: darkred;" class="fas fa-trash-alt" title="Delete Client"></i></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
57
templates/backend/clients/update.html
Normal file
57
templates/backend/clients/update.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Clients - Edit Client
|
||||
<div class="page-title-subheading">Edit client data
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Client" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Client
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Editing {{ object.get_full_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST" enctype="multipart/form-data" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "clients" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -14,7 +14,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Brand" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<button type="button" data-toggle="tooltip" title="New Rule" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Rule
|
||||
</button>
|
||||
|
||||
|
|
57
templates/backend/invoices/create.html
Normal file
57
templates/backend/invoices/create.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Invoices - Create Invoice
|
||||
<div class="page-title-subheading">Create a new invoice
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Invoice" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Invoice
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Create Invoice
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST" enctype="multipart/form-data" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
57
templates/backend/invoices/delete.html
Normal file
57
templates/backend/invoices/delete.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-database">
|
||||
</i>
|
||||
</div>
|
||||
<div>Invoices - Delete Invoice
|
||||
<div class="page-title-subheading">Delete an invoice from the system
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Rule" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Invoice
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Deleting invoice {{ object.number or object.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
Are you sure you wish to delete invoice {{ object.number or object.id }}?
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
69
templates/backend/invoices/index.html
Normal file
69
templates/backend/invoices/index.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-shield-alt">
|
||||
</i>
|
||||
</div>
|
||||
<div>Invoices
|
||||
<div class="page-title-subheading">Create, edit and delete invoices
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<a href="{% url "ratelimits_create" %}" type="button" data-toggle="tooltip" title="New Invoice" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Invoice
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Active Invoices
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<div class="card mb-3 widget-chart widget-chart2 text-left w-100">
|
||||
<table class="mb-0 table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Invoice Number</th>
|
||||
<th>Recipient</th>
|
||||
<th>Total (net)</th>
|
||||
<th>Total (gross)</th>
|
||||
<th>Status</th>
|
||||
<th>Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for invoice in object_list %}
|
||||
<tr>
|
||||
<td>{{ invoice.id }}</td>
|
||||
<td>{{ invoice.number }}</td>
|
||||
<td>{{ invoice.client.user.get_full_name }}</td>
|
||||
<td>{{ invoice.total_net }}</td>
|
||||
<td>{{ invoice.total_gross }}</td>
|
||||
<td>{{ invoice.status }}</td>
|
||||
<td><a href="{% url "invoices_edit" invoice.id %}"><i class="fas fa-edit" title="Edit Invoice"></i></a> <a href="{% url "invoices_delete" invoice.id %}"><i style="color: darkred;" class="fas fa-trash-alt" title="Delete Invoice"></i></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
57
templates/backend/invoices/update.html
Normal file
57
templates/backend/invoices/update.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
<div class="page-title-heading">
|
||||
<div class="page-title-icon">
|
||||
<i class="fa fa-users-cog">
|
||||
</i>
|
||||
</div>
|
||||
<div>Invoice - Edit Invoice
|
||||
<div class="page-title-subheading">Edit invoice
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-title-actions">
|
||||
<button type="button" data-toggle="tooltip" title="New Invoice" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-plus"></i> New Invoice
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-0">
|
||||
<div class="mb-3 card">
|
||||
<div class="card-header-tab card-header-tab-animation card-header">
|
||||
<div class="card-header-title">
|
||||
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||
Editing {{ object.number or object.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||
<form method="POST" enctype="multipart/form-data" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn-shadow mr-3 btn btn-success">
|
||||
<i class="fa fa-check"></i> Save
|
||||
</button>
|
||||
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in a new issue