Browse Source

course fileds updated for multilangual feat . serializers synced and dropdown values handle multilangual fileds statically

master
Mohsen Taba 2 months ago
parent
commit
81e3c73fde
  1. 38
      apps/course/migrations/0009_alter_course_description_and_more.py
  2. 204
      apps/course/models/course.py
  3. 16
      apps/course/serializers/course.py
  4. 4
      apps/course/views/admin.py

38
apps/course/migrations/0009_alter_course_description_and_more.py

@ -0,0 +1,38 @@
# Generated by Django 5.2.12 on 2026-06-06 09:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0008_remove_course_course_cour_status_57ffd9_idx_and_more'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(
sql=[
"ALTER TABLE course_course ALTER COLUMN description TYPE jsonb USING CASE WHEN description IS NULL OR description = '' THEN '[]'::jsonb ELSE jsonb_build_array(jsonb_build_object('title', description, 'language_code', 'en')) END;",
"ALTER TABLE course_course ALTER COLUMN short_description TYPE jsonb USING CASE WHEN short_description IS NULL OR short_description = '' THEN '[]'::jsonb ELSE jsonb_build_array(jsonb_build_object('title', short_description, 'language_code', 'en')) END;"
],
reverse_sql=[
"ALTER TABLE course_course ALTER COLUMN description TYPE text USING CASE WHEN jsonb_array_length(description) > 0 THEN description->0->>'title' ELSE '' END;",
"ALTER TABLE course_course ALTER COLUMN short_description TYPE varchar(500) USING CASE WHEN jsonb_array_length(short_description) > 0 THEN LEFT(short_description->0->>'title', 500) ELSE '' END;"
]
)
],
state_operations=[
migrations.AlterField(
model_name='course',
name='description',
field=models.JSONField(blank=True, default=list, null=True, verbose_name='Course Description'),
),
migrations.AlterField(
model_name='course',
name='short_description',
field=models.JSONField(blank=True, default=list, null=True, verbose_name='Short Description'),
),
]
)
]

204
apps/course/models/course.py

@ -113,6 +113,130 @@ class CourseCategory(models.Model):
def course_count(self):
return self.courses.exclude(status="inactive").count()
LEVEL_TRANSLATIONS = {
'beginner': {
'en': 'Beginner',
'ru': 'Начальный',
},
'mid': {
'en': 'Mid Level',
'ru': 'Средний',
},
'advanced': {
'en': 'Advanced',
'ru': 'Продвинутый',
}
}
STATUS_TRANSLATIONS = {
'inactive': {
'en': 'Inactive',
'ru': 'Неактивен',
},
'upcoming': {
'en': 'Upcoming',
'ru': 'Предстоящий',
},
'registering': {
'en': 'Registering',
'ru': 'Регистрация',
},
'ongoing': {
'en': 'Ongoing',
'ru': 'В процессе',
},
'finished': {
'en': 'Finished',
'ru': 'Завершен',
}
}
def normalize_level(value):
if not value:
return 'beginner'
text = extract_text_from_json(value).lower().strip()
if 'beginner' in text or 'начальн' in text or 'начинающ' in text:
return 'beginner'
if 'mid' in text or 'средн' in text:
return 'mid'
if 'advanced' in text or 'продвинут' in text:
return 'advanced'
return 'beginner'
def normalize_status(value):
if not value:
return 'inactive'
text = extract_text_from_json(value).lower().strip()
if 'inactive' in text or 'неактив' in text:
return 'inactive'
if 'upcoming' in text or 'предстоящ' in text:
return 'upcoming'
if 'registering' in text or 'регистрац' in text:
return 'registering'
if 'ongoing' in text or 'в процессе' in text:
return 'ongoing'
if 'finished' in text or 'заверш' in text or 'законч' in text:
return 'finished'
return 'inactive'
WEEKDAY_TRANSLATIONS = {
'monday': {
'en': 'Monday',
'ru': 'Понедельник',
'fa': 'دوشنبه',
},
'tuesday': {
'en': 'Tuesday',
'ru': 'Вторник',
'fa': 'سه‌شنبه',
},
'wednesday': {
'en': 'Wednesday',
'ru': 'Среда',
'fa': 'چهارشنبه',
},
'thursday': {
'en': 'Thursday',
'ru': 'Четверг',
'fa': 'پنج‌شنبه',
},
'friday': {
'en': 'Friday',
'ru': 'Пятница',
'fa': 'جمعه',
},
'saturday': {
'en': 'Saturday',
'ru': 'Суббота',
'fa': 'شنبه',
},
'sunday': {
'en': 'Sunday',
'ru': 'Воскресенье',
'fa': 'یک‌شنبه',
}
}
def normalize_weekday_to_key(day_value):
if not day_value:
return 'saturday'
txt = str(day_value).lower().strip()
if 'monday' in txt or 'понедельник' in txt or 'دوشنبه' in txt:
return 'monday'
if 'tuesday' in txt or 'вторник' in txt or 'سه شنبه' in txt or 'سه‌شنبه' in txt:
return 'tuesday'
if 'wednesday' in txt or 'среда' in txt or 'چهارشنبه' in txt:
return 'wednesday'
if 'thursday' in txt or 'четверг' in txt or 'پنج شنبه' in txt or 'پنج‌شنبه' in txt:
return 'thursday'
if 'friday' in txt or 'пятница' in txt or 'جمعه' in txt:
return 'friday'
if 'saturday' in txt or 'суббота' in txt or 'شنبه' in txt:
return 'saturday'
if 'sunday' in txt or 'воскресенье' in txt or 'یک شنبه' in txt or 'یک‌شنبه' in txt:
return 'sunday'
return 'saturday'
class Course(models.Model):
class LevelChoices(TextChoices):
@ -163,8 +287,8 @@ class Course(models.Model):
duration = models.PositiveIntegerField(verbose_name=_('Duration (in hours)'))
lessons_count = models.PositiveIntegerField(verbose_name=_('Number of Lessons'))
description = models.TextField(verbose_name=_('Course Description'))
short_description = models.CharField(max_length=500, blank=True, null=True, verbose_name=_("Short Description"))
description = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Course Description'))
short_description = models.JSONField(default=list, null=True, blank=True, verbose_name=_("Short Description"))
status = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Course Status'))
is_free = models.BooleanField(default=True, verbose_name=_('Is Free'))
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name=_('Course Price'))
@ -225,9 +349,79 @@ class Course(models.Model):
self.slug = []
else:
self.slug = format_multilingual_field(self.slug)
self.level = format_multilingual_field(self.level)
self.status = format_multilingual_field(self.status)
self.timing = format_multilingual_field(self.timing)
level_key = normalize_level(self.level)
self.level = [
{"title": LEVEL_TRANSLATIONS[level_key]["en"], "language_code": "en"},
{"title": LEVEL_TRANSLATIONS[level_key]["ru"], "language_code": "ru"},
]
status_key = normalize_status(self.status)
self.status = [
{"title": STATUS_TRANSLATIONS[status_key]["en"], "language_code": "en"},
{"title": STATUS_TRANSLATIONS[status_key]["ru"], "language_code": "ru"},
]
self.description = format_multilingual_field(self.description)
self.short_description = format_multilingual_field(self.short_description)
# Process timing (translate weekdays)
import json
raw_timings = []
val = self.timing
if val:
if isinstance(val, str):
try:
val = json.loads(val)
except Exception:
pass
if isinstance(val, list) and all(isinstance(x, dict) and 'language_code' in x for x in val):
# We can extract the timing list from any language item
extracted = []
for x in val:
title_val = x.get('title')
if title_val:
if isinstance(title_val, str):
try:
title_val = json.loads(title_val)
except Exception:
pass
if isinstance(title_val, list):
extracted = title_val
break
raw_timings = extracted
elif isinstance(val, list):
raw_timings = val
elif isinstance(val, dict):
raw_timings = []
for k, v in val.items():
raw_timings.append({"day": k, "time": str(v)})
en_timings = []
ru_timings = []
fa_timings = []
for item in raw_timings:
if isinstance(item, dict):
day_key = normalize_weekday_to_key(item.get('day'))
time_val = item.get('time') or item.get('timing') or ''
en_timings.append({
"day": WEEKDAY_TRANSLATIONS.get(day_key, {}).get('en', item.get('day', '')),
"time": time_val
})
ru_timings.append({
"day": WEEKDAY_TRANSLATIONS.get(day_key, {}).get('ru', item.get('day', '')),
"time": time_val
})
fa_timings.append({
"day": WEEKDAY_TRANSLATIONS.get(day_key, {}).get('fa', item.get('day', '')),
"time": time_val
})
self.timing = [
{"language_code": "fa", "title": json.dumps(fa_timings)},
{"language_code": "en", "title": json.dumps(en_timings)},
{"language_code": "ru", "title": json.dumps(ru_timings)},
]
self.features = format_multilingual_field(self.features)
# Ensure consistency: if price is 0, set is_free to True and discount_percentage to 0

