Browse Source

multilanguage enhanement

updated missing lazy text keywords for django messages in related models

updated .po file and translate the empty values in it

add a unfold translations file to keep the unfold messages whenever we run makemasseges.
master
Mohsen Taba 2 weeks ago
parent
commit
7fde6c9191
  1. 76
      apps/blog/migrations/0003_alter_blog_slogan_alter_blog_slug_alter_blog_summary_and_more.py
  2. 28
      apps/blog/models.py
  3. 35
      apps/chat/migrations/0003_alter_chatmessage_options_and_more.py
  4. 88
      apps/chat/models.py
  5. 58
      apps/course/migrations/0004_alter_lessoncompletion_options_and_more.py
  6. 95
      apps/course/models/course.py
  7. 43
      apps/course/models/lesson.py
  8. 12
      apps/course/models/live_session.py
  9. 17
      apps/course/models/participant.py
  10. 147
      apps/quiz/migrations/0002_alter_participantanswer_answer_timing_and_more.py
  11. 46
      apps/quiz/models/participant.py
  12. 50
      apps/quiz/models/quiz.py
  13. 40
      apps/transaction/migrations/0002_alter_participantinfo_options_and_more.py
  14. 31
      apps/transaction/models.py
  15. 37
      dynamic_preferences/locale/ru/LC_MESSAGES/django.po
  16. 1328
      locale/fa/LC_MESSAGES/django.po
  17. 4226
      locale/ru/LC_MESSAGES/django.po
  18. 48
      utils/unfold_translations.py

76
apps/blog/migrations/0003_alter_blog_slogan_alter_blog_slug_alter_blog_summary_and_more.py

@ -0,0 +1,76 @@
# Generated by Django 5.2.12 on 2026-05-03 14:09
import dj_language.field
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_alter_blog_slogan_alter_blog_title'),
('dj_language', '0002_auto_20220120_1344'),
]
operations = [
migrations.AlterField(
model_name='blog',
name='slogan',
field=models.JSONField(default=list, verbose_name='Slogan'),
),
migrations.AlterField(
model_name='blog',
name='slug',
field=models.JSONField(blank=True, default=list, help_text='URL slug for the blog', null=True, verbose_name='Slug'),
),
migrations.AlterField(
model_name='blog',
name='summary',
field=models.JSONField(blank=True, default=list, null=True, verbose_name='Summary'),
),
migrations.AlterField(
model_name='blog',
name='title',
field=models.JSONField(default=list, verbose_name='Title'),
),
migrations.AlterField(
model_name='blogcontent',
name='content',
field=models.JSONField(blank=True, default=list, help_text='The main content text', null=True, verbose_name='Content'),
),
migrations.AlterField(
model_name='blogcontent',
name='slug',
field=models.JSONField(blank=True, default=list, help_text='URL slug for this content (optional)', null=True, verbose_name='Slug'),
),
migrations.AlterField(
model_name='blogcontent',
name='title',
field=models.JSONField(blank=True, default=list, help_text='Title of this content section', null=True, verbose_name='Content Title'),
),
migrations.AlterField(
model_name='blogseo',
name='blog',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seos', to='blog.blog', verbose_name='Blog'),
),
migrations.AlterField(
model_name='blogseo',
name='description',
field=models.CharField(blank=True, help_text='describes and summarizes the contents of the page for the benefit of users and search engines', max_length=170, null=True, verbose_name='Description'),
),
migrations.AlterField(
model_name='blogseo',
name='keywords',
field=models.CharField(blank=True, help_text='keywords in the content that make it possible for people to find the site via search engines', max_length=700, null=True, verbose_name='Keywords'),
),
migrations.AlterField(
model_name='blogseo',
name='language',
field=dj_language.field.LanguageField(default=69, limit_choices_to={'status': True}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dj_language.language', verbose_name='Language'),
),
migrations.AlterField(
model_name='blogseo',
name='title',
field=models.CharField(blank=True, help_text='maximum length of page title is 70 characters and minimum length is 30', max_length=140, null=True, verbose_name='SEO Title'),
),
]

28
apps/blog/models.py

@ -9,21 +9,25 @@ class Blog(models.Model):
"""
Blog model with title, thumbnail, slogan, summary, views count and timestamps
"""
title = models.JSONField(default=list, null=False, blank=False, verbose_name=_('title')) # [{"title": "", "language_code": "en"},{"title": "", "language_code": "fa"},...]
title = models.JSONField(default=list, null=False, blank=False, verbose_name=_('Title')) # [{"title": "", "language_code": "en"},{"title": "", "language_code": "fa"},...]
thumbnail = models.ImageField(
upload_to='blog/thumbnails/%Y/%m/',
verbose_name=_('Thumbnail'),
help_text=_('Blog thumbnail image')
)
slogan = models.JSONField(default=list, null=False, blank=False, verbose_name=_('slogan'))
summary = models.JSONField(default=list, null=True, blank=True, verbose_name=_('summary'))
slogan = models.JSONField(default=list, null=False, blank=False, verbose_name=_('Slogan'))
summary = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Summary'))
views_count = models.PositiveIntegerField(
default=0,
verbose_name=_('Views Count'),
help_text=_('Number of times this blog was viewed')
)
slug = models.JSONField(default=list, null=True, blank=True, verbose_name=_('slug'), help_text=_('URL slug for the blog'))
slug = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Slug'), help_text=_('URL slug for the blog'))
created_at = models.DateTimeField(
auto_now_add=True,
@ -129,9 +133,10 @@ class BlogContent(models.Model):
related_name='contents',
verbose_name=_('Blog')
)
title = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Content title'), help_text=_('Title of this content section'))
content = models.JSONField(default=list, null=True, blank=True, verbose_name=_('content'), help_text=_('The main content text'))
slug = models.JSONField(default=list, null=True, blank=True, verbose_name=_('slug'), help_text=_('URL slug for this content (optional)'))
title = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Content Title'), help_text=_('Title of this content section'))
content = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Content'), help_text=_('The main content text'))
slug = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Slug'), help_text=_('URL slug for this content (optional)'))
image = models.ImageField(
upload_to='blog/content_images/%Y/%m/',
null=True,
@ -176,20 +181,23 @@ class BlogContent(models.Model):
class BlogSeo(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='seos', verbose_name=_('blog'))
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='seos', verbose_name=_('Blog'))
title = models.CharField(
_('seo title'), max_length=140, null=True, blank=True,
_('SEO Title'), max_length=140, null=True, blank=True,
help_text=_('maximum length of page title is 70 characters and minimum length is 30'),
)
keywords = models.CharField(
_('Keywords'),
max_length=700, null=True, blank=True,
help_text=_('keywords in the content that make it possible for people to find the site via search engines')
)
description = models.CharField(
_('Description'),
max_length=170, null=True, blank=True,
help_text=_('describes and summarizes the contents of the page for the benefit of users and search engines'),
)
language = LanguageField(null=True)
language = LanguageField(null=True, verbose_name=_('Language'))
class Meta:
verbose_name = _('Blog SEO')

35
apps/chat/migrations/0003_alter_chatmessage_options_and_more.py

@ -0,0 +1,35 @@
# Generated by Django 5.2.12 on 2026-05-03 14:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('chat', '0002_roommessage_is_locked'),
]
operations = [
migrations.AlterModelOptions(
name='chatmessage',
options={'verbose_name': 'Chat Message', 'verbose_name_plural': 'Chat Messages'},
),
migrations.AlterModelOptions(
name='messagereadstatus',
options={'verbose_name': 'Message Read Status', 'verbose_name_plural': 'Message Read Statuses'},
),
migrations.AlterModelOptions(
name='roommessage',
options={'verbose_name': 'Room Message', 'verbose_name_plural': 'Room Messages'},
),
migrations.AlterField(
model_name='chatmessage',
name='message_metadata',
field=models.JSONField(blank=True, null=True, verbose_name='Message Metadata'),
),
migrations.AlterField(
model_name='roommessage',
name='unread_messages_count',
field=models.IntegerField(default=0, verbose_name='Unread Messages Count'),
),
]

