addin driver rating
This commit is contained in:
parent
a3174399c8
commit
121c77dd5f
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2,7 +2,7 @@ from django import forms
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
from .models import Profile, Parcel, Country, Governate, City
|
from .models import Profile, Parcel, Country, Governate, City, DriverRating
|
||||||
|
|
||||||
class ContactForm(forms.Form):
|
class ContactForm(forms.Form):
|
||||||
name = forms.CharField(max_length=100, label=_("Name"), widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Your Name')}))
|
name = forms.CharField(max_length=100, label=_("Name"), widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Your Name')}))
|
||||||
@ -321,4 +321,17 @@ class ParcelForm(forms.ModelForm):
|
|||||||
if phone_code and phone_number:
|
if phone_code and phone_number:
|
||||||
if not phone_number.startswith(phone_code.phone_code):
|
if not phone_number.startswith(phone_code.phone_code):
|
||||||
cleaned_data['receiver_phone'] = f"{phone_code.phone_code}{phone_number}"
|
cleaned_data['receiver_phone'] = f"{phone_code.phone_code}{phone_number}"
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
class DriverRatingForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = DriverRating
|
||||||
|
fields = ['rating', 'comment']
|
||||||
|
widgets = {
|
||||||
|
'rating': forms.RadioSelect(attrs={'class': 'rating-stars'}),
|
||||||
|
'comment': forms.Textarea(attrs={'class': 'form-control', 'rows': 4, 'placeholder': _('Write your review here...')}),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
'rating': _('Rating'),
|
||||||
|
'comment': _('Comment'),
|
||||||
|
}
|
||||||
|
|||||||
32
core/migrations/0017_driverrating.py
Normal file
32
core/migrations/0017_driverrating.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 04:58
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0016_country_phone_code'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DriverRating',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('rating', models.PositiveSmallIntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], verbose_name='Rating')),
|
||||||
|
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||||
|
('driver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_ratings', to=settings.AUTH_USER_MODEL, verbose_name='Driver')),
|
||||||
|
('parcel', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='rating', to='core.parcel', verbose_name='Parcel')),
|
||||||
|
('shipper', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='given_ratings', to=settings.AUTH_USER_MODEL, verbose_name='Shipper')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Driver Rating',
|
||||||
|
'verbose_name_plural': 'Driver Ratings',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
core/migrations/__pycache__/0017_driverrating.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0017_driverrating.cpython-311.pyc
Normal file
Binary file not shown.
@ -79,6 +79,19 @@ class Profile(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user.username} - {self.get_role_display()}"
|
return f"{self.user.username} - {self.get_role_display()}"
|
||||||
|
|
||||||
|
def get_average_rating(self):
|
||||||
|
if self.role != 'car_owner':
|
||||||
|
return None
|
||||||
|
ratings = self.user.received_ratings.all()
|
||||||
|
if not ratings:
|
||||||
|
return 0
|
||||||
|
return sum(r.rating for r in ratings) / len(ratings)
|
||||||
|
|
||||||
|
def get_rating_count(self):
|
||||||
|
if self.role != 'car_owner':
|
||||||
|
return 0
|
||||||
|
return self.user.received_ratings.count()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Profile')
|
verbose_name = _('Profile')
|
||||||
@ -241,4 +254,19 @@ class Testimonial(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Testimonial')
|
verbose_name = _('Testimonial')
|
||||||
verbose_name_plural = _('Testimonials')
|
verbose_name_plural = _('Testimonials')
|
||||||
ordering = ['-created_at']
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
class DriverRating(models.Model):
|
||||||
|
parcel = models.OneToOneField(Parcel, on_delete=models.CASCADE, related_name='rating', verbose_name=_('Parcel'))
|
||||||
|
driver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_ratings', verbose_name=_('Driver'))
|
||||||
|
shipper = models.ForeignKey(User, on_delete=models.CASCADE, related_name='given_ratings', verbose_name=_('Shipper'))
|
||||||
|
rating = models.PositiveSmallIntegerField(_('Rating'), choices=[(i, str(i)) for i in range(1, 6)])
|
||||||
|
comment = models.TextField(_('Comment'), blank=True)
|
||||||
|
created_at = models.DateTimeField(_('Created At'), auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Rating {self.rating} for {self.driver.username} by {self.shipper.username}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Driver Rating')
|
||||||
|
verbose_name_plural = _('Driver Ratings')
|
||||||
|
|||||||
17
core/templates/core/emails/password_reset_email.txt
Normal file
17
core/templates/core/emails/password_reset_email.txt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% trans "Reset Your Password" %}
|
||||||
|
|
||||||
|
{% trans "Hello," %}
|
||||||
|
|
||||||
|
{% trans "You are receiving this email because you requested a password reset for your account at" %} {{ site_name }}.
|
||||||
|
|
||||||
|
{% trans "Please go to the following page and choose a new password:" %}
|
||||||
|
|
||||||
|
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||||
|
|
||||||
|
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
|
||||||
|
|
||||||
|
{% trans "Thanks," %}
|
||||||
|
{% trans "The" %} {{ site_name }} {% trans "Team" %}
|
||||||
|
|
||||||
|
{% trans "If you did not request this, please ignore this email." %}
|
||||||
@ -1 +1 @@
|
|||||||
{% trans "Password reset on" %} {{ site_name }}
|
{% load i18n %}{% trans "Password reset on" %} {{ site_name }}
|
||||||
@ -60,6 +60,49 @@
|
|||||||
<p class="fw-semibold">{{ profile.address|default:"-" }}</p>
|
<p class="fw-semibold">{{ profile.address|default:"-" }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Rating Section (Drivers Only) -->
|
||||||
|
{% if profile.role == 'car_owner' %}
|
||||||
|
<hr class="my-5">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h4 class="fw-bold">{% trans "Driver Rating" %}</h4>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<div class="display-4 fw-bold me-3">{{ profile.get_average_rating|default:"0.0"|floatformat:1 }}</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-warning fs-5">
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
{{ profile.get_rating_count }} {% trans "reviews" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if reviews %}
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
{% for review in reviews %}
|
||||||
|
<div class="list-group-item px-0 py-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div>
|
||||||
|
<span class="fw-bold">{{ review.shipper.first_name }}</span>
|
||||||
|
<small class="text-muted ms-2">{{ review.created_at|date:"M d, Y" }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-warning">
|
||||||
|
{{ review.rating }} <i class="fas fa-star small"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if review.comment %}
|
||||||
|
<p class="mb-0 text-muted">{{ review.comment }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">{% trans "No reviews yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,4 +118,4 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
126
core/templates/core/rate_driver.html
Normal file
126
core/templates/core/rate_driver.html
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n static core_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8 col-lg-6">
|
||||||
|
<div class="card shadow-sm border-0 rounded-4">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h2 class="fw-bold mb-3">{% trans "Rate Your Experience" %}</h2>
|
||||||
|
<p class="text-muted">
|
||||||
|
{% trans "How was the service provided by" %}
|
||||||
|
<strong>{{ parcel.carrier.username }}</strong>?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Star Rating -->
|
||||||
|
<div class="mb-4 text-center">
|
||||||
|
<label class="form-label d-block mb-2">{% trans "Rating" %}</label>
|
||||||
|
<div class="rating-stars">
|
||||||
|
{% for radio in form.rating %}
|
||||||
|
{{ radio.tag }}
|
||||||
|
<label for="{{ radio.id_for_label }}" class="star-label">
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if form.rating.errors %}
|
||||||
|
<div class="text-danger small mt-1">
|
||||||
|
{{ form.rating.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comment -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="{{ form.comment.id_for_label }}" class="form-label">{% trans "Comment" %}</label>
|
||||||
|
{{ form.comment }}
|
||||||
|
{% if form.comment.errors %}
|
||||||
|
<div class="text-danger small mt-1">
|
||||||
|
{{ form.comment.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg rounded-pill">
|
||||||
|
{% trans "Submit Review" %}
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'dashboard' %}" class="btn btn-light rounded-pill">
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Star Rating Styles */
|
||||||
|
.rating-stars {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-stars input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-stars label {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #ddd;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-stars label:hover,
|
||||||
|
.rating-stars label:hover ~ label,
|
||||||
|
.rating-stars input[type="radio"]:checked ~ label {
|
||||||
|
color: #ffc107; /* Bootstrap warning color (yellow) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reverse order fix for logic but display is reversed,
|
||||||
|
so we might need to adjust logic or just use flex-direction: row-reverse
|
||||||
|
and ensure inputs are 5,4,3,2,1 order?
|
||||||
|
Django RadioSelect usually outputs in order 1,2,3,4,5.
|
||||||
|
If we reverse via flex, 1 is rightmost. That's wrong.
|
||||||
|
We need 1 on left.
|
||||||
|
|
||||||
|
Actually simpler pure CSS rating usually involves flex-reverse.
|
||||||
|
Let's check Django output.
|
||||||
|
Django outputs 1, 2, 3, 4, 5.
|
||||||
|
If we use flex-direction: row-reverse, it shows 5 4 3 2 1.
|
||||||
|
Hovering 5 highlights 5, 4, 3, 2, 1. Correct.
|
||||||
|
|
||||||
|
So visually:
|
||||||
|
[5] [4] [3] [2] [1]
|
||||||
|
|
||||||
|
If I click left-most (5), it checks 5.
|
||||||
|
Wait, users expect [1] [2] [3] [4] [5].
|
||||||
|
|
||||||
|
If I use row-reverse:
|
||||||
|
DOM: 1 2 3 4 5
|
||||||
|
Visual: 5 4 3 2 1
|
||||||
|
|
||||||
|
This is counter-intuitive for LTR.
|
||||||
|
|
||||||
|
Let's use a different technique.
|
||||||
|
Use :checked ~ label selector, but then we need the input BEFORE the label.
|
||||||
|
Django widgets renders input then label?
|
||||||
|
{{ radio.tag }} is input. <label> we write manually.
|
||||||
|
|
||||||
|
So DOM is: Input1 Label1 Input2 Label2 ...
|
||||||
|
|
||||||
|
We can style this.
|
||||||
|
*/
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n core_tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
@ -84,10 +84,12 @@
|
|||||||
<th>{% trans "Carrier" %}</th>
|
<th>{% trans "Carrier" %}</th>
|
||||||
<th>{% trans "Bid/Price" %}</th>
|
<th>{% trans "Bid/Price" %}</th>
|
||||||
<th>{% trans "Status" %}</th>
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Action" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for parcel in history_parcels %}
|
{% for parcel in history_parcels %}
|
||||||
|
{% get_rating parcel as rating %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="ps-4">{{ parcel.created_at|date:"Y-m-d" }}</td>
|
<td class="ps-4">{{ parcel.created_at|date:"Y-m-d" }}</td>
|
||||||
<td><span class="badge bg-light text-dark">#{{ parcel.tracking_number }}</span></td>
|
<td><span class="badge bg-light text-dark">#{{ parcel.tracking_number }}</span></td>
|
||||||
@ -105,6 +107,19 @@
|
|||||||
{{ parcel.get_status_display }}
|
{{ parcel.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if parcel.status == 'delivered' and parcel.carrier %}
|
||||||
|
{% if not rating %}
|
||||||
|
<a href="{% url 'rate_driver' parcel.id %}" class="btn btn-sm btn-outline-warning">
|
||||||
|
<i class="fas fa-star"></i> {% trans "Rate Driver" %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-warning" title="{{ rating.comment }}">
|
||||||
|
{{ rating.rating }} <i class="fas fa-star"></i>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -120,4 +135,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Binary file not shown.
@ -1,8 +1,15 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from core.models import PlatformProfile
|
from core.models import PlatformProfile, DriverRating
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def get_platform_profile():
|
def get_platform_profile():
|
||||||
return PlatformProfile.objects.first()
|
return PlatformProfile.objects.first()
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def get_rating(parcel):
|
||||||
|
try:
|
||||||
|
return parcel.rating
|
||||||
|
except:
|
||||||
|
return None
|
||||||
@ -12,7 +12,8 @@ urlpatterns = [
|
|||||||
# Password Reset URLs
|
# Password Reset URLs
|
||||||
path('password-reset/', auth_views.PasswordResetView.as_view(
|
path('password-reset/', auth_views.PasswordResetView.as_view(
|
||||||
template_name='core/password_reset_form.html',
|
template_name='core/password_reset_form.html',
|
||||||
email_template_name='core/emails/password_reset_email.html',
|
email_template_name='core/emails/password_reset_email.txt',
|
||||||
|
html_email_template_name='core/emails/password_reset_email.html',
|
||||||
subject_template_name='core/emails/password_reset_subject.txt',
|
subject_template_name='core/emails/password_reset_subject.txt',
|
||||||
success_url='/password-reset/done/'
|
success_url='/password-reset/done/'
|
||||||
), name='password_reset'),
|
), name='password_reset'),
|
||||||
@ -48,4 +49,4 @@ urlpatterns = [
|
|||||||
path('profile/', views.profile_view, name='profile'),
|
path('profile/', views.profile_view, name='profile'),
|
||||||
path('profile/edit/', views.edit_profile, name='edit_profile'),
|
path('profile/edit/', views.edit_profile, name='edit_profile'),
|
||||||
path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'),
|
path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -3,8 +3,8 @@ from django.contrib.auth import login, authenticate, logout
|
|||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile, Testimonial
|
from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile, Testimonial, DriverRating
|
||||||
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm
|
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm, DriverRatingForm
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -319,7 +319,15 @@ def contact(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def profile_view(request):
|
def profile_view(request):
|
||||||
return render(request, 'core/profile.html', {'profile': request.user.profile})
|
profile = request.user.profile
|
||||||
|
reviews = []
|
||||||
|
if profile.role == 'car_owner':
|
||||||
|
reviews = request.user.received_ratings.all().order_by('-created_at')
|
||||||
|
|
||||||
|
return render(request, 'core/profile.html', {
|
||||||
|
'profile': profile,
|
||||||
|
'reviews': reviews
|
||||||
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_profile(request):
|
def edit_profile(request):
|
||||||
@ -425,4 +433,43 @@ def verify_otp_view(request):
|
|||||||
except OTPVerification.DoesNotExist:
|
except OTPVerification.DoesNotExist:
|
||||||
messages.error(request, _("Invalid code."))
|
messages.error(request, _("Invalid code."))
|
||||||
|
|
||||||
return render(request, 'core/verify_otp.html')
|
return render(request, 'core/verify_otp.html')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def rate_driver(request, parcel_id):
|
||||||
|
parcel = get_object_or_404(Parcel, id=parcel_id)
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
if parcel.shipper != request.user:
|
||||||
|
messages.error(request, _("You are not authorized to rate this shipment."))
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
if parcel.status != 'delivered':
|
||||||
|
messages.error(request, _("You can only rate delivered shipments."))
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
if not parcel.carrier:
|
||||||
|
messages.error(request, _("No driver was assigned to this shipment."))
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
if hasattr(parcel, 'rating'):
|
||||||
|
messages.info(request, _("You have already rated this shipment."))
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = DriverRatingForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
rating = form.save(commit=False)
|
||||||
|
rating.parcel = parcel
|
||||||
|
rating.driver = parcel.carrier
|
||||||
|
rating.shipper = request.user
|
||||||
|
rating.save()
|
||||||
|
messages.success(request, _("Thank you for your feedback!"))
|
||||||
|
return redirect('dashboard')
|
||||||
|
else:
|
||||||
|
form = DriverRatingForm()
|
||||||
|
|
||||||
|
return render(request, 'core/rate_driver.html', {
|
||||||
|
'form': form,
|
||||||
|
'parcel': parcel
|
||||||
|
})
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user