16
apps/course/serializers/course.py

@ -46,6 +46,7 @@ class CourseListSerializer(serializers.ModelSerializer):
slug = serializers.SerializerMethodField()
level = serializers.SerializerMethodField()
status = serializers.SerializerMethodField()
short_description = serializers.SerializerMethodField()
class Meta:
model = Course
@ -85,6 +86,9 @@ class CourseListSerializer(serializers.ModelSerializer):
def get_status(self, obj):
return get_localized_field(self._lang(), obj.status)
def get_short_description(self, obj):
return get_localized_field(self._lang(), obj.short_description)
def get_thumbnail(self, obj):
return get_thumbs(obj.thumbnail, self.context.get('request'))
@ -146,6 +150,8 @@ class CourseDetailSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField()
timing = serializers.SerializerMethodField()
features = serializers.SerializerMethodField()
description = serializers.SerializerMethodField()
short_description = serializers.SerializerMethodField()
class Meta:
model = Course
@ -415,6 +421,12 @@ class CourseDetailSerializer(serializers.ModelSerializer):
def get_status(self, obj):
return get_localized_field(self._lang(), obj.status)
def get_description(self, obj):
return get_localized_field(self._lang(), obj.description)
def get_short_description(self, obj):
return get_localized_field(self._lang(), obj.short_description)
def _parse_builder_payload(self, value):
if value is None:
return None
@ -483,6 +495,7 @@ class MyCourseListSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
slug = serializers.SerializerMethodField()
status = serializers.SerializerMethodField()
short_description = serializers.SerializerMethodField()
class Meta:
model = Course
@ -571,6 +584,9 @@ class MyCourseListSerializer(serializers.ModelSerializer):
def get_status(self, obj):
return get_localized_field(self._lang(), obj.status)
def get_short_description(self, obj):
return get_localized_field(self._lang(), obj.short_description)
class AttachmentSerializer(serializers.ModelSerializer):

4
apps/course/views/admin.py

@ -91,6 +91,10 @@ class AdminCourseViewSet(viewsets.ModelViewSet):
elif is_free_param.lower() == 'false':
queryset = queryset.filter(is_free=False)
student_id = self.request.query_params.get('student_id', None)
if student_id:
queryset = queryset.filter(participants__student_id=student_id)
return queryset.order_by('-id')
def perform_create(self, serializer):

Loading…
Cancel
Save