adding testimonial
This commit is contained in:
parent
d8387d341e
commit
08e8aa82d4
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile
|
||||
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import path, reverse
|
||||
from django.shortcuts import render
|
||||
@ -124,6 +124,12 @@ class PlatformProfileAdmin(admin.ModelAdmin):
|
||||
fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),)
|
||||
return fieldsets
|
||||
|
||||
class TestimonialAdmin(admin.ModelAdmin):
|
||||
list_display = ('name_en', 'role_en', 'is_active', 'created_at')
|
||||
list_filter = ('is_active', 'created_at')
|
||||
search_fields = ('name_en', 'name_ar', 'content_en', 'content_ar')
|
||||
list_editable = ('is_active',)
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, CustomUserAdmin)
|
||||
admin.site.register(Parcel, ParcelAdmin)
|
||||
@ -131,3 +137,4 @@ admin.site.register(Country)
|
||||
admin.site.register(Governate)
|
||||
admin.site.register(City)
|
||||
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
||||
admin.site.register(Testimonial, TestimonialAdmin)
|
||||
34
core/migrations/0015_testimonial.py
Normal file
34
core/migrations/0015_testimonial.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-25 16:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0014_alter_otpverification_purpose'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Testimonial',
|
||||
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)')),
|
||||
('role_en', models.CharField(max_length=100, verbose_name='Role (English)')),
|
||||
('role_ar', models.CharField(max_length=100, verbose_name='Role (Arabic)')),
|
||||
('content_en', models.TextField(verbose_name='Testimony (English)')),
|
||||
('content_ar', models.TextField(verbose_name='Testimony (Arabic)')),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='testimonials/', verbose_name='Image')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Testimonial',
|
||||
'verbose_name_plural': 'Testimonials',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0015_testimonial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0015_testimonial.cpython-311.pyc
Normal file
Binary file not shown.
@ -208,4 +208,36 @@ class OTPVerification(models.Model):
|
||||
|
||||
def is_valid(self):
|
||||
# OTP valid for 10 minutes
|
||||
return self.created_at >= timezone.now() - timezone.timedelta(minutes=10)
|
||||
return self.created_at >= timezone.now() - timezone.timedelta(minutes=10)
|
||||
|
||||
class Testimonial(models.Model):
|
||||
name_en = models.CharField(_('Name (English)'), max_length=100)
|
||||
name_ar = models.CharField(_('Name (Arabic)'), max_length=100)
|
||||
role_en = models.CharField(_('Role (English)'), max_length=100)
|
||||
role_ar = models.CharField(_('Role (Arabic)'), max_length=100)
|
||||
content_en = models.TextField(_('Testimony (English)'))
|
||||
content_ar = models.TextField(_('Testimony (Arabic)'))
|
||||
image = models.ImageField(_('Image'), upload_to='testimonials/', blank=True, null=True)
|
||||
is_active = models.BooleanField(_('Active'), default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.name_ar if get_language() == 'ar' else self.name_en
|
||||
|
||||
@property
|
||||
def role(self):
|
||||
return self.role_ar if get_language() == 'ar' else self.role_en
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
return self.content_ar if get_language() == 'ar' else self.content_en
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Testimonial')
|
||||
verbose_name_plural = _('Testimonials')
|
||||
ordering = ['-created_at']
|
||||
|
||||
@ -86,6 +86,38 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Testimonials Section -->
|
||||
{% if testimonials %}
|
||||
<section class="py-5 bg-light">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-5">{% trans "What Our Users Say" %}</h2>
|
||||
<p class="text-muted">{% trans "Real stories from our community" %}</p>
|
||||
</div>
|
||||
<div class="row g-4 justify-content-center">
|
||||
{% for testimonial in testimonials %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body p-4 text-center">
|
||||
{% if testimonial.image %}
|
||||
<img src="{{ testimonial.image.url }}" alt="{{ testimonial.name }}" class="rounded-circle mb-3" width="80" height="80" style="object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center mx-auto mb-3" style="width: 80px; height: 80px; font-size: 2rem;">
|
||||
{{ testimonial.name|first|upper }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h5 class="card-title">{{ testimonial.name }}</h5>
|
||||
<p class="text-muted small mb-3">{{ testimonial.role }}</p>
|
||||
<p class="card-text fst-italic">"{{ testimonial.content }}"</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="py-5" style="background-color: var(--primary-dark); color: white;">
|
||||
<div class="container py-4 text-center">
|
||||
|
||||
@ -3,7 +3,7 @@ from django.contrib.auth import login, authenticate, logout
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile
|
||||
from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile, Testimonial
|
||||
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import get_language
|
||||
@ -34,10 +34,13 @@ def index(request):
|
||||
except Parcel.DoesNotExist:
|
||||
error = _("Parcel not found.")
|
||||
|
||||
testimonials = Testimonial.objects.filter(is_active=True)
|
||||
|
||||
return render(request, 'core/index.html', {
|
||||
'parcel': parcel,
|
||||
'error': error,
|
||||
'tracking_id': tracking_id
|
||||
'tracking_id': tracking_id,
|
||||
'testimonials': testimonials
|
||||
})
|
||||
|
||||
def register(request):
|
||||
@ -391,4 +394,4 @@ def verify_otp_view(request):
|
||||
except OTPVerification.DoesNotExist:
|
||||
messages.error(request, _("Invalid code."))
|
||||
|
||||
return render(request, 'core/verify_otp.html')
|
||||
return render(request, 'core/verify_otp.html')
|
||||
Loading…
x
Reference in New Issue
Block a user