Autosave: 20260203-185239

This commit is contained in:
Flatlogic Bot 2026-02-03 18:52:41 +00:00
parent 65bfca78e9
commit b9bad07848
93 changed files with 3283 additions and 257 deletions

View File

@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
from pathlib import Path
import os
from dotenv import load_dotenv
from django.utils.translation import gettext_lazy as _
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR.parent / ".env")
@ -49,6 +50,7 @@ CSRF_COOKIE_SAMESITE = "None"
# Application definition
INSTALLED_APPS = [
'jazzmin',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@ -61,6 +63,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware', # Added for i18n
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@ -83,6 +86,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n', # Added for i18n
# IMPORTANT: do not remove injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp
'core.context_processors.project_context',
],
@ -141,11 +145,19 @@ USE_I18N = True
USE_TZ = True
LANGUAGES = [
('en', _('English')),
('ar', _('Arabic')),
]
LOCALE_PATHS = [
BASE_DIR / 'locale',
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_URL = '/static/'
# Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS.
STATIC_ROOT = BASE_DIR / 'staticfiles'
@ -155,6 +167,9 @@ STATICFILES_DIRS = [
BASE_DIR / 'node_modules',
]
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Email
EMAIL_BACKEND = os.getenv(
"EMAIL_BACKEND",
@ -180,3 +195,107 @@ if EMAIL_USE_SSL:
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
JAZZMIN_SETTINGS = {
# title of the window (Will default to current_admin_site.site_title if absent or None)
"site_title": "School Admin",
# Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_header": "School",
# Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_brand": "School Admin",
# Logo to use for your site, must be present in static files, used for brand on top left
# "site_logo": "books/img/logo.png",
# CSS classes that are applied to the logo above
"site_logo_classes": "img-circle",
# Welcome text on the login screen
"welcome_sign": "Welcome to the School Admin",
# Copyright on the footer
"copyright": "School Ltd",
# List of model admins to search from the search bar, search defaults to all models
"search_model": ["core.Student", "core.Resource"],
# Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user
"user_avatar": None,
############
# Top Menu #
############
# Links to put along the top menu
"topmenu_links": [
# Url that gets reversed (Permissions can be added)
{"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]},
# external url that opens in a new window (Permissions can be added)
{"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True},
# model admin to link to (Permissions checked against model)
{"model": "core.Student"},
],
#############
# User Menu #
#############
# Additional links to include in the user menu on the top right ("app" url type is not allowed)
"usermenu_links": [
{"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True},
{"model": "core.Student"}
],
#############
# Side Menu #
#############
# Whether to display the side menu
"show_sidebar": True,
# Whether to aut expand the menu
"navigation_expanded": True,
# Hide these apps when generating side menu e.g (auth)
"hide_apps": [],
# Hide these models when generating side menu (e.g auth.user)
"hide_models": [],
# List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models)
"order_with_respect_to": ["core", "auth"],
# Custom icons for side menu apps/models See https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.1.0,5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.5.0,5.6.0,5.6.1,5.6.3,5.7.0,5.7.1,5.7.2,5.8.0,5.8.1,5.8.2,5.9.0
"icons": {
"auth": "fas fa-users-cog",
"auth.user": "fas fa-user",
"auth.Group": "fas fa-users",
"core.Student": "fas fa-user-graduate",
"core.Teacher": "fas fa-chalkboard-teacher",
"core.Subject": "fas fa-book",
"core.Resource": "fas fa-file-alt",
"core.EducationalLevel": "fas fa-layer-group",
},
# Icons that are used when one is not manually specified
"default_icon_parents": "fas fa-chevron-circle-right",
"default_icon_children": "fas fa-circle",
#################
# Related Modal #
#################
# Use modals instead of popups
"related_modal_active": False,
#############
# UI Tweaks #
#############
# Relative paths to custom CSS/JS scripts (must be present in static files)
"custom_css": None,
"custom_js": None,
# Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise)
"use_google_fonts_cdn": True,
# Whether to show the UI customizer on the sidebar
"show_ui_builder": True,
}

View File

@ -1,29 +1,13 @@
"""
URL configuration for config project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("core.urls")),
path('admin/', admin.site.urls),
path('', include('core.urls')),
]
if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -1,3 +1,58 @@
from django.contrib import admin
from django import forms
from .models import EducationalLevel, Teacher, Subject, Resource, Student
# Register your models here.
@admin.register(EducationalLevel)
class EducationalLevelAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar')
@admin.register(Teacher)
class TeacherAdmin(admin.ModelAdmin):
list_display = ('user', 'specialization')
@admin.register(Subject)
class SubjectAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'level', 'price', 'teacher')
list_filter = ('level', 'teacher')
search_fields = ('name_en', 'name_ar')
class ResourceAdminForm(forms.ModelForm):
educational_level = forms.ModelChoiceField(
queryset=EducationalLevel.objects.all(),
required=True,
label="Filter by Educational Level"
)
class Meta:
model = Resource
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.pk and self.instance.subject:
self.fields['educational_level'].initial = self.instance.subject.level
@admin.register(Resource)
class ResourceAdmin(admin.ModelAdmin):
form = ResourceAdminForm
list_display = ('title_en', 'subject', 'created_at')
list_filter = ('subject__level', 'subject')
fields = ('educational_level', 'subject', 'title_en', 'title_ar', 'file', 'link')
class Media:
js = ('js/admin_resource.js',)
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
list_display = ('user', 'level', 'phone_number')
list_filter = ('level',)
filter_horizontal = ('subscribed_subjects',)
class Media:
js = ('js/admin_student.js',)
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if obj and obj.level:
form.base_fields['subscribed_subjects'].queryset = Subject.objects.filter(level=obj.level)
return form

View File

Binary file not shown.

View File

View File

@ -0,0 +1,58 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from core.models import EducationalLevel, Subject, Teacher
class Command(BaseCommand):
help = 'Seeds the database with sample data'
def handle(self, *args, **kwargs):
# Create a teacher
user, created = User.objects.get_or_create(username='teacher1', email='teacher1@example.com')
if created:
user.set_password('password123')
user.save()
teacher, _ = Teacher.objects.get_or_create(user=user, specialization='Mathematics')
# Create Educational Levels
l1, _ = EducationalLevel.objects.get_or_create(
name_en='Primary School',
name_ar='المرحلة الابتدائية',
description='Grades 1-6'
)
l2, _ = EducationalLevel.objects.get_or_create(
name_en='High School',
name_ar='المرحلة الثانوية',
description='Grades 10-12'
)
# Create Subjects
Subject.objects.get_or_create(
level=l1,
teacher=teacher,
name_en='Basic Math',
name_ar='الرياضيات الأساسية',
description_en='Learn basic addition, subtraction, and multiplication.',
description_ar='تعلم الجمع والطرح والضرب الأساسي.',
price=20.00
)
Subject.objects.get_or_create(
level=l2,
teacher=teacher,
name_en='Physics',
name_ar='الفيزياء',
description_en='Advanced physics for high school students.',
description_ar='فيزياء متقدمة لطلاب الثانوية العامة.',
price=50.00
)
Subject.objects.get_or_create(
level=l2,
teacher=teacher,
name_en='Chemistry',
name_ar='الكيمياء',
description_en='Explore the world of chemical reactions.',
description_ar='استكشف عالم التفاعلات الكيميائية.',
price=45.00
)
self.stdout.write(self.style.SUCCESS('Successfully seeded sample data'))

View File

@ -0,0 +1,78 @@
# Generated by Django 5.2.7 on 2026-02-03 13:59
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='EducationalLevel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_en', models.CharField(max_length=100, verbose_name='Name (English)')),
('name_ar', models.CharField(max_length=100, verbose_name='Name (Arabic)')),
('description', models.TextField(blank=True, verbose_name='Description')),
],
),
migrations.CreateModel(
name='Subject',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_en', models.CharField(max_length=200, verbose_name='Name (English)')),
('name_ar', models.CharField(max_length=200, verbose_name='Name (Arabic)')),
('description_en', models.TextField(blank=True, verbose_name='Description (English)')),
('description_ar', models.TextField(blank=True, verbose_name='Description (Arabic)')),
('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='Price')),
('image', models.ImageField(blank=True, null=True, upload_to='subjects/', verbose_name='Image')),
('google_drive_link', models.URLField(blank=True, verbose_name='Google Drive Link')),
('google_meet_link', models.URLField(blank=True, verbose_name='Google Meet Link')),
('level', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subjects', to='core.educationallevel')),
],
),
migrations.CreateModel(
name='Student',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', models.CharField(blank=True, max_length=20, verbose_name='Phone Number')),
('level', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='students', to='core.educationallevel')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='student_profile', to=settings.AUTH_USER_MODEL)),
('subscribed_subjects', models.ManyToManyField(blank=True, related_name='subscribers', to='core.subject')),
],
),
migrations.CreateModel(
name='Resource',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title_en', models.CharField(max_length=200, verbose_name='Title (English)')),
('title_ar', models.CharField(max_length=200, verbose_name='Title (Arabic)')),
('file', models.FileField(blank=True, null=True, upload_to='resources/', verbose_name='File')),
('link', models.URLField(blank=True, verbose_name='External Link')),
('created_at', models.DateTimeField(auto_now_add=True)),
('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='core.subject')),
],
),
migrations.CreateModel(
name='Teacher',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bio', models.TextField(blank=True, verbose_name='Bio')),
('avatar', models.ImageField(blank=True, null=True, upload_to='teachers/', verbose_name='Avatar')),
('specialization', models.CharField(blank=True, max_length=255, verbose_name='Specialization')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teacher_profile', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='subject',
name='teacher',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subjects', to='core.teacher'),
),
]

View File

@ -1,3 +1,55 @@
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
# Create your models here.
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='teacher_profile')
bio = models.TextField(_("Bio"), blank=True)
avatar = models.ImageField(_("Avatar"), upload_to='teachers/', blank=True, null=True)
specialization = models.CharField(_("Specialization"), max_length=255, blank=True)
def __str__(self):
return self.user.get_full_name() or self.user.username
class EducationalLevel(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=100)
name_ar = models.CharField(_("Name (Arabic)"), max_length=100)
description = models.TextField(_("Description"), blank=True)
def __str__(self):
return self.name_en
class Subject(models.Model):
level = models.ForeignKey(EducationalLevel, on_delete=models.CASCADE, related_name='subjects')
teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, related_name='subjects')
name_en = models.CharField(_("Name (English)"), max_length=200)
name_ar = models.CharField(_("Name (Arabic)"), max_length=200)
description_en = models.TextField(_("Description (English)"), blank=True)
description_ar = models.TextField(_("Description (Arabic)"), blank=True)
price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2, default=0.00)
image = models.ImageField(_("Image"), upload_to='subjects/', blank=True, null=True)
google_drive_link = models.URLField(_("Google Drive Link"), blank=True)
google_meet_link = models.URLField(_("Google Meet Link"), blank=True)
def __str__(self):
return self.name_en
class Resource(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='resources')
title_en = models.CharField(_("Title (English)"), max_length=200)
title_ar = models.CharField(_("Title (Arabic)"), max_length=200)
file = models.FileField(_("File"), upload_to='resources/', blank=True, null=True)
link = models.URLField(_("External Link"), blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title_en
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile')
level = models.ForeignKey(EducationalLevel, on_delete=models.SET_NULL, null=True, blank=True, related_name='students')
phone_number = models.CharField(_("Phone Number"), max_length=20, blank=True)
subscribed_subjects = models.ManyToManyField(Subject, blank=True, related_name='subscribers')
def __str__(self):
return self.user.get_full_name() or self.user.username

View File

@ -1,25 +1,169 @@
{% load i18n static %}
<!DOCTYPE html>
<html lang="en">
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% trans "E-Learning Platform" %}{% endblock %}</title>
<!-- Bootstrap 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
{% if LANGUAGE_BIDI %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
{% endif %}
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Noto+Kufi+Arabic:wght@400;700&display=swap" rel="stylesheet">
<!-- Custom CSS -->
<style>
:root {
--primary-color: #2D31FA;
--secondary-color: #FF6B6B;
--accent-color: #4ECDC4;
--bg-light: #F8F9FA;
--glass-bg: rgba(255, 255, 255, 0.8);
--glass-border: rgba(255, 255, 255, 0.2);
}
body {
font-family: 'Outfit', 'Noto Kufi Arabic', sans-serif;
background-color: var(--bg-light);
color: #333;
overflow-x: hidden;
}
.navbar {
background: var(--glass-bg);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--glass-border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 700;
font-size: 1.5rem;
color: var(--primary-color) !important;
}
.btn-primary {
background-color: var(--primary-color);
border: none;
padding: 0.6rem 1.5rem;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(45, 49, 250, 0.3);
background-color: #1b1edb;
}
.glass-card {
background: var(--glass-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease;
}
.glass-card:hover {
transform: translateY(-10px);
}
.hero-section {
padding: 8rem 0 5rem;
background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%);
position: relative;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(45, 49, 250, 0.1) 0%, transparent 70%);
z-index: 0;
}
.footer {
padding: 3rem 0;
background: #fff;
border-top: 1px solid #eee;
}
/* RTL Specific tweaks */
[dir="rtl"] .ms-auto {
margin-right: auto !important;
margin-left: 0 !important;
}
[dir="rtl"] .me-auto {
margin-left: auto !important;
margin-right: 0 !important;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<nav class="navbar navbar-expand-lg fixed-top">
<div class="container">
<a class="navbar-brand" href="{% url 'index' %}">EduPlatform</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="#">{% trans "Courses" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">{% trans "Teachers" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/">{% trans "Admin" %}</a>
</li>
</ul>
<div class="d-flex align-items-center">
<div class="dropdown me-3">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
{% if LANGUAGE_CODE == 'ar' %}العربية{% else %}English{% endif %}
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-menu-item dropdown-item" href="{% url 'set_language' 'en' %}?next={{ request.path }}">English</a></li>
<li><a class="dropdown-menu-item dropdown-item" href="{% url 'set_language' 'ar' %}?next={{ request.path }}">العربية</a></li>
</ul>
</div>
{% if user.is_authenticated %}
<span class="me-3">{% trans "Hello" %}, {{ user.username }}</span>
{% else %}
<a href="/admin/login/" class="btn btn-primary">{% trans "Login" %}</a>
{% endif %}
</div>
</div>
</div>
</nav>
{% block content %}{% endblock %}
<footer class="footer mt-5">
<div class="container text-center">
<p>&copy; 2026 EduPlatform. {% trans "All rights reserved." %}</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
</html>

View File

@ -1,14 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<div class="container mt-5">
<h1>{{ article.title }}</h1>
<p class="text-muted">Published on {{ article.created_at|date:"F d, Y" }}</p>
<hr>
<div>
{{ article.content|safe }}
</div>
</div>
{% endblock %}

View File

@ -1,145 +1,133 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ project_name }}{% endblock %}
{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
</style>
{% endblock %}
{% block title %}{% trans "Home" %} | EduPlatform{% endblock %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<section class="hero-section">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h1 class="display-4 fw-bold mb-4">
{% if LANGUAGE_CODE == 'ar' %}
تعلم من أفضل المعلمين في أي وقت وأي مكان
{% else %}
Learn from the best teachers anytime, anywhere
{% endif %}
</h1>
<p class="lead mb-5 text-muted">
{% if LANGUAGE_CODE == 'ar' %}
منصة تعليمية متكاملة تدعم الطلاب والمعلمين مع دروس حية ومصادر تعليمية متميزة.
{% else %}
A comprehensive educational platform supporting students and teachers with live classes and premium educational resources.
{% endif %}
</p>
<div class="d-flex gap-3">
<a href="#levels-section" class="btn btn-primary btn-lg px-5">{% trans "Get Started" %}</a>
<a href="#" class="btn btn-outline-secondary btn-lg px-5">{% trans "Learn More" %}</a>
</div>
</div>
<div class="col-lg-6 d-none d-lg-block">
<div class="position-relative">
<div style="width: 400px; height: 400px; background: var(--primary-color); border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; opacity: 0.1; position: absolute; top: -50px; left: 50px;"></div>
<div style="width: 300px; height: 300px; background: var(--secondary-color); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; opacity: 0.1; position: absolute; bottom: -50px; right: 50px;"></div>
<div class="glass-card text-center position-relative z-1">
<h3 class="fw-bold mb-3">{% trans "Ready to start?" %}</h3>
<p>{% trans "Join thousands of students learning today." %}</p>
<div class="avatar-group d-flex justify-content-center mt-4">
<div style="width: 40px; height: 40px; border-radius: 50%; background: #ccc; border: 2px solid #fff; margin-left: -10px;"></div>
<div style="width: 40px; height: 40px; border-radius: 50%; background: #ddd; border: 2px solid #fff; margin-left: -10px;"></div>
<div style="width: 40px; height: 40px; border-radius: 50%; background: #eee; border: 2px solid #fff; margin-left: -10px;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
</section>
<section id="levels-section" class="subjects-section py-5">
<div class="container">
<div class="text-center mb-5">
<h2 class="fw-bold">{% trans "Our Educational Programs" %}</h2>
<p class="text-muted">{% trans "Explore subjects tailored for each educational level." %}</p>
</div>
{% for level in levels %}
<div class="mb-5">
<div class="d-flex align-items-center mb-4">
<div class="p-2 rounded-3 bg-primary text-white me-3" style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">
{{ forloop.counter }}
</div>
<h3 class="fw-bold mb-0">
{% if LANGUAGE_CODE == 'ar' %}{{ level.name_ar }}{% else %}{{ level.name_en }}{% endif %}
</h3>
<hr class="flex-grow-1 ms-4 d-none d-md-block opacity-10">
</div>
<div class="row g-4">
{% for subject in level.subjects.all %}
<div class="col-md-6 col-lg-4">
<div class="glass-card h-100 p-0 overflow-hidden shadow-sm">
<div style="height: 180px; background: {% cycle '#E3F2FD' '#F3E5F5' '#E8F5E9' %}; display: flex; align-items: center; justify-content: center;">
{% if subject.image %}
<img src="{{ subject.image.url }}" class="img-fluid" alt="{{ subject.name_en }}">
{% else %}
<span class="display-2" style="opacity: 0.2;">📚</span>
{% endif %}
</div>
<div class="p-4">
<h4 class="fw-bold mb-3">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %}
</h4>
<p class="text-muted small mb-4">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.description_ar|truncatewords:15 }}{% else %}{{ subject.description_en|truncatewords:15 }}{% endif %}
</p>
<div class="d-flex justify-content-between align-items-center">
<span class="h5 fw-bold mb-0 text-primary">${{ subject.price }}</span>
<a href="{% url 'subject_detail' subject.pk %}" class="btn btn-primary btn-sm">{% trans "Subscribe" %}</a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-light border-0 text-center py-4">
{% trans "No subjects available for this level yet." %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</section>
<section class="features-section py-5 bg-white">
<div class="container">
<div class="row text-center g-4">
<div class="col-md-4">
<div class="p-4">
<div class="mb-3 text-primary h1">🎥</div>
<h5 class="fw-bold">{% trans "Live Classes" %}</h5>
<p class="text-muted">{% trans "Join interactive sessions via Google Meet." %}</p>
</div>
</div>
<div class="col-md-4">
<div class="p-4">
<div class="mb-3 text-primary h1">📁</div>
<h5 class="fw-bold">{% trans "Resources" %}</h5>
<p class="text-muted">{% trans "Access course materials on Google Drive." %}</p>
</div>
</div>
<div class="col-md-4">
<div class="p-4">
<div class="mb-3 text-primary h1">💳</div>
<h5 class="fw-bold">{% trans "Easy Payments" %}</h5>
<p class="text-muted">{% trans "Secure payments with Thawani gateway." %}</p>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,119 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}
{% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %} | EduPlatform
{% endblock %}
{% block content %}
<div class="container py-5">
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'index' %}">{% trans "Home" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.level.name_ar }}{% else %}{{ subject.level.name_en }}{% endif %}
</li>
</ol>
</nav>
<div class="row g-5">
<div class="col-lg-8">
<div class="glass-card mb-4 overflow-hidden p-0">
<div style="height: 300px; background: #f8f9fa; display: flex; align-items: center; justify-content: center; position: relative;">
{% if subject.image %}
<img src="{{ subject.image.url }}" class="img-fluid w-100 h-100 object-fit-cover" alt="{{ subject.name_en }}">
{% else %}
<span class="display-1" style="opacity: 0.1;">📚</span>
{% endif %}
<div class="position-absolute top-0 end-0 m-4">
<span class="badge bg-primary fs-6 px-3 py-2 shadow-sm">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.level.name_ar }}{% else %}{{ subject.level.name_en }}{% endif %}
</span>
</div>
</div>
<div class="p-4 p-md-5">
<h1 class="fw-bold mb-4">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %}
</h1>
<div class="d-flex align-items-center mb-5 p-3 rounded-3 bg-light">
<div class="avatar-placeholder me-3" style="width: 50px; height: 50px; background: var(--primary-color); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white;">
{{ subject.teacher.user.username|first|upper }}
</div>
<div>
<p class="mb-0 text-muted small">{% trans "Teacher" %}</p>
<h6 class="fw-bold mb-0">{{ subject.teacher }}</h6>
</div>
</div>
<h4 class="fw-bold mb-3">{% trans "About this Subject" %}</h4>
<div class="lead text-muted mb-5">
{% if LANGUAGE_CODE == 'ar' %}
{{ subject.description_ar|linebreaks }}
{% else %}
{{ subject.description_en|linebreaks }}
{% endif %}
</div>
<div class="row g-4 mb-5">
<div class="col-md-6">
<div class="p-4 border rounded-4 h-100">
<div class="h3 mb-3 text-primary">🎥</div>
<h5 class="fw-bold">{% trans "Live Sessions" %}</h5>
<p class="text-muted small mb-0">{% trans "Interactive online classes and group discussions." %}</p>
</div>
</div>
<div class="col-md-6">
<div class="p-4 border rounded-4 h-100">
<div class="h3 mb-3 text-primary">📁</div>
<h5 class="fw-bold">{% trans "Study Materials" %}</h5>
<p class="text-muted small mb-0">{% trans "Comprehensive guides, notes, and practice exams." %}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="sticky-top" style="top: 2rem; z-index: 10;">
<div class="glass-card shadow-lg border-primary border-top border-4">
<div class="p-4">
<h3 class="fw-bold text-primary mb-4">${{ subject.price }}</h3>
<ul class="list-unstyled mb-4">
<li class="mb-3 d-flex align-items-center">
<span class="text-success me-2"></span> {% trans "Full lifetime access" %}
</li>
<li class="mb-3 d-flex align-items-center">
<span class="text-success me-2"></span> {% trans "Access on mobile and desktop" %}
</li>
<li class="mb-3 d-flex align-items-center">
<span class="text-success me-2"></span> {% trans "Certificate of completion" %}
</li>
</ul>
<button class="btn btn-primary btn-lg w-100 py-3 fw-bold mb-3 shadow-sm">
{% trans "Enroll Now" %}
</button>
<p class="text-center text-muted small mb-0">
{% trans "30-Day Money-Back Guarantee" %}
</p>
</div>
</div>
<div class="glass-card mt-4">
<div class="p-4 text-center">
<h6 class="fw-bold mb-3">{% trans "Share this course" %}</h6>
<div class="d-flex justify-content-center gap-2">
<button class="btn btn-light btn-sm rounded-circle" style="width: 40px; height: 40px;">f</button>
<button class="btn btn-light btn-sm rounded-circle" style="width: 40px; height: 40px;">t</button>
<button class="btn btn-light btn-sm rounded-circle" style="width: 40px; height: 40px;">in</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,9 @@
from django.urls import path
from .views import home
from . import views
urlpatterns = [
path("", home, name="home"),
path('', views.index, name='index'),
path('set-language/<str:lang_code>/', views.set_language, name='set_language'),
path('subject/<int:pk>/', views.subject_detail, name='subject_detail'),
path('ajax/get-subjects-by-level/', views.get_subjects_by_level, name='get_subjects_by_level'),
]

View File

@ -1,25 +1,39 @@
import os
import platform
from django import get_version as django_version
from django.shortcuts import render
from django.utils import timezone
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import translation
from django.conf import settings
from django.http import JsonResponse
from django.contrib.admin.views.decorators import staff_member_required
from .models import EducationalLevel, Subject, Teacher
def index(request):
# Fetch levels with their related subjects using prefetch_related for efficiency
levels = EducationalLevel.objects.prefetch_related('subjects').all()
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
'levels': levels,
}
return render(request, "core/index.html", context)
return render(request, 'core/index.html', context)
def set_language(request, lang_code):
next_url = request.GET.get('next', '/')
response = redirect(next_url)
if lang_code in [lang[0] for lang in settings.LANGUAGES]:
translation.activate(lang_code)
if hasattr(request, 'session'):
request.session['_language'] = lang_code
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
return response
def subject_detail(request, pk):
subject = get_object_or_404(Subject, pk=pk)
return render(request, 'core/subject_detail.html', {'subject': subject})
@staff_member_required
def get_subjects_by_level(request):
level_id = request.GET.get('level_id')
if not level_id:
return JsonResponse([], safe=False)
subjects = Subject.objects.filter(level_id=level_id).values('id', 'name_en', 'name_ar')
return JsonResponse(list(subjects), safe=False)

BIN
media/resources/as.pdf Normal file

Binary file not shown.

View File

@ -1,3 +1,4 @@
Django==5.2.7
mysqlclient==2.2.7
python-dotenv==1.1.1
django-jazzmin

View File

@ -0,0 +1,65 @@
(function($) {
'use strict';
$(function() {
console.log("Admin Resource JS loaded");
var $levelSelect = $('#id_educational_level');
var $subjectSelect = $('#id_subject');
// Check if elements exist
if (!$levelSelect.length) {
console.warn("Educational Level field #id_educational_level not found");
}
if (!$subjectSelect.length) {
console.warn("Subject field #id_subject not found");
}
if (!$levelSelect.length || !$subjectSelect.length) return;
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
function updateSubjects(levelId, currentSubjectId) {
console.log("Updating subjects for level:", levelId);
// If no level selected, clear subjects
if (!levelId) {
$subjectSelect.html('<option value="">---------</option>');
return;
}
$.getJSON('/ajax/get-subjects-by-level/', {level_id: levelId}, function(data) {
console.log("Subjects fetched:", data.length);
var options = '<option value="">---------</option>';
$.each(data, function(index, subject) {
var selected = '';
// If we have a current subject ID, check if it matches
if (currentSubjectId && subject.id.toString() === currentSubjectId.toString()) {
selected = ' selected';
}
var name = isArabic ? subject.name_ar : subject.name_en;
options += '<option value="' + subject.id + '"' + selected + '>' + name + '</option>';
});
$subjectSelect.html(options);
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ", " + error;
console.error("Request Failed: " + err);
});
}
$levelSelect.on('change', function() {
var levelId = $(this).val();
// When level changes, we reset the subject selection
updateSubjects(levelId, null);
});
// Initial load
var initialLevel = $levelSelect.val();
var initialSubject = $subjectSelect.val();
if (initialLevel) {
updateSubjects(initialLevel, initialSubject);
} else {
if (!initialSubject) {
$subjectSelect.html('<option value="">---------</option>');
}
}
});
})(django.jQuery);

View File

@ -0,0 +1,78 @@
(function($) {
'use strict';
$(function() {
var $levelSelect = $('#id_level');
var $subjectSelect = $('#id_subscribed_subjects');
// Check if we are in the admin change form
if (!$levelSelect.length || !$subjectSelect.length) return;
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
function updateSubjects(levelId, selectedValues) {
if (!levelId) {
$subjectSelect.html('');
refreshSelectFilter();
return;
}
$.getJSON('/ajax/get-subjects-by-level/', {level_id: levelId}, function(data) {
var options = '';
$.each(data, function(index, subject) {
var selected = '';
if (selectedValues && selectedValues.indexOf(subject.id.toString()) !== -1) {
selected = ' selected';
}
var name = isArabic ? subject.name_ar : subject.name_en;
options += '<option value="' + subject.id + '"' + selected + '>' + name + '</option>';
});
$subjectSelect.html(options);
refreshSelectFilter();
});
}
function refreshSelectFilter() {
// Django's filter_horizontal uses SelectFilter2
if (typeof SelectFilter2 !== 'undefined') {
var fieldName = 'subscribed_subjects';
// Remove the existing widget elements
$('.selector').has('#id_' + fieldName + '_from').remove();
// Re-initialize it
// SelectFilter2.init(field_id, field_name, is_stacked)
SelectFilter2.init('id_' + fieldName, fieldName, false);
} else if (typeof SelectFilter !== 'undefined') {
var fieldName = 'subscribed_subjects';
$('.selector').has('#id_' + fieldName + '_from').remove();
SelectFilter.init('id_' + fieldName, fieldName, false);
}
}
$levelSelect.on('change', function() {
var selectedValues = [];
// When level changes, we might want to keep currently selected subjects if they still belong to the level?
// Actually, usually when level changes, we reset subjects or only keep valid ones.
// For now, let's reset to avoid confusion, or the user can re-select.
updateSubjects($(this).val(), []);
});
// If we are editing an existing student, we should filter the subjects on load
// but KEEP the currently selected ones.
if ($levelSelect.val()) {
// Get currently selected values from the "chosen" side if possible,
// or from the original select before it was widgetized.
var selectedValues = [];
$('#id_subscribed_subjects_to option').each(function() {
selectedValues.push($(this).val());
});
// If empty (new form or nothing selected), try the main select
if (selectedValues.length === 0) {
$subjectSelect.find('option:selected').each(function() {
selectedValues.push($(this).val());
});
}
updateSubjects($levelSelect.val(), selectedValues);
}
});
})(django.jQuery);

View File

@ -1,29 +1,17 @@
'use strict';
{
// Call function fn when the DOM is loaded and ready. If it is already
// loaded, call the function now.
// http://youmightnotneedjquery.com/#ready
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
(function($) {
'use strict';
ready(function() {
function handleClick(event) {
event.preventDefault();
const params = new URLSearchParams(window.location.search);
if (params.has('_popup')) {
window.close(); // Close the popup.
$(document).ready(function() {
$('.cancel-link').click(function(e) {
e.preventDefault();
const parentWindow = window.parent;
if (parentWindow && typeof(parentWindow.dismissRelatedObjectModal) === 'function' && parentWindow !== window) {
parentWindow.dismissRelatedObjectModal();
} else {
window.history.back(); // Otherwise, go back.
// fallback to default behavior
window.history.back();
}
}
document.querySelectorAll('.cancel-link').forEach(function(el) {
el.addEventListener('click', handleClick);
return false;
});
});
}
})(django.jQuery);

View File

@ -1,15 +1,44 @@
'use strict';
{
const initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
switch(initData.action) {
case 'change':
opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value);
break;
case 'delete':
opener.dismissDeleteRelatedObjectPopup(window, initData.value);
break;
default:
opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj);
break;
(function() {
'use strict';
var windowRef = window;
var windowRefProxy;
var windowName, widgetName;
var openerRef = windowRef.opener;
if (!openerRef) {
// related modal is active
openerRef = windowRef.parent;
windowName = windowRef.name;
widgetName = windowName.replace(/^(change|add|delete|lookup)_/, '');
windowRefProxy = {
name: widgetName,
location: windowRef.location,
close: function() {
openerRef.dismissRelatedObjectModal();
}
};
windowRef = windowRefProxy;
}
}
// default django popup_response.js
var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
switch (initData.action) {
case 'change':
if (typeof(openerRef.dismissChangeRelatedObjectPopup) === 'function') {
openerRef.dismissChangeRelatedObjectPopup(windowRef, initData.value, initData.obj, initData.new_value);
}
break;
case 'delete':
if (typeof(openerRef.dismissDeleteRelatedObjectPopup) === 'function') {
openerRef.dismissDeleteRelatedObjectPopup(windowRef, initData.value);
}
break;
default:
if (typeof(openerRef.dismissAddRelatedObjectPopup) === 'function') {
openerRef.dismissAddRelatedObjectPopup(windowRef, initData.value, initData.obj);
}
break;
}
})();

View File

@ -0,0 +1,927 @@
/** Django-related improvements to AdminLTE UI **/
div.inline-related {
padding: 10px;
}
.form-row {
padding: 5px;
}
.help-block ul {
margin: 10px 0 0 15px;
padding: 0;
}
/** Fix bug of adminLTE, since django is using th headers in middle of table **/
.card-body.p-0 .table thead > tr > th:first-of-type,
.card-body.p-0 .table thead > tr > td:first-of-type,
.card-body.p-0 .table tfoot > tr > th:first-of-type,
.card-body.p-0 .table tfoot > tr > td:first-of-type,
.card-body.p-0 .table tbody > tr > th:first-of-type,
.card-body.p-0 .table tbody > tr > td:first-of-type {
padding-left: 0.75rem;
}
.card-body.p-0 .table thead > tr > th:last-of-type,
.card-body.p-0 .table thead > tr > td:last-of-type,
.card-body.p-0 .table tfoot > tr > th:last-of-type,
.card-body.p-0 .table tfoot > tr > td:last-of-type,
.card-body.p-0 .table tbody > tr > th:last-of-type,
.card-body.p-0 .table tbody > tr > td:last-of-type {
padding-right: 0.75rem;
}
.card-body.p-0 .table thead > tr > th:first-child,
.card-body.p-0 .table thead > tr > td:first-child,
.card-body.p-0 .table tfoot > tr > th:first-child,
.card-body.p-0 .table tfoot > tr > td:first-child,
.card-body.p-0 .table tbody > tr > th:first-child,
.card-body.p-0 .table tbody > tr > td:first-child {
padding-left: 1.5rem;
}
.card-body.p-0 .table thead > tr > th:last-child,
.card-body.p-0 .table thead > tr > td:last-child,
.card-body.p-0 .table tfoot > tr > th:last-child,
.card-body.p-0 .table tfoot > tr > td:last-child,
.card-body.p-0 .table tbody > tr > th:last-child,
.card-body.p-0 .table tbody > tr > td:last-child {
padding-right: 1.5rem;
}
[class*=sidebar-dark-] .nav-header {
color: rgb(255,255,255,0.3);
margin-top: 1rem;
}
/* Table styles */
.table tr.form-row {
display: table-row;
}
.table td.action-checkbox {
width: 45px;
}
.table thead th {
color: #64748b;
border-bottom: 0;
}
.empty-form {
display: none !important;
}
.inline-related .tabular {
background-color: white;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
td.djn-td,
th.djn-th {
padding: 10px;
}
td.delete input {
margin: 10px;
}
tr.djn-tr>.original {
padding-left: 20px;
}
.hidden {
display: none;
}
/* Checkbox selection table header */
.djn-checkbox-select-all {
padding-right: 0 !important;
width: 0;
}
.object-tools {
padding: 0;
}
.object-tools li {
list-style: none;
margin: 0;
padding: 0;
}
.object-tools .historylink {
background-color: #3c8dbc;
width: 100%;
display: block;
padding: 5px;
text-align: center;
color: white;
}
.jazzmin-avatar {
font-size: 20px;
}
.related-widget-wrapper-link {
padding: 7px;
}
.related-widget-wrapper select {
width: initial;
/* Setting a width will make the *-related btns overflow */
height: auto;
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
box-shadow: inset 0 0 0 transparent;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.tab-pane {
overflow-x: auto;
}
table.dataTable thead .sorting::after,
table.dataTable thead .sorting_asc::after,
table.dataTable thead .sorting_desc::after,
table.dataTable thead .sorting_asc_disabled::after,
table.dataTable thead .sorting_desc_disabled::after {
right: 0.5em;
content: "\2193";
}
.select2-container {
min-width: 200px;
}
.select2-container .select2-selection--single {
border: 1px solid #ced4da !important;
min-height: 38px;
/* Center text inside */
display: flex !important;
align-items: center;
}
.select2-container--default .select2-selection--single {
border: 1px solid #ced4da;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
right: 5px !important;
top: unset !important;
}
.select2-results__option {
color: black;
}
#changelist-search .form-group {
margin-bottom: .5em;
margin-right: .5em;
}
.table tbody tr th {
padding-left: .75rem;
}
.user-profile {
font-size: 2.4em;
}
.date-hierarchy {
margin-right: 8px;
display: block;
}
/* APP.CSS */
.form-group div .vTextField,
.form-group div .vLargeTextField,
.form-group div .vURLField,
.form-group div .vBigIntegerField,
.form-group div input[type="text"]
{
display: block;
width: 100%;
}
.vTextField,
.vLargeTextField,
.vURLField,
.vIntegerField,
.vBigIntegerField,
.vForeignKeyRawIdAdminField,
.vDateField,
.vTimeField,
input[type="number"],
input[type="text"]
{
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
box-shadow: inset 0 0 0 transparent;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.vDateField,
.vTimeField {
margin-bottom: 5px;
display: inline-block;
}
.vLargeTextField {
height: auto;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
}
.date-icon:before,
.clock-icon:before {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome !important;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f073";
}
.clock-icon:before {
content: "\f017";
}
/* CALENDARS & CLOCKS */
.calendarbox,
.clockbox {
margin: 5px auto;
font-size: 12px;
width: 19em;
text-align: center;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
overflow: hidden;
position: relative;
}
.clockbox {
width: auto;
}
.calendar {
margin: 0;
padding: 0;
}
.calendar table {
margin: 0;
padding: 0;
border-collapse: collapse;
background: white;
width: 100%;
}
.calendar caption,
.calendarbox h2,
.clockbox h2 {
margin: 0;
text-align: center;
border-top: none;
background: #f5dd5d;
font-weight: 700;
font-size: 12px;
color: #333;
}
.clockbox h2 {
font-size: 16px;
padding: 5px;
}
.calendar th {
padding: 8px 5px;
background: #f8f8f8;
border-bottom: 1px solid #ddd;
font-weight: 400;
font-size: 12px;
text-align: center;
color: #666;
}
.calendar td {
font-weight: 400;
font-size: 12px;
text-align: center;
padding: 0;
border-top: 1px solid #eee;
border-bottom: none;
}
.calendar td.selected a {
background: #3C8DBC;
color: #fff !important;
}
.calendar td.nonday {
background: #f8f8f8;
}
.calendar td.today a {
font-weight: 700;
}
.calendar td a,
.timelist a {
display: block;
font-weight: 400;
padding: 6px;
text-decoration: none;
color: #444;
}
.calendar td a:focus,
.timelist a:focus,
.calendar td a:hover,
.timelist a:hover {
background: #3C8DBC;
color: white;
}
.calendar td a:active,
.timelist a:active {
background: #3C8DBC;
color: white;
}
.calendarnav {
font-size: 10px;
text-align: center;
color: #ccc;
margin: 0;
padding: 1px 3px;
}
.calendarnav a:link,
#calendarnav a:visited,
#calendarnav a:focus,
#calendarnav a:hover {
color: #999;
}
.calendar-shortcuts {
background: white;
font-size: 11px;
line-height: 11px;
border-top: 1px solid #eee;
padding: 8px 0;
color: #ccc;
}
.calendarbox .calendarnav-previous,
.calendarbox .calendarnav-next {
display: block;
position: absolute;
top: 8px;
width: 15px;
height: 15px;
text-indent: -9999px;
padding: 0;
}
.calendarnav-previous {
left: 10px;
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
}
.calendarbox .calendarnav-previous:focus,
.calendarbox .calendarnav-previous:hover {
background-position: 0 -15px;
}
.calendarnav-next {
right: 10px;
background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
}
.calendarbox .calendarnav-next:focus,
.calendarbox .calendarnav-next:hover {
background-position: 0 -45px;
}
.calendar-cancel {
margin: 0;
padding: 4px 0;
font-size: 12px;
background: #eee;
border-top: 1px solid #ddd;
color: #333;
}
.calendar-cancel:focus,
.calendar-cancel:hover {
background: #ddd;
}
.calendar-cancel a {
color: black;
display: block;
}
/* Selectors - This needs some work TODO */
.selector {
width: 100%;
float: left;
}
.selector select {
width: 100%;
height: 15em;
}
.selector-available,
.selector-chosen {
float: left;
width: 48%;
text-align: center;
margin-bottom: 5px;
}
.selector-available h2,
.selector-chosen h2 {
border: 1px solid #ccc;
font-size: 16px;
padding: 5px;
}
.selector-chosen h2 {
background: #007bff;
color: #fff;
}
.selector .selector-available h2 {
background: #f8f8f8;
color: #666;
}
.selector .selector-filter {
background: white;
border: 1px solid #ccc;
padding: 8px;
color: #999;
font-size: 10px;
margin: 0;
text-align: left;
}
.selector-filter input {
height: 24px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
margin-left: 0 !important;
}
.selector .selector-filter label,
.inline-group .aligned .selector .selector-filter label {
float: left;
margin: 0;
width: 18px;
height: 18px;
padding: 0;
overflow: hidden;
line-height: 1;
}
/* Might need to import more rules from:
* https://github.com/django/django/blob/master/django/contrib/admin/static/admin/css/responsive.css
*/
.inline-group {
overflow: auto;
}
.selector .selector-available input {
width: 100%;
margin-left: 8px;
}
.selector ul.selector-chooser {
float: left;
width: 4%;
background-color: #eee;
border-radius: 10px;
margin: 10em 0 0;
padding: 0;
}
.selector-chooser li {
margin: 0;
padding: 3px;
list-style-type: none;
}
.selector select {
padding: 0 10px;
margin: 0 0 10px;
/*border-radius: 0 0 4px 4px;*/
;
}
.selector-add,
.selector-remove {
height: 16px;
display: block;
text-indent: -3000px;
overflow: hidden;
cursor: default;
opacity: 0.3;
}
.active.selector-add,
.active.selector-remove {
opacity: 1;
}
.active.selector-add:hover,
.active.selector-remove:hover {
cursor: pointer;
}
.selector-add {
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
}
.active.selector-add:focus,
.active.selector-add:hover {
background-position: 0 -112px;
}
.selector-remove {
background: url(../img/selector-icons.svg) 0 -64px no-repeat;
}
.active.selector-remove:focus,
.active.selector-remove:hover {
background-position: 0 -80px;
}
a.selector-chooseall,
a.selector-clearall {
display: inline-block;
height: 16px;
text-align: left;
margin: 1px auto 3px;
overflow: hidden;
font-weight: bold;
line-height: 16px;
color: #666;
text-decoration: none;
opacity: 0.3;
}
a.active.selector-chooseall:focus,
a.active.selector-clearall:focus,
a.active.selector-chooseall:hover,
a.active.selector-clearall:hover {
color: #447e9b;
}
a.active.selector-chooseall,
a.active.selector-clearall {
opacity: 1;
}
a.active.selector-chooseall:hover,
a.active.selector-clearall:hover {
cursor: pointer;
}
a.selector-chooseall {
padding: 0 18px 0 0;
background: url(../img/selector-icons.svg) right -160px no-repeat;
cursor: default;
}
a.active.selector-chooseall:focus,
a.active.selector-chooseall:hover {
background-position: 100% -176px;
}
a.selector-clearall {
padding: 0 0 0 18px;
background: url(../img/selector-icons.svg) 0 -128px no-repeat;
cursor: default;
}
a.active.selector-clearall:focus,
a.active.selector-clearall:hover {
background-position: 0 -144px;
}
.selector .search-label-icon {
height: 0;
}
#user_form input[type="password"] {
width: 100%;
}
.control-label {
margin-top: 7px;
}
.help-block,
.timezonewarning {
font-size: .8em;
color: #859099;
font-style: italic;
}
.dashboard tbody tr:first-child td {
border-top: none;
}
.vTimeField {
margin-top: 10px;
}
.vTimeField,
.vDateField {
min-width: 200px;
}
.date-icon::before,
.clock-icon::before {
font-family: "Font Awesome 5 Free" !important;
}
.timelist li {
list-style-type: none;
}
.timelist {
margin: 0;
padding: 0;
}
body.no-sidebar .content-wrapper,
body.no-sidebar .main-footer,
body.no-sidebar .main-header {
margin-left: 0;
}
.vCheckboxLabel.inline {
vertical-align: top;
color: red;
margin-bottom: 0;
}
.inline-related .card-header>span {
float: right;
}
.ui-customiser .menu-items div {
width: 40px;
height: 20px;
border-radius: 25px;
margin-right: 10px;
margin-bottom: 10px;
opacity: 0.8;
cursor: pointer;
}
.ui-customiser select {
width: 100%;
height: auto;
padding: 6px 2px;
}
.control-sidebar-content label {
vertical-align: top;
}
.ui-customiser .menu-items div.inactive {
opacity: 0.3;
}
.ui-customiser .menu-items div.active {
opacity: 1;
border: 1px solid white;
}
.timeline-item {
word-break: break-word;
}
.navbar-nav .brand-link {
padding-top: 3px;
}
.breadcrumb {
background: transparent;
margin: 0;
}
.breadcrumb-item+.breadcrumb-item::before {
content: "\203A";
}
.login-box,
.register-box {
width: 500px;
max-width: 100%;
}
#jazzy-collapsible .collapsible-header:hover {
background: #007bff;
color: white;
}
#jazzy-collapsible .collapsible-header {
cursor: pointer;
}
#jazzy-carousel .carousel-indicators li {
background-color: #007bfe;
}
#jazzy-carousel .carousel-indicators {
position: initial;
}
form ul.radiolist li {
list-style-type: none;
}
form ul.radiolist label {
float: none;
display: inline;
}
form ul.radiolist input[type="radio"] {
margin: -2px 4px 0 0;
padding: 0;
}
form ul.inline {
margin-left: 0;
padding: 0;
}
form ul.inline li {
float: left;
padding-right: 7px;
}
.content-wrapper>.content {
padding: 1rem 2rem;
}
.navbar {
padding: .5rem 2rem;
}
.main-footer {
color: #869099;
padding: 1rem 2rem;
font-size: 14px;
}
.page-actions > a {
margin-right:0.25rem;
margin-left: 0.25rem;
}
#jazzy-actions.sticky-top {
top: 10px;
}
body.layout-navbar-fixed #jazzy-actions.sticky-top {
top: 67px;
}
/* stacked inlines */
a.inline-deletelink:hover {
background-color: #c82333;
border-color: #bd2130;
}
a.inline-deletelink {
float: right;
padding: 3px 5px;
margin: 10px;
background-color: #dc3545;
border-radius: .25rem;
color: white !important;
border: 1px solid #dc3545;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
/* end stacked inlines */
/* Support for django-mptt */
#result_list .field-tree_actions {
width: calc(40px + 2.25rem);
}
#result_list .field-tree_actions>div {
margin-top: 0;
}
/* End support for django-mptt */
/* modal tweaks */
.modal.modal-wide .modal-dialog {
width: 50%;
max-width: inherit;
}
.modal-wide .modal-body {
overflow-y: auto;
}
iframe.related-iframe {
width: 100%;
height: 450px;
}
/* Blur background when using modal */
.modal-open .wrapper {
-webkit-filter: blur(1px);
-moz-filter: blur(1px);
-o-filter: blur(1px);
-ms-filter: blur(1px);
filter: blur(1px);
}
/* end modal tweaks */
.control-sidebar {
overflow: hidden scroll;
}
/* tweaks to allow bootstrap styling */
body.jazzmin-login-page {
-ms-flex-align: center;
align-items: center;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
height: 100vh;
-ms-flex-pack: center;
justify-content: center;
}
.callout {
color: black;
}
/* sidebar scrolling */
.layout-fixed #jazzy-sidebar {
top: 0;
bottom: 0;
/* Enable y scroll */
overflow-y: scroll;
/* May inherit scroll, so we need to explicitly hide */
overflow-x: hidden;
}
/* calculate height to fit content, we don't to enable scrolling if the content fits */
.layout-fixed #jazzy-sidebar .sidebar {
height: auto !important;
}
/* Hide scrollbar */
.layout-fixed #jazzy-sidebar {
scrollbar-width: none;
}
.layout-fixed #jazzy-sidebar::-webkit-scrollbar {
width: 0;
}
/* nav-item will overflow container in width if scrollbar is visible */
#jazzy-sidebar .nav-sidebar > .nav-item {
width: 100%;
}
/* tweeks for django-filer*/
.navigator-top-nav + #content-main {
float: left;
width: 100%;
}

View File

@ -0,0 +1,14 @@
<svg width="15" height="60" viewBox="0 0 1792 7168" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="previous">
<path d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="next">
<path d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
</defs>
<use xlink:href="#previous" x="0" y="0" fill="#333333" />
<use xlink:href="#previous" x="0" y="1792" fill="#000000" />
<use xlink:href="#next" x="0" y="3584" fill="#333333" />
<use xlink:href="#next" x="0" y="5376" fill="#000000" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,9 @@
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="icon">
<path d="M192 1664h288v-288h-288v288zm352 0h320v-288h-320v288zm-352-352h288v-320h-288v320zm352 0h320v-320h-320v320zm-352-384h288v-288h-288v288zm736 736h320v-288h-320v288zm-384-736h320v-288h-320v288zm768 736h288v-288h-288v288zm-384-352h320v-320h-320v320zm-352-864v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm736 864h288v-320h-288v320zm-384-384h320v-288h-320v288zm384 0h288v-288h-288v288zm32-480v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm384-64v1280q0 52-38 90t-90 38h-1408q-52 0-90-38t-38-90v-1280q0-52 38-90t90-38h128v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h384v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h128q52 0 90 38t38 90z"/>
</g>
</defs>
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#efb80b" d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/>
</svg>

After

Width:  |  Height:  |  Size: 380 B

View File

@ -0,0 +1,34 @@
<svg width="16" height="192" viewBox="0 0 1792 21504" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="up">
<path d="M1412 895q0-27-18-45l-362-362-91-91q-18-18-45-18t-45 18l-91 91-362 362q-18 18-18 45t18 45l91 91q18 18 45 18t45-18l189-189v502q0 26 19 45t45 19h128q26 0 45-19t19-45v-502l189 189q19 19 45 19t45-19l91-91q18-18 18-45zm252 1q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="down">
<path d="M1412 897q0-27-18-45l-91-91q-18-18-45-18t-45 18l-189 189v-502q0-26-19-45t-45-19h-128q-26 0-45 19t-19 45v502l-189-189q-19-19-45-19t-45 19l-91 91q-18 18-18 45t18 45l362 362 91 91q18 18 45 18t45-18l91-91 362-362q18-18 18-45zm252-1q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="left">
<path d="M1408 960v-128q0-26-19-45t-45-19h-502l189-189q19-19 19-45t-19-45l-91-91q-18-18-45-18t-45 18l-362 362-91 91q-18 18-18 45t18 45l91 91 362 362q18 18 45 18t45-18l91-91q18-18 18-45t-18-45l-189-189h502q26 0 45-19t19-45zm256-64q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="right">
<path d="M1413 896q0-27-18-45l-91-91-362-362q-18-18-45-18t-45 18l-91 91q-18 18-18 45t18 45l189 189h-502q-26 0-45 19t-19 45v128q0 26 19 45t45 19h502l-189 189q-19 19-19 45t19 45l91 91q18 18 45 18t45-18l362-362 91-91q18-18 18-45zm251 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="clearall">
<path transform="translate(336, 336) scale(0.75)" d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="chooseall">
<path transform="translate(336, 336) scale(0.75)" d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
</defs>
<use xlink:href="#up" x="0" y="0" fill="#666666" />
<use xlink:href="#up" x="0" y="1792" fill="#447e9b" />
<use xlink:href="#down" x="0" y="3584" fill="#666666" />
<use xlink:href="#down" x="0" y="5376" fill="#447e9b" />
<use xlink:href="#left" x="0" y="7168" fill="#666666" />
<use xlink:href="#left" x="0" y="8960" fill="#447e9b" />
<use xlink:href="#right" x="0" y="10752" fill="#666666" />
<use xlink:href="#right" x="0" y="12544" fill="#447e9b" />
<use xlink:href="#clearall" x="0" y="14336" fill="#666666" />
<use xlink:href="#clearall" x="0" y="16128" fill="#447e9b" />
<use xlink:href="#chooseall" x="0" y="17920" fill="#666666" />
<use xlink:href="#chooseall" x="0" y="19712" fill="#447e9b" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,151 @@
(function($) {
'use strict';
function FixSelectorHeight() {
$('.selector .selector-chosen').each(function () {
let selector_chosen = $(this);
let selector_available = selector_chosen.siblings('.selector-available');
let selector_chosen_select = selector_chosen.find('select').first();
let selector_available_select = selector_available.find('select').first();
let selector_available_filter = selector_available.find('p.selector-filter').first();
selector_chosen_select.height(selector_available_select.height() + selector_available_filter.outerHeight());
selector_chosen_select.css('border-top', selector_chosen_select.css('border-bottom'));
});
}
function handleCarousel($carousel) {
const errors = $('.errorlist li', $carousel);
const hash = document.location.hash;
// If we have errors, open that tab first
if (errors.length) {
const errorCarousel = errors.eq(0).closest('.carousel-item');
$carousel.carousel(errorCarousel.data('carouselid'));
$('.carousel-fieldset-label', $carousel).text(errorCarousel.data()["label"]);
} else if (hash) {
// If we have a tab hash, open that
const activeCarousel = $('.carousel-item[data-target="' + hash + '"]', $carousel);
$carousel.carousel(activeCarousel.data()["carouselid"]);
$('.carousel-fieldset-label', $carousel).text(activeCarousel.data()["label"]);
}
// Update page hash/history on slide
$carousel.on('slide.bs.carousel', function (e) {
FixSelectorHeight();
// call resize in change view after tab switch
window.dispatchEvent(new Event('resize'));
if (e.relatedTarget.dataset.hasOwnProperty("label")) {
$('.carousel-fieldset-label', $carousel).text(e.relatedTarget.dataset.label);
}
const hash = e.relatedTarget.dataset.target;
if (history.pushState) {
history.pushState(null, null, hash);
} else {
location.hash = hash;
}
});
}
function handleTabs($tabs) {
const errors = $('.change-form .errorlist li');
const hash = document.location.hash;
// If we have errors, open that tab first
if (errors.length) {
const tabId = errors.eq(0).closest('.tab-pane').attr('id');
$('a[href="#' + tabId + '"]').tab('show');
} else if (hash) {
// If we have a tab hash, open that
$('a[href="' + hash + '"]', $tabs).tab('show');
}
// Change hash for page-reload
$('a', $tabs).on('shown.bs.tab', function (e) {
FixSelectorHeight();
// call resize in change view after tab switch
window.dispatchEvent(new Event('resize'));
e.preventDefault();
if (history.pushState) {
history.pushState(null, null, e.target.hash);
} else {
location.hash = e.target.hash;
}
});
}
function handleCollapsible($collapsible) {
const errors = $('.errorlist li', $collapsible);
const hash = document.location.hash;
// If we have errors, open that tab first
if (errors.length) {
$('.panel-collapse', $collapsible).collapse('hide');
errors.eq(0).closest('.panel-collapse').collapse('show');
} else if (hash) {
// If we have a tab hash, open that
$('.panel-collapse', $collapsible).collapse('hide');
$(hash, $collapsible).collapse('show');
}
// Change hash for page-reload
$collapsible.on('shown.bs.collapse', function (e) {
FixSelectorHeight();
// call resize in change view after tab switch
window.dispatchEvent(new Event('resize'));
if (history.pushState) {
history.pushState(null, null, '#' + e.target.id);
} else {
location.hash = '#' + e.target.id;
}
});
}
function applySelect2() {
// Apply select2 to any select boxes that don't yet have it
// and are not part of the django's empty-form inline
const noSelect2 = '.empty-form select, .select2-hidden-accessible, .selectfilter, .selector-available select, .selector-chosen select, select[data-autocomplete-light-function=select2]';
$('select').not(noSelect2).select2({ width: 'element' });
}
$(document).ready(function () {
const $carousel = $('#content-main form #jazzy-carousel');
const $tabs = $('#content-main form #jazzy-tabs');
const $collapsible = $('#content-main form #jazzy-collapsible');
// Ensure all raw_id_fields have the search icon in them
$('.related-lookup').append('<i class="fa fa-search"></i>');
// Style the inline fieldset button
$('.inline-related fieldset.module .add-row a').addClass('btn btn-sm btn-default float-right');
$('div.add-row>a').addClass('btn btn-sm btn-default float-right');
// Ensure we preserve the tab the user was on using the url hash, even on page reload
if ($tabs.length) { handleTabs($tabs); }
else if ($carousel.length) { handleCarousel($carousel); }
else if ($collapsible.length) { handleCollapsible($collapsible); }
applySelect2();
$('body').on('change', '.related-widget-wrapper select', function(e) {
const event = $.Event('django:update-related');
$(this).trigger(event);
if (!event.isDefaultPrevented() && typeof(window.updateRelatedObjectLinks) !== 'undefined') {
updateRelatedObjectLinks(this);
}
});
});
// Apply select2 to all select boxes when new inline row is created
django.jQuery(document).on('formset:added', applySelect2);
})(jQuery);

View File

@ -0,0 +1,64 @@
(function($) {
'use strict';
$.fn.search_filters = function () {
$(this).change(function () {
const $field = $(this);
const $option = $field.find('option:selected');
const select_name = $option.data('name');
if (select_name) {
$field.attr('name', select_name);
} else {
$field.removeAttr('name');
}
});
$(this).trigger('change');
};
function getMinimuInputLength(element) {
return window.filterInputLength[element.data('name')] ?? window.filterInputLengthDefault;
}
function searchFilters() {
// Make search filters select2 and ensure they work for filtering
const $ele = $('.search-filter');
$ele.search_filters();
$ele.each(function () {
const $this = $(this);
$this.select2({ width: '100%', minimumInputLength: getMinimuInputLength($this) });
});
// Use select2 for mptt dropdowns
const $mptt = $('.search-filter-mptt');
if ($mptt.length) {
$mptt.search_filters();
$mptt.select2({
width: '100%',
minimumInputLength: getMinimuInputLength($mptt),
templateResult: function (data) {
// https://stackoverflow.com/questions/30820215/selectable-optgroups-in-select2#30948247
// rewrite templateresult for build tree hierarchy
if (!data.element) {
return data.text;
}
const $element = $(data.element);
let $wrapper = $('<span></span>');
$wrapper.attr('style', $($element[0]).attr('style'));
$wrapper.text(data.text);
return $wrapper;
},
});
}
}
$(document).ready(function () {
// Ensure all raw_id_fields have the search icon in them
$('.related-lookup').append('<i class="fa fa-search"></i>')
// Allow for styling of selects
$('.actions select').addClass('form-control').select2({ width: 'element' });
searchFilters();
});
})(jQuery);

View File

@ -0,0 +1,67 @@
(function($) {
'use strict';
function setCookie(key, value) {
const expires = new Date();
expires.setTime(expires.getTime() + (value * 24 * 60 * 60 * 1000));
document.cookie = key + '=' + value + ';expires=' + expires.toUTCString() + '; SameSite=Strict;path=/';
}
function getCookie(key) {
const keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
}
function handleMenu() {
$('[data-widget=pushmenu]').bind('click', function () {
const menuClosed = getCookie('jazzy_menu') === 'closed';
if (!menuClosed) {
setCookie('jazzy_menu', 'closed');
} else {
setCookie('jazzy_menu', 'open');
}
});
}
function setActiveLinks() {
/*
Set the currently active menu item based on the current url, or failing that, find the parent
item from the breadcrumbs
*/
const url = window.location.pathname;
const $breadcrumb = $('.breadcrumb a').last();
const $link = $('a[href="' + url + '"]');
const $parent_link = $('a[href="' + $breadcrumb.attr('href') + '"]');
if ($link.length) {
$link.addClass('active');
} else if ($parent_link.length) {
$parent_link.addClass('active');
};
const $a_active = $('a.nav-link.active');
const $main_li_parent = $a_active.closest('li.nav-item.has-treeview');
const $ul_child = $main_li_parent.children('ul');
$ul_child.show();
$main_li_parent.addClass('menu-is-opening menu-open');
};
$(document).ready(function () {
// Set active status on links
setActiveLinks()
// When we use the menu, store its state in a cookie to preserve it
handleMenu();
// Add minimal changelist styling to templates that we have been unable to override (e.g MPTT)
// Needs to be here and not in change_list.js because this is the only JS we are guaranteed to run
// (as its included in base.html)
const $changeListTable = $('#changelist .results table');
if ($changeListTable.length && !$changeListTable.hasClass('table table-striped')) {
$changeListTable.addClass('table table-striped');
};
});
})(jQuery);

View File

@ -0,0 +1,188 @@
(function($) {
'use strict';
let relatedModalCounter = 0;
function checkIfInIframe() {
return window.top !== window.self;
}
// create the function that will close the modal
function dismissModal() {
if (checkIfInIframe()) {
const parentWindow = window.parent;
parentWindow.dismissModal();
return;
}
$('.related-modal-' + relatedModalCounter).modal('hide');
relatedModalCounter-=1;
}
// create the function that will show the modal
function showModal(title, body, e) {
if (checkIfInIframe()) {
const parentWindow = window.parent;
parentWindow.showModal(title, body, e);
return;
}
relatedModalCounter+=1;
$.showModal({
title: title,
body: body,
backdrop: false,
modalDialogClass: "modal-dialog-centered modal-lg",
modalClass: "fade modal-wide related-modal-" + relatedModalCounter,
onDispose: function() {
// add focus to the previous modal (if exists) when the current one is closed
var lastModal = $("div[class*='related-modal-']").last();
if (lastModal) {
lastModal.focus();
}
}
});
const modalEl = $("div[class*='related-modal-']");
const iframeEl = modalEl.find('#related-modal-iframe');
if (e.data.lookup === true) {
// set current window as iframe opener because
// the callback is called on the opener window
iframeEl.on('load', function() {
const iframeObj = $(this).get(0);
const iframeWindow = iframeObj.contentWindow;
iframeWindow.opener = window;
});
}
}
function dismissRelatedLookupModal(win, chosenId) {
const windowName = win.name;
const widgetName = windowName.replace(/^(change|add|delete|lookup)_/, '');
let widgetEl;
if (checkIfInIframe) {
// select second to last iframe in the main parent document
const secondLastIframe = $('iframe.related-iframe', win.parent.document).eq(-2);
let documentContext;
// if second to last iframe exists get its contents
if (secondLastIframe.length) {
documentContext = secondLastIframe.contents();
// else get main parent document
} else {
documentContext = $(win.parent.document);
}
// find and select widget from the specified document context
widgetEl = documentContext.find('#' + widgetName);
// else select widget from the main document
} else {
widgetEl = $('#' + widgetName);
}
const widgetVal = widgetEl.val();
if (widgetEl.hasClass('vManyToManyRawIdAdminField') && Boolean(widgetVal)) {
widgetEl.val(widgetVal + ', ' + chosenId);
} else {
widgetEl.val(chosenId);
}
dismissModal();
}
// assign functions to global variables
window.dismissRelatedObjectModal = dismissModal;
window.dismissRelatedLookupPopup = dismissRelatedLookupModal;
window.showModal = showModal;
function presentRelatedObjectModal(e) {
let linkEl = $(this);
let href = (linkEl.attr('href') || '');
if (href === '') {
return;
}
// open the popup as modal
e.preventDefault();
e.stopImmediatePropagation();
// remove focus from clicked link
linkEl.blur();
// use the clicked link id as iframe name
// it will be available as window.name in the loaded iframe
let iframeName = linkEl.attr('id');
let iframeSrc = href;
const modalTitle = linkEl.attr('title');
if (e.data.lookup !== true) {
// browsers stop loading nested iframes having the same src url
// create a random parameter and append it to the src url to prevent it
// this workaround doesn't work with related lookup url
let iframeSrcRandom = String(Math.round(Math.random() * 999999));
if (iframeSrc.indexOf('?') === -1) {
iframeSrc += '?_modal=' + iframeSrcRandom;
} else {
iframeSrc += '&_modal=' + iframeSrcRandom;
}
}
if (iframeSrc.indexOf('_popup=1') === -1) {
if (iframeSrc.indexOf('?') === -1) {
iframeSrc += '?_popup=1';
} else {
iframeSrc += '&_popup=1';
}
}
// build the iframe html
let iframeHTML = '<iframe id="related-modal-iframe" name="' + iframeName + '" src="' + iframeSrc + '" frameBorder="0" class="related-iframe"></iframe>';
// the modal css class
let iframeInternalModalClass = 'related-modal';
// if the current window is inside an iframe, it means that it is already in a modal,
// append an additional css class to the modal to offer more customization
if (window.top !== window.self) {
iframeInternalModalClass += ' related-modal__nested';
}
// open the modal using dynamic bootstrap modal
showModal(modalTitle, iframeHTML, e);
return false;
}
// listen click events on related links
function presentRelatedObjectModalOnClickOn(selector, lookup) {
let el = $(selector);
el.removeAttr('onclick');
el.unbind('click');
el.click({lookup: lookup}, presentRelatedObjectModal);
}
function init() {
presentRelatedObjectModalOnClickOn('a.related-widget-wrapper-link', false);
// raw_id_fields support
presentRelatedObjectModalOnClickOn('a.related-lookup', true);
// django-dynamic-raw-id support - #61
// https://github.com/lincolnloop/django-dynamic-raw-id
presentRelatedObjectModalOnClickOn('a.dynamic_raw_id-related-lookup', true);
}
$(document).ready(function(){
init()
});
django.jQuery(document).on('formset:added', init);
})(jQuery);

View File

@ -0,0 +1,343 @@
(function ($) {
'use strict';
const $body = $('body');
const $footer = $('footer');
const $sidebar_ul = $('aside#jazzy-sidebar nav ul:first-child');
const $sidebar = $('aside#jazzy-sidebar');
const $navbar = $('nav#jazzy-navbar');
const $logo = $('#jazzy-logo');
const $actions = $('#jazzy-actions');
const buttons = [
"primary",
"secondary",
"info",
"warning",
"danger",
"success",
]
const darkThemes = ["darkly", "cyborg", "slate", "solar", "superhero"]
window.ui_changes = window.ui_changes || {'button_classes': {}};
function miscListeners() {
$('#footer-fixed').on('click', function () {
$body.toggleClass('layout-footer-fixed');
if (this.checked) {
$('#layout-boxed:checked').click();
}
window.ui_changes['footer_fixed'] = this.checked;
});
$('#layout-boxed').on('click', function () {
$body.toggleClass('layout-boxed');
// We cannot combine these options with layout boxed
if (this.checked) {
$('#navbar-fixed:checked').click();
$('#footer-fixed:checked').click();
}
window.ui_changes['layout_boxed'] = this.checked;
});
$('#actions-fixed').on('click', function () {
$actions.toggleClass('sticky-top');
window.ui_changes['actions_sticky_top'] = this.checked;
});
// Colour pickers
$('#accent-colours div').on('click', function () {
$(this).removeClass('inactive').addClass('active').parent().find(
'div'
).not(this).removeClass('active').addClass('inactive');
const newClasses = $(this).data('classes');
$body.removeClass(function (index, className) {
return (className.match(/(^|\s)accent-\S+/g) || []).join(' ');
}).addClass(newClasses);
window.ui_changes['accent'] = newClasses;
});
$('#brand-logo-variants div').on('click', function () {
$(this).removeClass('inactive').addClass('active').parent().find(
'div'
).not(this).removeClass('active').addClass('inactive');
let newClasses = $(this).data('classes');
$logo.removeClass(function (index, className) {
return (className.match(/(^|\s)navbar-\S+/g) || []).join(' ');
}).addClass(newClasses);
if (newClasses === "") {
newClasses = false;
$(this).parent().find('div').removeClass('active inactive');
}
window.ui_changes['brand_colour'] = newClasses;
});
// show code
$("#codeBox").on('show.bs.modal', function () {
$('.modal-body code', this).html(
'JAZZMIN_UI_TWEAKS = ' + JSON.stringify(
window.ui_changes, null, 4
).replace(
/true/g, 'True'
).replace(
/false/g, 'False'
).replace(
/null/g, 'None'
)
);
});
}
function themeSpecificTweaks(theme) {
if (darkThemes.indexOf(theme) > -1) {
$('#navbar-variants .bg-dark').click();
$("#jazzmin-btn-style-primary").val('btn-primary').change();
$("#jazzmin-btn-style-secondary").val('btn-secondary').change();
$body.addClass('dark-mode');
} else {
$('#navbar-variants .bg-white').click();
$("#jazzmin-btn-style-primary").val('btn-outline-primary').change();
$("#jazzmin-btn-style-secondary").val('btn-outline-secondary').change();
$body.removeClass('dark-mode');
}
}
function themeChooserListeners() {
// Theme chooser (standard)
$("#jazzmin-theme-chooser").on('change', function () {
let $themeCSS = $('#jazzmin-theme');
// If we are using the default theme, there will be no theme css, just the bundled one in adminlte
if (!$themeCSS.length) {
const staticSrc = $('#adminlte-css').attr('href').split('vendor')[0]
$themeCSS = $('<link>').attr({
'href': staticSrc + 'vendor/bootswatch/default/bootstrap.min.css',
'rel': 'stylesheet',
'id': 'jazzmin-theme'
}).appendTo('head');
}
const currentSrc = $themeCSS.attr('href');
const currentTheme = currentSrc.split('/')[4];
let newTheme = $(this).val();
$themeCSS.attr('href', currentSrc.replace(currentTheme, newTheme));
$body.removeClass (function (index, className) {
return (className.match (/(^|\s)theme-\S+/g) || []).join(' ');
});
$body.addClass('theme-' + newTheme);
themeSpecificTweaks(newTheme);
window.ui_changes['theme'] = newTheme;
});
// Theme chooser (dark mode)
$("#jazzmin-dark-mode-theme-chooser").on('change', function () {
let $themeCSS = $('#jazzmin-dark-mode-theme');
// If we are using the default theme, there will be no theme css, just the bundled one in adminlte
if (this.value === "") {
$themeCSS.remove();
window.ui_changes['dark_mode_theme'] = null;
return
}
if (!$themeCSS.length) {
const staticSrc = $('#adminlte-css').attr('href').split('vendor')[0]
$themeCSS = $('<link>').attr({
'href': staticSrc + 'vendor/bootswatch/darkly/bootstrap.min.css',
'rel': 'stylesheet',
'id': 'jazzmin-dark-mode-theme',
'media': '(prefers-color-scheme: dark)'
}).appendTo('head');
}
const currentSrc = $themeCSS.attr('href');
const currentTheme = currentSrc.split('/')[4];
const newTheme = $(this).val();
$themeCSS.attr('href', currentSrc.replace(currentTheme, newTheme));
themeSpecificTweaks(newTheme);
window.ui_changes['dark_mode_theme'] = newTheme;
});
}
function navBarTweaksListeners() {
$('#navbar-fixed').on('click', function () {
$body.toggleClass('layout-navbar-fixed');
if (this.checked) {$('#layout-boxed:checked').click();}
window.ui_changes['navbar_fixed'] = this.checked;
});
$('#no-navbar-border').on('click', function () {
$navbar.toggleClass('border-bottom-0');
window.ui_changes['no_navbar_border'] = $navbar.hasClass('border-bottom-0');
});
// Colour picker
$('#navbar-variants div').on('click', function () {
$(this).removeClass('inactive').addClass('active').parent().find(
'div'
).not(this).removeClass('active').addClass('inactive');
const newClasses = $(this).data('classes');
$navbar.removeClass(function (index, className) {
return (className.match(/(^|\s)navbar-\S+/g) || []).join(' ');
}).addClass('navbar-expand ' + newClasses);
window.ui_changes['navbar'] = newClasses;
});
}
function sideBarTweaksListeners() {
$('#sidebar-nav-flat-style').on('click', function () {
$sidebar_ul.toggleClass('nav-flat');
window.ui_changes['sidebar_nav_flat_style'] = this.checked;
});
$('#sidebar-nav-legacy-style').on('click', function () {
$sidebar_ul.toggleClass('nav-legacy');
window.ui_changes['sidebar_nav_legacy_style'] = this.checked;
});
$('#sidebar-nav-compact').on('click', function () {
$sidebar_ul.toggleClass('nav-compact');
window.ui_changes['sidebar_nav_compact_style'] = this.checked;
});
$('#sidebar-nav-child-indent').on('click', function () {
$sidebar_ul.toggleClass('nav-child-indent');
window.ui_changes['sidebar_nav_child_indent'] = this.checked;
});
$('#main-sidebar-disable-hover-focus-auto-expand').on('click', function () {
$sidebar.toggleClass('sidebar-no-expand');
window.ui_changes['sidebar_disable_expand'] = this.checked;
});
$('#sidebar-fixed').on('click', function () {
$body.toggleClass('layout-fixed');
window.ui_changes['sidebar_fixed'] = this.checked;
});
// Colour pickers
$('#dark-sidebar-variants div, #light-sidebar-variants div').on('click', function () {
$(this).removeClass('inactive').addClass('active').parent().find(
'div'
).not(this).removeClass('active').addClass('inactive');
const newClasses = $(this).data('classes');
$sidebar.removeClass(function (index, className) {
return (className.match(/(^|\s)sidebar-[\S|-]+/g) || []).join(' ');
}).addClass(newClasses);
window.ui_changes['sidebar'] = newClasses.trim();
});
}
function smallTextListeners() {
$('#navbar-small-text').on('click', function () {
$navbar.toggleClass('text-sm');
window.ui_changes['navbar_small_text'] = this.checked;
});
$('#brand-small-text').on('click', function () {
$logo.toggleClass('text-sm');
window.ui_changes['brand_small_text'] = this.checked;
});
$('#body-small-text').on('click', function () {
$body.toggleClass('text-sm');
window.ui_changes['body_small_text'] = this.checked;
const $smallTextControls = $('#navbar-small-text, #brand-small-text, #footer-small-text, #sidebar-nav-small-text');
if (this.checked) {
window.ui_changes['navbar_small_text'] = false;
window.ui_changes['brand_small_text'] = false;
window.ui_changes['footer_small_text'] = false;
window.ui_changes['sidebar_nav_small_text'] = false;
$smallTextControls.prop({'checked': false, 'disabled': 'disabled'});
} else {
$smallTextControls.prop({'checked': false, 'disabled': ''});
}
});
$('#footer-small-text').on('click', function () {
$footer.toggleClass('text-sm');
window.ui_changes['footer_small_text'] = this.checked;
});
$('#sidebar-nav-small-text').on('click', function () {
$sidebar_ul.toggleClass('text-sm');
window.ui_changes['sidebar_nav_small_text'] = this.checked;
});
}
function buttonStyleListeners() {
buttons.forEach(function(btn) {
$("#jazzmin-btn-style-" + btn).on('change', function () {
const btnClasses = ['btn-' + btn, 'btn-outline-' + btn];
const selectorClasses = '.btn-' + btn + ', .btn-outline-' + btn;
$(selectorClasses).removeClass(btnClasses).addClass(this.value);
window.ui_changes['button_classes'][btn] = this.value;
});
});
}
function setFromExisting() {
$('#jazzmin-theme-chooser').val(window.ui_changes['theme']);
$('#jazzmin-dark-mode-theme-chooser').val(window.ui_changes['dark_mode_theme']);
$('#theme-condition').val(window.ui_changes['theme_condition']);
$('#body-small-text').get(0).checked = window.ui_changes['body_small_text'];
$('#footer-small-text').get(0).checked = window.ui_changes['footer_small_text'];
$('#sidebar-nav-small-text').get(0).checked = window.ui_changes['sidebar_nav_small_text'];
$('#sidebar-nav-legacy-style').get(0).checked = window.ui_changes['sidebar_nav_legacy_style'];
$('#sidebar-nav-compact').get(0).checked = window.ui_changes['sidebar_nav_compact_style'];
$('#sidebar-nav-child-indent').get(0).checked = window.ui_changes['sidebar_nav_child_indent'];
$('#main-sidebar-disable-hover-focus-auto-expand').get(0).checked = window.ui_changes['sidebar_disable_expand'];
$('#no-navbar-border').get(0).checked = window.ui_changes['no_navbar_border'];
$('#navbar-small-text').get(0).checked = window.ui_changes['navbar_small_text'];
$('#brand-small-text').get(0).checked = window.ui_changes['brand_small_text'];
// deactivate colours
$('#navbar-variants div, #accent-colours div, #dark-sidebar-variants div, #light-sidebar-variants div, #brand-logo-variants div').addClass('inactive');
// set button styles
buttons.forEach(function(btn) {
$("#jazzmin-btn-style-" + btn).val(window.ui_changes['button_classes'][btn]);
});
// set colours
$('#navbar-variants div[data-classes="' + window.ui_changes['navbar'] + '"]').addClass('active');
$('#accent-colours div[data-classes="' + window.ui_changes['accent'] + '"]').addClass('active');
$('#dark-sidebar-variants div[data-classes="' + window.ui_changes['sidebar'] + '"]').addClass('active');
$('#light-sidebar-variants div[data-classes="' + window.ui_changes['sidebar'] + '"]').addClass('active');
$('#brand-logo-variants div[data-classes="' + window.ui_changes['brand_colour'] + '"]').addClass('active');
}
/*
Don't call if it is inside an iframe
*/
if (!$body.hasClass("popup")) {
setFromExisting();
themeChooserListeners();
miscListeners();
navBarTweaksListeners();
sideBarTweaksListeners();
smallTextListeners();
buttonStyleListeners();
}
})(jQuery);

View File

@ -0,0 +1 @@
!function(o){"use strict";var s=0;function i(t){for(var e in this.props={title:"",body:"",footer:"",modalClass:"fade",modalDialogClass:"",options:null,onCreate:null,onDispose:null,onSubmit:null},t)this.props[e]=t[e];this.id="bootstrap-show-modal-"+s,s++,this.show()}i.prototype.createContainerElement=function(){var t=this;this.element=document.createElement("div"),this.element.id=this.id,this.element.setAttribute("class","modal "+this.props.modalClass),this.element.setAttribute("tabindex","-1"),this.element.setAttribute("role","dialog"),this.element.setAttribute("aria-labelledby",this.id),this.element.innerHTML='<div class="modal-dialog '+this.props.modalDialogClass+'" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"></h5><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"></div><div class="modal-footer"></div></div></div>',document.body.appendChild(this.element),this.titleElement=this.element.querySelector(".modal-title"),this.bodyElement=this.element.querySelector(".modal-body"),this.footerElement=this.element.querySelector(".modal-footer"),o(this.element).on("hidden.bs.modal",function(){t.dispose()}),this.props.onCreate&&this.props.onCreate(this)},i.prototype.show=function(){this.element?o(this.element).modal("show"):(this.createContainerElement(),this.props.options?o(this.element).modal(this.props.options):o(this.element).modal()),this.props.title?(o(this.titleElement).show(),this.titleElement.innerHTML=this.props.title):o(this.titleElement).hide(),this.props.body?(o(this.bodyElement).show(),this.bodyElement.innerHTML=this.props.body):o(this.bodyElement).hide(),this.props.footer?(o(this.footerElement).show(),this.footerElement.innerHTML=this.props.footer):o(this.footerElement).hide()},i.prototype.hide=function(){o(this.element).modal("hide")},i.prototype.dispose=function(){o(this.element).modal("dispose"),document.body.removeChild(this.element),this.props.onDispose&&this.props.onDispose(this)},o.extend({showModal:function(t){if(t.buttons){var e,o="";for(e in t.buttons){o+='<button type="button" class="btn btn-primary" data-value="'+e+'" data-dismiss="modal">'+t.buttons[e]+"</button>"}t.footer=o}return new i(t)},showAlert:function(t){return t.buttons={OK:"OK"},this.showModal(t)},showConfirm:function(t){return t.footer='<button class="btn btn-secondary btn-false btn-cancel">'+t.textFalse+'</button><button class="btn btn-primary btn-true">'+t.textTrue+"</button>",t.onCreate=function(e){o(e.element).on("click",".btn",function(t){t.preventDefault(),e.hide(),e.props.onSubmit(-1!==t.target.getAttribute("class").indexOf("btn-true"),e)})},this.showModal(t)}})}(jQuery);

View File

@ -0,0 +1,65 @@
(function($) {
'use strict';
$(function() {
console.log("Admin Resource JS loaded");
var $levelSelect = $('#id_educational_level');
var $subjectSelect = $('#id_subject');
// Check if elements exist
if (!$levelSelect.length) {
console.warn("Educational Level field #id_educational_level not found");
}
if (!$subjectSelect.length) {
console.warn("Subject field #id_subject not found");
}
if (!$levelSelect.length || !$subjectSelect.length) return;
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
function updateSubjects(levelId, currentSubjectId) {
console.log("Updating subjects for level:", levelId);
// If no level selected, clear subjects
if (!levelId) {
$subjectSelect.html('<option value="">---------</option>');
return;
}
$.getJSON('/ajax/get-subjects-by-level/', {level_id: levelId}, function(data) {
console.log("Subjects fetched:", data.length);
var options = '<option value="">---------</option>';
$.each(data, function(index, subject) {
var selected = '';
// If we have a current subject ID, check if it matches
if (currentSubjectId && subject.id.toString() === currentSubjectId.toString()) {
selected = ' selected';
}
var name = isArabic ? subject.name_ar : subject.name_en;
options += '<option value="' + subject.id + '"' + selected + '>' + name + '</option>';
});
$subjectSelect.html(options);
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ", " + error;
console.error("Request Failed: " + err);
});
}
$levelSelect.on('change', function() {
var levelId = $(this).val();
// When level changes, we reset the subject selection
updateSubjects(levelId, null);
});
// Initial load
var initialLevel = $levelSelect.val();
var initialSubject = $subjectSelect.val();
if (initialLevel) {
updateSubjects(initialLevel, initialSubject);
} else {
if (!initialSubject) {
$subjectSelect.html('<option value="">---------</option>');
}
}
});
})(django.jQuery);

View File

@ -0,0 +1,78 @@
(function($) {
'use strict';
$(function() {
var $levelSelect = $('#id_level');
var $subjectSelect = $('#id_subscribed_subjects');
// Check if we are in the admin change form
if (!$levelSelect.length || !$subjectSelect.length) return;
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
function updateSubjects(levelId, selectedValues) {
if (!levelId) {
$subjectSelect.html('');
refreshSelectFilter();
return;
}
$.getJSON('/ajax/get-subjects-by-level/', {level_id: levelId}, function(data) {
var options = '';
$.each(data, function(index, subject) {
var selected = '';
if (selectedValues && selectedValues.indexOf(subject.id.toString()) !== -1) {
selected = ' selected';
}
var name = isArabic ? subject.name_ar : subject.name_en;
options += '<option value="' + subject.id + '"' + selected + '>' + name + '</option>';
});
$subjectSelect.html(options);
refreshSelectFilter();
});
}
function refreshSelectFilter() {
// Django's filter_horizontal uses SelectFilter2
if (typeof SelectFilter2 !== 'undefined') {
var fieldName = 'subscribed_subjects';
// Remove the existing widget elements
$('.selector').has('#id_' + fieldName + '_from').remove();
// Re-initialize it
// SelectFilter2.init(field_id, field_name, is_stacked)
SelectFilter2.init('id_' + fieldName, fieldName, false);
} else if (typeof SelectFilter !== 'undefined') {
var fieldName = 'subscribed_subjects';
$('.selector').has('#id_' + fieldName + '_from').remove();
SelectFilter.init('id_' + fieldName, fieldName, false);
}
}
$levelSelect.on('change', function() {
var selectedValues = [];
// When level changes, we might want to keep currently selected subjects if they still belong to the level?
// Actually, usually when level changes, we reset subjects or only keep valid ones.
// For now, let's reset to avoid confusion, or the user can re-select.
updateSubjects($(this).val(), []);
});
// If we are editing an existing student, we should filter the subjects on load
// but KEEP the currently selected ones.
if ($levelSelect.val()) {
// Get currently selected values from the "chosen" side if possible,
// or from the original select before it was widgetized.
var selectedValues = [];
$('#id_subscribed_subjects_to option').each(function() {
selectedValues.push($(this).val());
});
// If empty (new form or nothing selected), try the main select
if (selectedValues.length === 0) {
$subjectSelect.find('option:selected').each(function() {
selectedValues.push($(this).val());
});
}
updateSubjects($levelSelect.val(), selectedValues);
}
});
})(django.jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long