88
apps/chat/models.py

@ -1,8 +1,8 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from apps.account.models import User, User
from apps.account.models import User
from apps.course.models import Course
@ -15,90 +15,92 @@ def chat_upload_path(instance, filename):
return f'chat/room_{instance.room_id}/{date.year}/{date.month:02d}/{date.day:02d}/{filename}'
class RoomMessage(models.Model):
class RoomTypeChoices(models.TextChoices):
GROUP = 'group', 'Group'
PRIVATE = 'private', 'Private'
GROUP = 'group', _('Group')
PRIVATE = 'private', _('Private')
name = models.CharField(
max_length=255,
verbose_name="Room Name"
verbose_name=_("Room Name")
)
description = models.TextField(
verbose_name="Description",
verbose_name=_("Description"),
blank=True,
null=True
)
course = models.ForeignKey(Course,on_delete=models.CASCADE, null=True, blank=True ,related_name="room_messages", verbose_name="Course")
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True, blank=True, related_name="room_messages", verbose_name=_("Course"))
initiator = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="initiated_rooms",
verbose_name="Initiator"
verbose_name=_("Initiator")
)
recipient = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="messages_received",
verbose_name="Recipient",
verbose_name=_("Recipient"),
null=True,
blank=True
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At")
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(
auto_now=True,
verbose_name="Updated At"
verbose_name=_("Updated At")
)
is_locked = models.BooleanField(
default=False,
verbose_name="Is Locked",
help_text="If True, only the professor and admins can send new messages."
verbose_name=_("Is Locked"),
help_text=_("If True, only the professor and admins can send new messages.")
)
room_type = models.CharField(
max_length=10,
choices=RoomTypeChoices.choices,
default=RoomTypeChoices.GROUP,
verbose_name="Room Type"
verbose_name=_("Room Type")
)
unread_messages_count = models.IntegerField(default=0)
unread_messages_count = models.IntegerField(default=0, verbose_name=_("Unread Messages Count"))
def __str__(self):
if self.room_type == self.RoomTypeChoices.GROUP:
return f"Group Room: {self.course.title if self.course else 'N/A'}"
return f"Private Room with {self.recipient}"
class Meta:
verbose_name = _("Room Message")
verbose_name_plural = _("Room Messages")
class ChatMessage(models.Model):
class ChatTypeChoices(models.TextChoices):
TEXT = 'text', 'Text'
FILE = 'file', 'File'
AUDIO = 'audio', 'Audio'
IMAGE = 'image', 'Image'
TEXT = 'text', _('Text')
FILE = 'file', _('File')
AUDIO = 'audio', _('Audio')
IMAGE = 'image', _('Image')
room = models.ForeignKey(
RoomMessage,
on_delete=models.CASCADE,
related_name="messages",
verbose_name="Room",
verbose_name=_("Room"),
)
sender = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="messages_sent",
verbose_name="Sender"
verbose_name=_("Sender")
)
content = models.TextField(verbose_name="Message Content")
content = models.TextField(verbose_name=_("Message Content"))
content_type = models.CharField(
max_length=10,
choices=ChatTypeChoices.choices,
default=ChatTypeChoices.TEXT,
verbose_name="Chat Type"
verbose_name=_("Chat Type")
)
content_size = models.PositiveIntegerField(
verbose_name="Content Size (bytes)",
verbose_name=_("Content Size (bytes)"),
blank=True,
null=True
)
@ -107,23 +109,23 @@ class ChatMessage(models.Model):
blank=True,
null=True,
max_length=500,
verbose_name="File Attachment",
help_text="For file and audio messages"
verbose_name=_("File Attachment"),
help_text=_("For file and audio messages")
)
image_attachment = models.ImageField(
upload_to=chat_upload_path,
blank=True,
null=True,
max_length=500,
verbose_name="Image Attachment",
help_text="For image messages"
verbose_name=_("Image Attachment"),
help_text=_("For image messages")
)
is_read = models.BooleanField(default=False, verbose_name="Is Read")
message_metadata = models.JSONField(blank=True, null=True)
sent_at = models.DateTimeField(auto_now_add=True, verbose_name="Sent At")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated At")
deleted_at = models.DateTimeField(null=True, blank=True, verbose_name="Deleted At")
is_deleted = models.BooleanField(default=False, verbose_name="Is deleted")
is_read = models.BooleanField(default=False, verbose_name=_("Is Read"))
message_metadata = models.JSONField(blank=True, null=True, verbose_name=_("Message Metadata"))
sent_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Sent At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
deleted_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Deleted At"))
is_deleted = models.BooleanField(default=False, verbose_name=_("Is deleted"))
@property
def file_url(self):
@ -151,25 +153,31 @@ class ChatMessage(models.Model):
def __str__(self):
return f"Message from {self.sender} in {self.room}"
class Meta:
verbose_name = _("Chat Message")
verbose_name_plural = _("Chat Messages")
class MessageReadStatus(models.Model):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="read_statuses",
verbose_name="User"
verbose_name=_("User")
)
message = models.ForeignKey(
ChatMessage,
on_delete=models.CASCADE,
related_name="read_statuses",
verbose_name="Message"
verbose_name=_("Message")
)
is_read = models.BooleanField(default=False, verbose_name="Is Read")
read_at = models.DateTimeField(null=True, blank=True, verbose_name="Read At")
is_read = models.BooleanField(default=False, verbose_name=_("Is Read"))
read_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Read At"))
class Meta:
unique_together = ("user", "message") # جلوگیری از ثبت تکراری
verbose_name = _("Message Read Status")
verbose_name_plural = _("Message Read Statuses")
def __str__(self):
return f"User {self.user.fullname} read Message {self.message.id}: {self.is_read}"

