adding banner

This commit is contained in:
Flatlogic Bot 2026-01-23 17:23:29 +00:00
parent 9964306747
commit 08da7e84a0
11 changed files with 128 additions and 6 deletions

View File

@ -4,7 +4,7 @@ from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from .models import Profile, Truck, Shipment, Bid, Message, WhatsAppConfig, Country, City, TruckType, AppSetting
from .models import Profile, Truck, Shipment, Bid, Message, WhatsAppConfig, Country, City, TruckType, AppSetting, Banner
from .whatsapp import send_whatsapp_message
@admin.register(Country)
@ -97,3 +97,9 @@ class AppSettingAdmin(admin.ModelAdmin):
if self.model.objects.exists():
return False
return super().has_add_permission(request)
@admin.register(Banner)
class BannerAdmin(admin.ModelAdmin):
list_display = ('title', 'order', 'is_active', 'created_at')
list_editable = ('order', 'is_active')
search_fields = ('title', 'title_ar', 'subtitle', 'subtitle_ar')

View File

@ -0,0 +1,33 @@
# Generated by Django 5.2.7 on 2026-01-23 17:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0014_appsetting'),
]
operations = [
migrations.CreateModel(
name='Banner',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200, verbose_name='Title (EN)')),
('title_ar', models.CharField(blank=True, max_length=200, verbose_name='Title (AR)')),
('subtitle', models.CharField(blank=True, max_length=255, verbose_name='Subtitle (EN)')),
('subtitle_ar', models.CharField(blank=True, max_length=255, verbose_name='Subtitle (AR)')),
('image', models.ImageField(upload_to='banners/', verbose_name='Banner Image')),
('link', models.URLField(blank=True, help_text='Internal or external URL', null=True, verbose_name='Link URL')),
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
('order', models.PositiveIntegerField(default=0, verbose_name='Order')),
('created_at', models.DateTimeField(auto_now_add=True)),
],
options={
'verbose_name': 'Banner',
'verbose_name_plural': 'Banners',
'ordering': ['order', '-created_at'],
},
),
]

View File

@ -248,6 +248,37 @@ class AppSetting(models.Model):
def __str__(self):
return self.app_name
class Banner(models.Model):
title = models.CharField(_('Title (EN)'), max_length=200)
title_ar = models.CharField(_('Title (AR)'), max_length=200, blank=True)
subtitle = models.CharField(_('Subtitle (EN)'), max_length=255, blank=True)
subtitle_ar = models.CharField(_('Subtitle (AR)'), max_length=255, blank=True)
image = models.ImageField(_('Banner Image'), upload_to='banners/')
link = models.URLField(_('Link URL'), blank=True, null=True, help_text=_("Internal or external URL"))
is_active = models.BooleanField(_('Is Active'), default=True)
order = models.PositiveIntegerField(_('Order'), default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = _('Banner')
verbose_name_plural = _('Banners')
ordering = ['order', '-created_at']
def __str__(self):
return self.title
@property
def display_title(self):
if get_language() == 'ar' and self.title_ar:
return self.title_ar
return self.title
@property
def display_subtitle(self):
if get_language() == 'ar' and self.subtitle_ar:
return self.subtitle_ar
return self.subtitle
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
@ -276,4 +307,4 @@ def sync_user_groups(sender, instance, **kwargs):
instance.user.groups.remove(*other_groups)
# Add user to the correct group
instance.user.groups.add(group)
instance.user.groups.add(group)

View File

@ -2,7 +2,56 @@
{% load i18n static %}
{% block content %}
<!-- Hero Section -->
{% if banners %}
<!-- Carousel Section -->
<section class="banner-carousel mb-4 mt-n1">
<div id="mainBannerCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
{% for banner in banners %}
<button type="button" data-bs-target="#mainBannerCarousel" data-bs-slide-to="{{ forloop.counter0 }}" {% if forloop.first %}class="active" aria-current="true"{% endif %} aria-label="Slide {{ forloop.counter }}"></button>
{% endfor %}
</div>
<div class="carousel-inner">
{% for banner in banners %}
<div class="carousel-item {% if forloop.first %}active{% endif %}" data-bs-interval="5000">
<div class="banner-wrapper position-relative" style="height: 450px; overflow: hidden;">
<img src="{{ banner.image.url }}" class="d-block w-100 h-100" style="object-fit: cover;" alt="{{ banner.display_title }}">
<div class="carousel-caption d-none d-md-block text-start start-0 bottom-0 mb-5 ms-5 p-4 rounded" style="background: rgba(0,0,0,0.5); backdrop-filter: blur(5px); max-width: 600px;">
<h2 class="fw-bold display-6">{{ banner.display_title }}</h2>
<p class="lead">{{ banner.display_subtitle }}</p>
{% if banner.link %}
<a href="{{ banner.link }}" class="btn btn-primary btn-lg px-4">{% trans "Learn More" %}</a>
{% endif %}
</div>
</div>
<!-- Mobile caption visible only on small screens below the image -->
<div class="d-md-none bg-dark text-white p-4 text-center">
<h3 class="fw-bold">{{ banner.display_title }}</h3>
<p>{{ banner.display_subtitle }}</p>
{% if banner.link %}
<a href="{{ banner.link }}" class="btn btn-primary">{% trans "Learn More" %}</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% if banners.count > 1 %}
<button class="carousel-control-prev" type="button" data-bs-target="#mainBannerCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#mainBannerCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
{% endif %}
</div>
</section>
{% endif %}
<!-- Hero Section (Only show if no banners, or maybe as a secondary section?) -->
{% if not banners %}
<section class="hero-section py-5">
<div class="container">
<div class="row align-items-center">
@ -25,6 +74,7 @@
</div>
</div>
</section>
{% endif %}
<!-- Role Selection Section -->
<section class="py-5 bg-light" id="how-it-works">
@ -139,4 +189,4 @@
</div>
</section>
{% endblock %}
{% endblock %}

View File

@ -2,7 +2,7 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, authenticate, logout
from django.utils import timezone
from .models import Profile, Truck, Shipment, Bid, Message, OTPCode, Country, City, AppSetting
from .models import Profile, Truck, Shipment, Bid, Message, OTPCode, Country, City, AppSetting, Banner
from .forms import TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm, ShipperOfferForm
from django.contrib import messages
from django.utils.translation import gettext as _
@ -13,8 +13,10 @@ from django.contrib.auth.forms import AuthenticationForm
def home(request):
"""Render the landing screen for MASAR CARGO."""
banners = Banner.objects.filter(is_active=True)
context = {
"deployment_timestamp": timezone.now().timestamp(),
"banners": banners,
}
return render(request, "core/index.html", context)
@ -397,4 +399,4 @@ def terms_of_service(request):
'content': app_settings.terms_of_service if app_settings else _("Terms of service are coming soon.")
}
}
return render(request, 'core/article_detail.html', context)
return render(request, 'core/article_detail.html', context)

BIN
media/banners/banner_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
media/banners/banner_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB