Implement brands

More administration pages *whoop-whoop*
This commit is contained in:
Kumi 2020-05-31 14:08:26 +02:00
parent 43c110253c
commit 853a49abe8
21 changed files with 384 additions and 41 deletions

View file

@ -5,8 +5,6 @@ from django.contrib.auth import get_user_model
from phonenumber_field.formfields import PhoneNumberField
from core.models import AdminProfile
class AdminEditForm(ModelForm):
display_name = CharField(required=False, label=_('Internal Display Name'))
mobile = PhoneNumberField(required=False, label=_('Mobile Number'))
@ -16,4 +14,14 @@ class AdminEditForm(ModelForm):
class Meta:
model = get_user_model()
fields = ('first_name', 'last_name', "display_name", "email", 'mobile', "role", "image", "remove_image")
fields = ('first_name', 'last_name', "display_name", "email", 'mobile', "role", "image", "remove_image")
class AdminCreateForm(ModelForm):
display_name = CharField(required=False, label=_('Internal Display Name'))
mobile = PhoneNumberField(required=False, label=_('Mobile Number'))
role = CharField(required=False, label=_("Role"))
image = ImageField(required=False, label=_("Image"))
class Meta:
model = get_user_model()
fields = ('first_name', 'last_name', "display_name", "email", 'mobile', "role", "image")

View file

@ -1,6 +1,6 @@
from core.helpers.mail import get_template
from core.helpers.mail import get_template, simple_send_mail
from core.helpers.urls import relative_to_absolute as reltoabs
from core.models.auth import LoginLog
from core.models.auth import LoginLog, PWResetToken
from core.helpers.request import get_client_ip
from django.urls import reverse
@ -28,4 +28,9 @@ def clear_login_log(maxage=int(getValue("core.auth.ratelimit.period", 600))):
def clear_ratelimits(maxage=int(getValue("core.auth.ratelimit.block", 3600))):
timestamp = timezone.now() - timezone.timedelta(seconds=maxage)
LoginLog.objects.filter(timestamp__lt=timestamp).delete()
LoginLog.objects.filter(timestamp__lt=timestamp).delete()
def request_password(user):
token = PWResetToken.objects.create(user=user)
mail = generate_pwreset_mail(user, token)
simple_send_mail("Password Reset", mail, user.email)

View file

@ -1,4 +1,19 @@
from django.db.models import Model
from django.db.models import Model, ImageField
from core.fields.base import LongCharField
from core.helpers.files import generate_storage_filename
from internationalflavor.vat_number.models import VATNumberField
from django_countries.fields import CountryField
class Brand(Model):
pass
name = LongCharField(null=True, blank=True)
logo = ImageField(null=True, blank=True, upload_to=generate_storage_filename)
address1 = LongCharField()
address2 = LongCharField(null=True, blank=True)
zip = LongCharField()
city = LongCharField()
state = LongCharField(null=True, blank=True)
country = CountryField()
vat_id = VATNumberField(null=True, blank=True)
company_id = LongCharField(null=True, blank=True)

View file

@ -28,15 +28,15 @@ class AdminProfile(Profile):
return self.display_name if self.display_name else self.user.get_full_name
class ClientProfile(Profile):
company = LongCharField()
company = LongCharField(null=True, blank=True)
address1 = LongCharField()
address2 = LongCharField()
address2 = LongCharField(null=True, blank=True)
zip = LongCharField()
city = LongCharField()
state = LongCharField()
state = LongCharField(null=True, blank=True)
country = CountryField()
vat_id = VATNumberField()
company_id = LongCharField()
vat_id = VATNumberField(null=True, blank=True)
company_id = LongCharField(null=True, blank=True)
default_currency = ForeignKey(Currency, on_delete=SET_DEFAULT, default=Currency.get_base)
client_groups = ManyToManyField(ClientGroup)
brands = ManyToManyField(Brand)

View file