58
apps/course/migrations/0004_alter_lessoncompletion_options_and_more.py

@ -0,0 +1,58 @@
# Generated by Django 5.2.12 on 2026-05-03 14:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0002_alter_user_email_alter_user_username'),
('course', '0003_rename_is_chat_group_lock_course_is_group_chat_locked_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='lessoncompletion',
options={'verbose_name': 'Lesson Completion', 'verbose_name_plural': 'Lesson Completions'},
),
migrations.AlterModelOptions(
name='participant',
options={'verbose_name': 'Participant', 'verbose_name_plural': 'Participants'},
),
migrations.AlterField(
model_name='lessoncompletion',
name='course_lesson',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='completions', to='course.courselesson', verbose_name='Course Lesson'),
),
migrations.AlterField(
model_name='lessoncompletion',
name='student',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lesson_completions', to='account.studentuser', verbose_name='Student'),
),
migrations.AlterField(
model_name='participant',
name='course',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='course.course', verbose_name='Course'),
),
migrations.AlterField(
model_name='participant',
name='is_active',
field=models.BooleanField(default=True, verbose_name='Is Active'),
),
migrations.AlterField(
model_name='participant',
name='joined_date',
field=models.DateTimeField(auto_now_add=True, verbose_name='Joined Date'),
),
migrations.AlterField(
model_name='participant',
name='student',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participated_courses', to='account.studentuser', verbose_name='Student'),
),
migrations.AlterField(
model_name='participant',
name='unread_messages_count',
field=models.IntegerField(default=0, verbose_name='Unread Messages Count'),
),
]

95
apps/course/models/course.py

@ -26,7 +26,8 @@ def course_attachment_file_upload_to(instance, filename):
class CourseCategory(models.Model):
name = models.CharField(max_length=255, verbose_name='Category Name')
name = models.CharField(max_length=255, verbose_name=_('Category Name'))
slug = models.SlugField(unique=True, max_length=255)
def __str__(self):
@ -44,25 +45,26 @@ class CourseCategory(models.Model):
class Course(models.Model):
class LevelChoices(TextChoices):
BEGINNER = 'beginner', 'Beginner'
MID = 'mid', 'Mid Level'
ADVANCED = 'advanced', 'Advanced'
BEGINNER = 'beginner', _('Beginner')
MID = 'mid', _('Mid Level')
ADVANCED = 'advanced', _('Advanced')
class StatusChoices(TextChoices):
INACTIVE = 'inactive', 'Inactive' # Not Active (does not show)
UPCOMING = 'upcoming', 'Upcoming' # Upcoming (visible but registration not allowed)-Предстоящие
REGISTERING = 'registering', 'Registering' # Registering (registration is open)-регистрация
ONGOING = 'ongoing', 'Ongoing' # Ongoing (course has started, registration closed)-В процессе
FINISHED = 'finished', 'Finished' # Finished (course has ended)-закончился
INACTIVE = 'inactive', _('Inactive') # Not Active (does not show)
UPCOMING = 'upcoming', _('Upcoming') # Upcoming (visible but registration not allowed)-Предстоящие
REGISTERING = 'registering', _('Registering') # Registering (registration is open)-регистрация
ONGOING = 'ongoing', _('Ongoing') # Ongoing (course has started, registration closed)-В процессе
FINISHED = 'finished', _('Finished') # Finished (course has ended)-закончился
class VedioTypeChoices(models.TextChoices):
YOUTUBE_LINK = 'youtube_link', 'Youtube Link'
VIDEO_FILE = 'video_file', 'Video File'
YOUTUBE_LINK = 'youtube_link', _('Youtube Link')
VIDEO_FILE = 'video_file', _('Video File')
title = models.CharField(max_length=255, verbose_name='Course Title')
title = models.CharField(max_length=255, verbose_name=_('Course Title'))
slug = models.SlugField(allow_unicode=True, unique=True)
category = models.ForeignKey(CourseCategory, on_delete=models.CASCADE, related_name='courses', verbose_name='Category')
category = models.ForeignKey(CourseCategory, on_delete=models.CASCADE, related_name='courses', verbose_name=_('Category'))
professor = models.ForeignKey(
ProfessorUser,
on_delete=models.CASCADE,
@ -73,7 +75,7 @@ class Course(models.Model):
video_type = models.CharField(
max_length=20,
choices=VedioTypeChoices.choices,
verbose_name='Preview Video Type (YouTube Link or File Upload)'
verbose_name=_('Preview Video Type (YouTube Link or File Upload)')
)
video_file = models.FileField(
upload_to=course_file_upload_to,
@ -82,18 +84,18 @@ class Course(models.Model):
)
video_link = models.CharField(max_length=500, null=True, blank=True)
is_online = models.BooleanField(default=False, verbose_name='Is Online Course')
online_link = models.CharField(max_length=500, null=True, blank=True, verbose_name='Online Class Link')
level = models.CharField(max_length=10, choices=LevelChoices.choices, verbose_name='Course Level')
duration = models.PositiveIntegerField(verbose_name='Duration (in hours)')
lessons_count = models.PositiveIntegerField(verbose_name='Number of Lessons')
is_online = models.BooleanField(default=False, verbose_name=_('Is Online Course'))
online_link = models.CharField(max_length=500, null=True, blank=True, verbose_name=_('Online Class Link'))
level = models.CharField(max_length=10, choices=LevelChoices.choices, verbose_name=_('Course Level'))
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")
status = models.CharField(max_length=15, choices=StatusChoices.choices, default=StatusChoices.INACTIVE, 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')
discount_percentage = models.PositiveIntegerField(default=0, verbose_name='Discount Percentage')
description = models.TextField(verbose_name=_('Course Description'))
short_description = models.CharField(max_length=500, blank=True, null=True, verbose_name=_("Short Description"))
status = models.CharField(max_length=15, choices=StatusChoices.choices, default=StatusChoices.INACTIVE, 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'))
discount_percentage = models.PositiveIntegerField(default=0, verbose_name=_('Discount Percentage'))
final_price = models.DecimalField(
verbose_name=_('Course Final Price'), decimal_places=2, max_digits=10, default=0.00, blank=True,
help_text=_('This field is automatically calculated based on the discount percentage.')
@ -148,8 +150,9 @@ class Course(models.Model):
class Meta:
verbose_name = "Course"
verbose_name_plural = "Courses"
verbose_name = _("Course")
verbose_name_plural = _("Courses")
indexes = [
models.Index(fields=['status']),
models.Index(fields=['is_free']),
@ -165,8 +168,8 @@ class Glossary(models.Model):
"""
Base Glossary model that contains the actual content
"""
title = models.CharField(max_length=555, verbose_name='Glossary Title')
description = models.TextField(verbose_name='Description')
title = models.CharField(max_length=555, verbose_name=_('Glossary Title'))
description = models.TextField(verbose_name=_('Description'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -174,16 +177,17 @@ class Glossary(models.Model):
return self.title
class Meta:
verbose_name = "Glossary"
verbose_name_plural = "Glossaries"
verbose_name = _("Glossary")
verbose_name_plural = _("Glossaries")
class CourseGlossary(models.Model):
"""
Intermediate model that connects Course with Glossary
"""
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='glossaries', verbose_name='Course')
glossary = models.ForeignKey(Glossary, on_delete=models.CASCADE, related_name='course_glossaries', verbose_name='Glossary')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='glossaries', verbose_name=_('Course'))
glossary = models.ForeignKey(Glossary, on_delete=models.CASCADE, related_name='course_glossaries', verbose_name=_('Glossary'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -200,20 +204,21 @@ class CourseGlossary(models.Model):
class Meta:
ordering = ("-id",)
verbose_name = "Course Glossary"
verbose_name_plural = "Course Glossaries"
verbose_name = _("Course Glossary")
verbose_name_plural = _("Course Glossaries")
class Attachment(models.Model):
"""
Base Attachment model that contains the actual file
"""
title = models.CharField(max_length=255, verbose_name='Attachment Title')
title = models.CharField(max_length=255, verbose_name=_('Attachment Title'))
file = models.FileField(
upload_to=attachment_file_upload_to,
verbose_name='Attachment File'
verbose_name=_('Attachment File')
)
file_size = models.PositiveIntegerField(verbose_name='File Size (in bytes)', null=True, blank=True)
file_size = models.PositiveIntegerField(verbose_name=_('File Size (in bytes)'), null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -227,16 +232,17 @@ class Attachment(models.Model):
return self.title
class Meta:
verbose_name = "Attachment"
verbose_name_plural = "Attachments"
verbose_name = _("Attachment")
verbose_name_plural = _("Attachments")
class CourseAttachment(models.Model):
"""
Intermediate model that connects Course with Attachment
"""
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='attachments', verbose_name='Course')
attachment = models.ForeignKey(Attachment, on_delete=models.CASCADE, related_name='course_attachments', verbose_name='Attachment')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='attachments', verbose_name=_('Course'))
attachment = models.ForeignKey(Attachment, on_delete=models.CASCADE, related_name='course_attachments', verbose_name=_('Attachment'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -257,8 +263,9 @@ class CourseAttachment(models.Model):
class Meta:
ordering = ("-id",)
verbose_name = "Course Attachment"
verbose_name_plural = "Course Attachments"
verbose_name = _("Course Attachment")
verbose_name_plural = _("Course Attachments")
indexes = [
models.Index(fields=['course']),
models.Index(fields=['attachment']),

43
apps/course/models/lesson.py

@ -18,18 +18,20 @@ class Lesson(models.Model):
Base Lesson model that contains the actual content (video file or link)
"""
class ContentTypeChoices(models.TextChoices):
YOUTUBE_LINK = 'youtube_link', 'Youtube Link'
VIDEO_FILE = 'video_file', 'Video File'
YOUTUBE_LINK = 'youtube_link', _('Youtube Link')
VIDEO_FILE = 'video_file', _('Video File')
title = models.CharField(max_length=255, verbose_name='Lesson Title')
content_type = models.CharField(max_length=50, choices=ContentTypeChoices.choices, verbose_name='Content Type')
title = models.CharField(max_length=255, verbose_name=_('Lesson Title'))
content_type = models.CharField(max_length=50, choices=ContentTypeChoices.choices, verbose_name=_('Content Type'))
content_file = models.FileField(
null=True,
blank=True,
upload_to=lesson_file_upload_to,
)
video_link = models.CharField(max_length=500, null=True, blank=True, verbose_name='Link')
duration = models.PositiveIntegerField(verbose_name='Duration (in minutes)')
video_link = models.CharField(max_length=500, null=True, blank=True, verbose_name=_('Link'))
duration = models.PositiveIntegerField(verbose_name=_('Duration (in minutes)'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -37,8 +39,9 @@ class Lesson(models.Model):
return self.title
class Meta:
verbose_name = "Lesson"
verbose_name_plural = "Lessons"
verbose_name = _("Lesson")
verbose_name_plural = _("Lessons")
indexes = [
models.Index(fields=['content_type']),
models.Index(fields=['created_at']),
@ -49,10 +52,11 @@ class CourseLesson(models.Model):
"""
Intermediate model that connects Course with Lesson
"""
course = models.ForeignKey("course.Course", on_delete=models.CASCADE, related_name='lessons', verbose_name='Course')
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='course_lessons', verbose_name='Lesson')
title = models.CharField(max_length=255, verbose_name='Course Lesson Title', null=True, blank=True)
priority = models.IntegerField(null=True, blank=True, verbose_name='Priority')
course = models.ForeignKey("course.Course", on_delete=models.CASCADE, related_name='lessons', verbose_name=_('Course'))
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='course_lessons', verbose_name=_('Lesson'))
title = models.CharField(max_length=255, verbose_name=_('Course Lesson Title'), null=True, blank=True)
priority = models.IntegerField(null=True, blank=True, verbose_name=_('Priority'))
is_active = models.BooleanField(default=True, verbose_name=_('Is Active'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -100,8 +104,9 @@ class CourseLesson(models.Model):
lessons.filter(priority__gte=self.priority).update(priority=models.F('priority') + 1)
class Meta:
verbose_name = "Course Lesson"
verbose_name_plural = "Course Lessons"
verbose_name = _("Course Lesson")
verbose_name_plural = _("Course Lessons")
indexes = [
models.Index(fields=['course']),
models.Index(fields=['lesson']),
@ -116,18 +121,24 @@ class LessonCompletion(models.Model):
student = models.ForeignKey(
StudentUser,
on_delete=models.CASCADE,
related_name='lesson_completions'
related_name='lesson_completions',
verbose_name=_('Student')
)
course_lesson = models.ForeignKey(
CourseLesson,
on_delete=models.CASCADE,
related_name='completions'
related_name='completions',
verbose_name=_('Course Lesson')
)
completed_at = models.DateTimeField(auto_now_add=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
class Meta:
unique_together = ('student', 'course_lesson')
verbose_name = _("Lesson Completion")
verbose_name_plural = _("Lesson Completions")
indexes = [
models.Index(fields=['student']),
models.Index(fields=['course_lesson']),

12
apps/course/models/live_session.py

@ -61,12 +61,13 @@ class CourseLiveSession(models.Model):
USER_ROLE_CHOICES = (
("participant", "Participant"),
("moderator", "Moderator"),
("observer", "Observer"),
("participant", _("Participant")),
("moderator", _("Moderator")),
("observer", _("Observer")),
)
class LiveSessionUser(models.Model):
session = models.ForeignKey(
CourseLiveSession,
@ -123,11 +124,12 @@ class LiveSessionUser(models.Model):
RECORDING_TYPE_CHOICES = (
("voice", "Voice"),
("video", "Video"),
("voice", _("Voice")),
("video", _("Video")),
)
class LiveSessionRecording(models.Model):
session = models.ForeignKey(
CourseLiveSession,

17
apps/course/models/participant.py

@ -1,6 +1,5 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.account.models import StudentUser, User
from apps.course.models import Course
@ -10,19 +9,23 @@ class Participant(models.Model):
student = models.ForeignKey(
StudentUser,
on_delete=models.CASCADE,
related_name='participated_courses'
related_name='participated_courses',
verbose_name=_('Student')
)
course = models.ForeignKey(
Course,
on_delete=models.CASCADE,
related_name='participants'
related_name='participants',
verbose_name=_('Course')
)
is_active = models.BooleanField(default=True)
joined_date = models.DateTimeField(auto_now_add=True)
unread_messages_count = models.IntegerField(default=0)
is_active = models.BooleanField(default=True, verbose_name=_('Is Active'))
joined_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Joined Date'))
unread_messages_count = models.IntegerField(default=0, verbose_name=_('Unread Messages Count'))
class Meta:
unique_together = ('student', 'course')
verbose_name = _('Participant')
verbose_name_plural = _('Participants')
indexes = [
models.Index(fields=['student']),
models.Index(fields=['course']),

147
apps/quiz/migrations/0002_alter_participantanswer_answer_timing_and_more.py

@ -0,0 +1,147 @@
# Generated by Django 5.2.12 on 2026-05-03 14:09
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0004_alter_lessoncompletion_options_and_more'),
('quiz', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='participantanswer',
name='answer_timing',
field=models.PositiveSmallIntegerField(default=0, verbose_name='Seconds Take to Answer'),
),
migrations.AlterField(
model_name='participantanswer',
name='at_time',
field=models.DateTimeField(verbose_name='At Time'),
),
migrations.AlterField(
model_name='participantanswer',
name='option_num',
field=models.PositiveSmallIntegerField(choices=[(1, 'Option 1'), (2, 'Option 2'), (3, 'Option 3'), (4, 'Option 4')], verbose_name='Selected Option'),
),
migrations.AlterField(
model_name='participantanswer',
name='participant',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='quiz.quizparticipant', verbose_name='Participant'),
),
migrations.AlterField(
model_name='participantanswer',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.question', verbose_name='Question'),
),
migrations.AlterField(
model_name='question',
name='correct_answer',
field=models.PositiveSmallIntegerField(choices=[(1, 'Option 1'), (2, 'Option 2'), (3, 'Option 3'), (4, 'Option 4')], verbose_name='Correct Answer'),
),
migrations.AlterField(
model_name='question',
name='created_at',
field=models.DateTimeField(auto_now_add=True, verbose_name='Created At'),
),
migrations.AlterField(
model_name='question',
name='option1',
field=models.CharField(max_length=255, verbose_name='Option 1'),
),
migrations.AlterField(
model_name='question',
name='option2',
field=models.CharField(max_length=255, verbose_name='Option 2'),
),
migrations.AlterField(
model_name='question',
name='option3',
field=models.CharField(max_length=255, verbose_name='Option 3'),
),
migrations.AlterField(
model_name='question',
name='option4',
field=models.CharField(max_length=255, verbose_name='Option 4'),
),
migrations.AlterField(
model_name='question',
name='priority',
field=models.IntegerField(blank=True, null=True, verbose_name='Priority'),
),
migrations.AlterField(
model_name='question',
name='question',
field=models.CharField(max_length=255, verbose_name='Question'),
),
migrations.AlterField(
model_name='question',
name='quiz',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='quiz.quiz', verbose_name='Quiz'),
),
migrations.AlterField(
model_name='quiz',
name='each_question_timing',
field=models.PositiveIntegerField(verbose_name='Each Question Timing'),
),
migrations.AlterField(
model_name='quiz',
name='lesson',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quizzes', to='course.courselesson', verbose_name='Lesson'),
),
migrations.AlterField(
model_name='quiz',
name='status',
field=models.BooleanField(default=True, verbose_name='Status'),
),
migrations.AlterField(
model_name='quiz',
name='title',
field=models.CharField(help_text='Quiz Title', max_length=255, verbose_name='Title'),
),
migrations.AlterField(
model_name='quizparticipant',
name='ended_at',
field=models.DateTimeField(verbose_name='Ended At'),
),
migrations.AlterField(
model_name='quizparticipant',
name='question_score',
field=models.PositiveIntegerField(verbose_name='Question Score'),
),
migrations.AlterField(
model_name='quizparticipant',
name='quiz',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='quiz.quiz', verbose_name='Quiz'),
),
migrations.AlterField(
model_name='quizparticipant',
name='started_at',
field=models.DateTimeField(verbose_name='Started At'),
),
migrations.AlterField(
model_name='quizparticipant',
name='timing_score',
field=models.PositiveIntegerField(verbose_name='Timing Score'),
),
migrations.AlterField(
model_name='quizparticipant',
name='total_score',
field=models.PositiveIntegerField(verbose_name='Total Score'),
),
migrations.AlterField(
model_name='quizparticipant',
name='total_timing',
field=models.PositiveIntegerField(help_text='Seconds take to finish the quiz', verbose_name='Total Timing'),
),
migrations.AlterField(
model_name='quizparticipant',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uquizzes', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

46
apps/quiz/models/participant.py

@ -1,26 +1,25 @@
from django.db import models
from django.db.models import F, Window
from django.db.models.functions import Rank
from django.utils.translation import gettext_lazy as _
from apps.account.models import User
class QuizParticipant(models.Model):
quiz = models.ForeignKey('quiz.Quiz', on_delete=models.CASCADE, related_name='participants')
user = models.ForeignKey('account.User', on_delete=models.CASCADE, verbose_name='user', related_name='uquizzes')
started_at = models.DateTimeField(verbose_name='started at')
ended_at = models.DateTimeField(verbose_name='ended at')
total_timing = models.PositiveIntegerField(help_text='Seconds take to finish the quiz')
quiz = models.ForeignKey('quiz.Quiz', on_delete=models.CASCADE, related_name='participants', verbose_name=_('Quiz'))
user = models.ForeignKey('account.User', on_delete=models.CASCADE, verbose_name=_('User'), related_name='uquizzes')
started_at = models.DateTimeField(verbose_name=_('Started At'))
ended_at = models.DateTimeField(verbose_name=_('Ended At'))
total_timing = models.PositiveIntegerField(verbose_name=_('Total Timing'), help_text=_('Seconds take to finish the quiz'))
question_score = models.PositiveIntegerField()
timing_score = models.PositiveIntegerField()
total_score = models.PositiveIntegerField()
question_score = models.PositiveIntegerField(verbose_name=_('Question Score'))
timing_score = models.PositiveIntegerField(verbose_name=_('Timing Score'))
total_score = models.PositiveIntegerField(verbose_name=_('Total Score'))
class Meta:
verbose_name = "Participant"
verbose_name_plural = "Participants"
verbose_name = _("Participant")
verbose_name_plural = _("Participants")
ordering = ("-id",)
def __str__(self):
@ -40,25 +39,24 @@ class QuizParticipant(models.Model):
)
class ParticipantAnswer(models.Model):
CHOICES = [
(1, 'Option 1'),
(2, 'Option 2'),
(3, 'Option 3'),
(4, 'Option 4'),
(1, _('Option 1')),
(2, _('Option 2')),
(3, _('Option 3')),
(4, _('Option 4')),
]
participant = models.ForeignKey(QuizParticipant, on_delete=models.CASCADE, related_name='answers')
question = models.ForeignKey("quiz.Question", on_delete=models.CASCADE)
option_num = models.PositiveSmallIntegerField(choices=CHOICES, verbose_name='selected option')
at_time = models.DateTimeField()
answer_timing = models.PositiveSmallIntegerField(default=0, verbose_name='seconds take to answer')
participant = models.ForeignKey(QuizParticipant, on_delete=models.CASCADE, related_name='answers', verbose_name=_('Participant'))
question = models.ForeignKey("quiz.Question", on_delete=models.CASCADE, verbose_name=_('Question'))
option_num = models.PositiveSmallIntegerField(choices=CHOICES, verbose_name=_('Selected Option'))
at_time = models.DateTimeField(verbose_name=_('At Time'))
answer_timing = models.PositiveSmallIntegerField(default=0, verbose_name=_('Seconds Take to Answer'))
class Meta:
verbose_name = "User Quiz Answer"
verbose_name_plural = "User Quiz Answers"
verbose_name = _("User Quiz Answer")
verbose_name_plural = _("User Quiz Answers")
ordering = ("-id",)
def __str__(self):

50
apps/quiz/models/quiz.py

@ -5,17 +5,17 @@ from apps.account.models import User
class Quiz(models.Model):
lesson = models.ForeignKey("course.CourseLesson", verbose_name=_('lesson'), related_name='quizzes', on_delete=models.CASCADE)
lesson = models.ForeignKey("course.CourseLesson", verbose_name=_('Lesson'), related_name='quizzes', on_delete=models.CASCADE)
title = models.CharField(max_length=255, verbose_name=_('title'), help_text="Quiz Title")
description = models.CharField(max_length=55, blank=True, null=True, verbose_name="Description")
each_question_timing = models.PositiveIntegerField()
status = models.BooleanField(default=True)
title = models.CharField(max_length=255, verbose_name=_('Title'), help_text=_("Quiz Title"))
description = models.CharField(max_length=55, blank=True, null=True, verbose_name=_("Description"))
each_question_timing = models.PositiveIntegerField(verbose_name=_("Each Question Timing"))
status = models.BooleanField(default=True, verbose_name=_("Status"))
class Meta:
verbose_name = "Quiz"
verbose_name_plural = "Quizzes"
verbose_name = _("Quiz")
verbose_name_plural = _("Quizzes")
ordering = ("-id",)
def __str__(self):
@ -25,30 +25,28 @@ class Quiz(models.Model):
return f"Quiz(id={self.id})"
class Question(models.Model):
CHOICES = [
(1, 'Option 1'),
(2, 'Option 2'),
(3, 'Option 3'),
(4, 'Option 4'),
(1, _('Option 1')),
(2, _('Option 2')),
(3, _('Option 3')),
(4, _('Option 4')),
]
quiz = models.ForeignKey(Quiz, verbose_name='quiz', on_delete=models.CASCADE, related_name='questions')
question = models.CharField(max_length=255)
option1 = models.CharField(max_length=255, verbose_name='option 1')
option2 = models.CharField(max_length=255, verbose_name='option 2')
option3 = models.CharField(max_length=255, verbose_name='option 3')
option4 = models.CharField(max_length=255, verbose_name='option 4')
correct_answer = models.PositiveSmallIntegerField(choices=CHOICES)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='created at')
priority = models.IntegerField(null=True, blank=True)
quiz = models.ForeignKey(Quiz, verbose_name=_('Quiz'), on_delete=models.CASCADE, related_name='questions')
question = models.CharField(max_length=255, verbose_name=_('Question'))
option1 = models.CharField(max_length=255, verbose_name=_('Option 1'))
option2 = models.CharField(max_length=255, verbose_name=_('Option 2'))
option3 = models.CharField(max_length=255, verbose_name=_('Option 3'))
option4 = models.CharField(max_length=255, verbose_name=_('Option 4'))
correct_answer = models.PositiveSmallIntegerField(choices=CHOICES, verbose_name=_('Correct Answer'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
priority = models.IntegerField(null=True, blank=True, verbose_name=_('Priority'))
class Meta:
verbose_name = "Question"
verbose_name_plural = "Questions"
verbose_name = _("Question")
verbose_name_plural = _("Questions")
ordering = ("-priority", "-id",)
def __str__(self):
@ -61,6 +59,6 @@ class Question(models.Model):
class QuizRankUser(User):
class Meta:
proxy = True
verbose_name = 'Rank Quiz'
verbose_name_plural = 'Rank Quizzes'
verbose_name = _('Rank Quiz')
verbose_name_plural = _('Rank Quizzes')

40
apps/transaction/migrations/0002_alter_participantinfo_options_and_more.py

@ -0,0 +1,40 @@
# Generated by Django 5.2.12 on 2026-05-03 14:09
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0004_alter_lessoncompletion_options_and_more'),
('transaction', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='participantinfo',
options={'verbose_name': 'Participant Info', 'verbose_name_plural': 'Participant Infos'},
),
migrations.AlterModelOptions(
name='transactionparticipant',
options={'verbose_name': 'Transaction Participant', 'verbose_name_plural': 'Transaction Participants'},
),
migrations.AlterField(
model_name='transactionparticipant',
name='course',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_transactions', to='course.course', verbose_name='Course'),
),
migrations.AlterField(
model_name='transactionparticipant',
name='is_deleted',
field=models.BooleanField(default=False, verbose_name='Is Deleted'),
),
migrations.AlterField(
model_name='transactionparticipant',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

31
apps/transaction/models.py

@ -13,9 +13,6 @@ def receipt_file_upload_to(instance, filename):
return os.path.join(f"receipts/{instance.transaction.id}/{filename}")
class TransactionParticipant(models.Model):
@ -30,15 +27,15 @@ class TransactionParticipant(models.Model):
FREE = 'free', _('Free')
PAYMENT_GATEWAY = 'Payment_Gateway', _('Payment Gateway')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_transactions')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions', verbose_name=_('User'))
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_transactions', verbose_name=_('Course'))
payment_method=models.CharField(max_length=20, choices=PaymentMethods.choices, default=PaymentMethods.PAYMENT_GATEWAY, verbose_name=_('Transaction Payment Method'))
# is_paid = models.BooleanField(default=False, verbose_name='Payment Status', help_text='Indicates whether the payment has been completed or not')
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='Transaction Price')
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name=_('Transaction Price'))
status = models.CharField(max_length=20, choices=TransactionStatus.choices, default=TransactionStatus.PENDING, verbose_name=_('Transaction Status'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated at"))
is_deleted = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False, verbose_name=_('Is Deleted'))
def __str__(self):
return f"{self.user.email} - {self.course.title} ({self.status})"
@ -59,31 +56,37 @@ class TransactionParticipant(models.Model):
course=self.course
).first()
class Meta:
verbose_name = _('Transaction Participant')
verbose_name_plural = _('Transaction Participants')
class ParticipantInfo(models.Model):
class GenderChoices(models.TextChoices):
MALE = 'male', 'Male'
FEMALE = 'female', 'Female'
MALE = 'male', _('Male')
FEMALE = 'female', _('Female')
transaction_participant = models.ForeignKey(
TransactionParticipant,
on_delete=models.CASCADE,
related_name='participant_infos',
verbose_name="Transaction Participant"
verbose_name=_("Transaction Participant")
)
fullname = models.CharField(max_length=255, verbose_name="Full Name", help_text="Enter the full name of the user.")
email = models.EmailField(verbose_name="Email Address", help_text="Enter the user's email address.")
fullname = models.CharField(max_length=255, verbose_name=_("Full Name"), help_text=_("Enter the full name of the user."))
email = models.EmailField(verbose_name=_("Email Address"), help_text=_("Enter the user's email address."))
phone_number = PhoneNumberField(validators=[validate_possible_number], null=True, blank=True, verbose_name=_('phone'))
gender = models.CharField(
max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text="Select the user's gender."
max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text=_("Select the user's gender.")
)
birthdate = models.DateField(verbose_name=_('birthdate'), null=True, blank=True)
def __str__(self):
return f"{self.fullname} (Transaction: {self.transaction_participant.id}) - {self.email}"
class Meta:
verbose_name = _('Participant Info')
verbose_name_plural = _('Participant Infos')
class TransactionReceipt(models.Model):
"""

37
dynamic_preferences/locale/ru/LC_MESSAGES/django.po

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-04 06:07+0330\n"
"POT-Creation-Date: 2026-05-02 15:29+0330\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -19,62 +19,67 @@ msgstr ""
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
"(n%100>=11 && n%100<=14)? 2 : 3);\n"
#: dynamic_preferences/admin.py:52
#: .\dynamic_preferences\admin.py:59
msgid "Verbose name"
msgstr ""
#: dynamic_preferences/admin.py:56
#: .\dynamic_preferences\admin.py:63
msgid "Help text"
msgstr ""
#: dynamic_preferences/admin.py:77
#: .\dynamic_preferences\admin.py:84
msgid "Default Value"
msgstr ""
#: dynamic_preferences/admin.py:86 dynamic_preferences/models.py:30
#: .\dynamic_preferences\admin.py:93 .\dynamic_preferences\models.py:30
msgid "Section Name"
msgstr ""
#: dynamic_preferences/apps.py:10
#: .\dynamic_preferences\admin.py:118
msgid "Reset selected preferences to default values"
msgstr ""
#: .\dynamic_preferences\apps.py:10
msgid "Settings"
msgstr ""
#: dynamic_preferences/models.py:34
#: .\dynamic_preferences\models.py:34
msgid "Name"
msgstr ""
#: dynamic_preferences/models.py:37
#: .\dynamic_preferences\models.py:37
msgid "Raw Value"
msgstr ""
#: dynamic_preferences/models.py:51
#: .\dynamic_preferences\models.py:51
msgid "Verbose Name"
msgstr ""
#: dynamic_preferences/models.py:57
#: .\dynamic_preferences\models.py:57
msgid "Help Text"
msgstr ""
#: dynamic_preferences/models.py:94
#: .\dynamic_preferences\models.py:94
msgid "Global preference"
msgstr ""
#: dynamic_preferences/models.py:95
#: .\dynamic_preferences\models.py:95
msgid "Global preferences"
msgstr ""
#: dynamic_preferences/templates/dynamic_preferences/form.html:11
#: .\dynamic_preferences\templates\dynamic_preferences\form.html:11
msgid "Submit"
msgstr ""
#: dynamic_preferences/users/apps.py:11
#: .\dynamic_preferences\users\apps.py:11
msgid "Preferences - Users"
msgstr ""
#: dynamic_preferences/users/models.py:14
#: .\dynamic_preferences\users\models.py:14
msgid "user preference"
msgstr ""
#: dynamic_preferences/users/models.py:15
#: .\dynamic_preferences\users\models.py:15
msgid "user preferences"
msgstr ""

1328
locale/fa/LC_MESSAGES/django.po
File diff suppressed because it is too large
View File

4226
locale/ru/LC_MESSAGES/django.po
File diff suppressed because it is too large
View File

48
utils/unfold_translations.py

@ -0,0 +1,48 @@
# backend/utils/unfold_translations.py
from django.utils.translation import gettext_lazy as _
# Dummy list to trick makemessages into keeping our Unfold overrides safe!
UNFOLD_CUSTOM_STRINGS = [
_("Search"),
_("Search apps and models"),
_("View site"),
_("Log out"),
_("general"),
_("manage all Participants"),
_("questions"),
_("question"),
_("option"),
_("correct answer"),
_("Total score"),
_("Timing score"),
_("Question score"),
_("Total timing"),
_("Ended at"),
_("Started at"),
_("Participant Answer"),
_("Selected option"),
_("Correct Answer"),
_("Seconds take to answer"),
_("Recent Messages Latest"),
_("Room name"),
_("Is Locked"),
_("Initiator"),
_("Recipient"),
_("Manage All Messages"),
_("Sender"),
_("Message Content"),
_("Chat Type"),
_("Sent At"),
_("Is deleted"),
_("Group"),
_("Private"),
_("System Administration"),
_("All Users"),
_("Monday"),
_("Tuesday"),
_("Wednesday"),
_("Thursday"),
_("Friday"),
_("Saturday"),
_("Sunday"),
]
Loading…
Cancel
Save