Autosave: 20260203-185239
@ -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,
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -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
|
||||
0
core/management/__init__.py
Normal file
BIN
core/management/__pycache__/__init__.cpython-311.pyc
Normal file
0
core/management/commands/__init__.py
Normal file
BIN
core/management/commands/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
core/management/commands/__pycache__/seed_data.cpython-311.pyc
Normal file
58
core/management/commands/seed_data.py
Normal 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'))
|
||||
78
core/migrations/0001_initial.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal 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
|
||||
@ -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>© 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>
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
119
core/templates/core/subject_detail.html
Normal 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 %}
|
||||
@ -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'),
|
||||
]
|
||||
|
||||
@ -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
@ -1,3 +1,4 @@
|
||||
Django==5.2.7
|
||||
mysqlclient==2.2.7
|
||||
python-dotenv==1.1.1
|
||||
django-jazzmin
|
||||
|
||||
65
static/js/admin_resource.js
Normal 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);
|
||||
78
static/js/admin_student.js
Normal 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);
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
})();
|
||||
927
staticfiles/jazzmin/css/main.css
Normal 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%;
|
||||
}
|
||||
14
staticfiles/jazzmin/img/calendar-icons.svg
Normal 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 |
23
staticfiles/jazzmin/img/default-log.svg
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
staticfiles/jazzmin/img/default.jpg
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
9
staticfiles/jazzmin/img/icon-calendar.svg
Normal 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 |
3
staticfiles/jazzmin/img/icon-changelink.svg
Normal 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 |
34
staticfiles/jazzmin/img/selector-icons.svg
Normal 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 |
151
staticfiles/jazzmin/js/change_form.js
Normal 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);
|
||||
64
staticfiles/jazzmin/js/change_list.js
Normal 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);
|
||||
67
staticfiles/jazzmin/js/main.js
Normal 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);
|
||||
188
staticfiles/jazzmin/js/related-modal.js
Normal 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);
|
||||
343
staticfiles/jazzmin/js/ui-builder.js
Normal 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);
|
||||
1
staticfiles/jazzmin/plugins/bootstrap-show-modal/bootstrap-show-modal.min.js
vendored
Normal 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">×</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);
|
||||
65
staticfiles/js/admin_resource.js
Normal 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);
|
||||
78
staticfiles/js/admin_student.js
Normal 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);
|
||||
12
staticfiles/vendor/adminlte/css/adminlte.min.css
vendored
Normal file
1
staticfiles/vendor/adminlte/css/adminlte.min.css.map
vendored
Normal file
BIN
staticfiles/vendor/adminlte/img/AdminLTELogo.png
vendored
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
staticfiles/vendor/adminlte/img/icons.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
staticfiles/vendor/adminlte/img/user2-160x160.jpg
vendored
Normal file
|
After Width: | Height: | Size: 5.2 KiB |