@ -21,6 +21,10 @@ from core.views import (
DBSettingsEditView,
DBSettingsDeleteView,
DBSettingsCreateView,
BrandCreateView,
BrandDeleteView,
BrandEditView,
BrandListView,
)
URLPATTERNS = []
@ -54,6 +58,13 @@ URLPATTERNS.append(path("admin/profiles/<pk>/delete/", AdminDeleteView.as_view()
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"))
# External Module URLs
for module in settings.EXPEPHALON_MODULES:

View file

@ -89,7 +89,7 @@ navigations["backend_main"].add_section(reports_section)
administration_section = NavSection("Administration", "")
user_administration_item = NavItem("Administrator Users", "fa-users-cog", "admins")
brand_administration_item = NavItem("Brands", "fa-code-branch", "backendni")
brand_administration_item = NavItem("Brands", "fa-code-branch", "brands")
sms_administration_item = NavItem("SMS Gateway", "fa-sms", "backendni")
otp_administration_item = NavItem("Two-Factor Authentication", "fa-id-badge", "backendni")
backup_administration_item = NavItem("Backups", "fa-shield-alt", "backendni")

View file

@ -6,6 +6,7 @@ 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.mixins.auth import AdminMixin
# Create your views here.

View file

@ -10,7 +10,7 @@ from core.forms import LoginForm, OTPSelectorForm, OTPVerificationForm, PWResetF
from core.models.auth import LoginSession, PWResetToken, IPLimit, LoginLog
from core.helpers.otp import get_user_otps, get_otp_choices, get_otp_by_name
from core.helpers.mail import simple_send_mail
from core.helpers.auth import generate_pwreset_mail, login_fail, login_success
from core.helpers.auth import generate_pwreset_mail, login_fail, login_success, request_password
from core.helpers.request import get_client_ip
from dbsettings.functions import getValue
@ -205,9 +205,7 @@ class PWRequestView(AuthView):
def form_valid(self, form):
try:
user = get_user_model().objects.get(username=form.cleaned_data["email"])
token = PWResetToken.objects.create(user=user)
mail = generate_pwreset_mail(user, token)
simple_send_mail("Password Reset", mail, user.email)
request_password(user)
finally:
messages.success(self.request, "If a matching account was found, you should shortly receive an email containing password reset instructions. If you have not received this message after five minutes, please verify that you have entered the correct email address, or contact support.")
return redirect("login")

46
core/views/brands.py Normal file
View file

@ -0,0 +1,46 @@
from django.conf import settings
from django.urls import reverse_lazy
from core.models.brands import Brand
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class BrandListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/brands/index.html"
model = Brand
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Brand Settings"
return context
class BrandEditView(BackendUpdateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/brands/update.html"
model = Brand
success_url = reverse_lazy("dbsettings")
fields = ["key", "value"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Edit Brand"
return context
class BrandDeleteView(BackendDeleteView):
template_name = f"{settings.EXPEPHALON_BACKEND}/brands/delete.html"
model = Brand
success_url = reverse_lazy("dbsettings")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Delete Brand"
return context
class BrandCreateView(BackendCreateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/create.html"
model = Brand
success_url = reverse_lazy("dbsettings")
fields = ["key", "value"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Create Brand"
return context

View file

@ -1,10 +1,10 @@
from django.conf import settings
from django.views.generic import ListView, UpdateView, DeleteView, CreateView
from django.urls import reverse_lazy
from dbsettings.models import Setting
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class DBSettingsListView(ListView):
class DBSettingsListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/index.html"
model = Setting
@ -13,7 +13,7 @@ class DBSettingsListView(ListView):
context["title"] = "Database Settings"
return context
class DBSettingsEditView(UpdateView):
class DBSettingsEditView(BackendUpdateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/update.html"
model = Setting
success_url = reverse_lazy("dbsettings")
@ -24,7 +24,7 @@ class DBSettingsEditView(UpdateView):
context["title"] = "Edit Setting"
return context
class DBSettingsDeleteView(DeleteView):
class DBSettingsDeleteView(BackendDeleteView):
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/delete.html"
model = Setting
success_url = reverse_lazy("dbsettings")
@ -34,7 +34,7 @@ class DBSettingsDeleteView(DeleteView):
context["title"] = "Delete Setting"
return context
class DBSettingsCreateView(CreateView):
class DBSettingsCreateView(BackendCreateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/create.html"
model = Setting
success_url = reverse_lazy("dbsettings")

View file

@ -1,4 +1,4 @@
from django.views.generic import TemplateView, ListView, CreateView, FormView, DeleteView
from django.views.generic import TemplateView, ListView, CreateView, FormView, DeleteView, UpdateView
from core.mixins.auth import AdminMixin
class BackendTemplateView(AdminMixin, TemplateView):
@ -14,4 +14,7 @@ class BackendFormView(AdminMixin, FormView):
pass
class BackendDeleteView(AdminMixin, DeleteView):
pass
class BackendUpdateView(AdminMixin, UpdateView):
pass

View file

@ -1,12 +1,14 @@
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 import AdminEditForm
from core.views.generic import BackendFormView as FormView, BackendListView as ListView, BackendDeleteView as DeleteView
from core.forms.profiles import AdminEditForm, AdminCreateForm
from core.views.generic import BackendFormView, BackendListView, BackendDeleteView
from core.helpers.auth import request_password
class AdminListView(ListView):
class AdminListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/index.html"
model = AdminProfile
@ -15,7 +17,7 @@ class AdminListView(ListView):
context["title"] = "Administrator Users"
return context
class AdminEditView(FormView):
class AdminEditView(BackendFormView):
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/update.html"
form_class = AdminEditForm
success_url = reverse_lazy("admins")
@ -54,7 +56,7 @@ class AdminEditView(FormView):
admin.save()
return super().form_valid(form)
class AdminDeleteView(DeleteView):
class AdminDeleteView(BackendDeleteView):
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/delete.html"
model = get_user_model()
success_url = reverse_lazy("admins")
@ -69,13 +71,35 @@ class AdminDeleteView(DeleteView):
assert type(admin.profile) == AdminProfile
return admin
class AdminCreateView(FormView):
class AdminCreateView(BackendFormView):
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/create.html"
model = get_user_model()
form_class = AdminCreateForm
success_url = reverse_lazy("admins")
fields = ["key", "value"]
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(f"User {admin.get_full_name} was successfully created. They should receive an email to set their password shortly.")
return super().form_valid(form)

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View file

@ -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="assets/images/avatars/9.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.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="assets/images/avatars/5.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.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="assets/images/avatars/4.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.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="assets/images/avatars/3.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.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="assets/images/avatars/2.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/pixel.png" %}" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">Ruben Tillman</div>

View 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>Brands - Create Brand
<div class="page-title-subheading">Create a new brand
</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 Brand
</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 Brand
</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 %}

View 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>Brands - Delete Brand
<div class="page-title-subheading">Delete a brand 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 Brand
</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 }}? This will irrevocably delete the brand and any associated information. You can also disable the brand without deleting its data!
{% 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 %}

View 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>Brands
<div class="page-title-subheading">Create, edit and delete brands
</div>
</div>
</div>
<div class="page-title-actions">
<a href="{% url "brands_create" %}" 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 Brand
</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 Brands
</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 brand in object_list %}
<tr>
<td>{{ brand.name }}</td>
<td>{{ brand.country }}</td>
<td><a href="{% url "brands_edit" brand.id %}"><i class="fas fa-edit" title="Edit Brand"></i></a> <a href="{% url "brands_delete" brand.id %}"><i style="color: darkred;" class="fas fa-trash-alt" title="Delete Brand"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View 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>Brands - Edit Brand
<div class="page-title-subheading">Edit brand properties
</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 Brand
</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 "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 %}

View file

@ -14,7 +14,7 @@
</div>
</div>
<div class="page-title-actions">
<button type="button" data-toggle="tooltip" title="New Setting" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
<button type="button" data-toggle="tooltip" title="New Administrator" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
<i class="fa fa-plus"></i> New Administrator
</button>
@ -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 "dbsettings" %}" class="btn-shadow mr-3 btn btn-danger">
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
<i class="fa fa-times"></i> Cancel
</a>
{% endbuttons %}

View file

@ -13,7 +13,7 @@
</div>
</div>
<div class="page-title-actions">
<a href="{% url "dbsettings_create" %}" type="button" data-toggle="tooltip" title="New Administrator" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
<a href="{% url "admins_create" %}" type="button" data-toggle="tooltip" title="New Administrator" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
<i class="fa fa-plus"></i> New Administrator
</a>

View file

@ -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 "dbsettings" %}" class="btn-shadow mr-3 btn btn-danger">
<a href="{% url "admins" %}" class="btn-shadow mr-3 btn btn-danger">
<i class="fa fa-times"></i> Cancel
</a>
{% endbuttons %}