From 05987f69acd7778fd3343b1a9ac86ede1a8c1b6a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 4 Feb 2026 09:30:28 +0000 Subject: [PATCH] Autosave: 20260204-093027 --- core/__pycache__/admin.cpython-311.pyc | Bin 6755 -> 7363 bytes core/__pycache__/models.cpython-311.pyc | Bin 13242 -> 15762 bytes core/__pycache__/views.cpython-311.pyc | Bin 14481 -> 14688 bytes core/admin.py | 15 +- ...remove_subject_teacher_subject_teachers.py | 22 ++ .../0012_subject_youtube_live_url.py | 18 ++ ...ource_resource_type_alter_resource_link.py | 23 +++ ...t_teacher_subject_teachers.cpython-311.pyc | Bin 0 -> 1020 bytes ...2_subject_youtube_live_url.cpython-311.pyc | Bin 0 -> 974 bytes ...e_type_alter_resource_link.cpython-311.pyc | Bin 0 -> 1297 bytes core/models.py | 61 +++++- core/templates/core/index.html | 44 ++++ core/templates/core/live_classroom.html | 146 +++++++++----- core/templates/core/student_dashboard.html | 6 +- core/templates/core/subject_detail.html | 189 +++++++++++++++--- core/views.py | 7 +- 16 files changed, 446 insertions(+), 85 deletions(-) create mode 100644 core/migrations/0011_remove_subject_teacher_subject_teachers.py create mode 100644 core/migrations/0012_subject_youtube_live_url.py create mode 100644 core/migrations/0013_resource_resource_type_alter_resource_link.py create mode 100644 core/migrations/__pycache__/0011_remove_subject_teacher_subject_teachers.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0012_subject_youtube_live_url.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0013_resource_resource_type_alter_resource_link.cpython-311.pyc diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index b27f09156c22e466c82872945393f88e7b1ad1ac..f34b19d8d346dec690c2f1ec03be56240fd37167 100644 GIT binary patch delta 1510 zcmZ`(&u<$=6rNez@sD7K*l`kvh&WAZWH)s}TcOr++?1w2)Fy#6sfbe2W!YU4hqX7% zt_6-#d4cv)At5wE1q4!hX%m$T%7GIS5*LmxvV>U)QPF=OQ8;md_tth4R5A8`^XAPr zZ@!)PW}lw==}hQhFc=`vmi9i%3!(2qulfT5j-l^(c|~Og8vsU9V6Q-D`&wRb90La%!AZ)_Kqt4h0-Jc? zalx}+1h_{Ox(WTWO%ZOB4Z2EOGn*L0+Z^$KBE6MK@&Ja9S_JX{xQ`C_AlWsuE{w$G z0~auH^9VfqsO}zFCL3g_^AeSjDEMfuiDZ z_9AqD2eMk;FlD{4mRmnQS9*I@mCbWXAzw@%KD2aJ(`89Ba$vORD8|4T7OBR@8~@GO zZ-%~*zD#_bIB>BzP?9d&y{hMntb!+NyChA66Ym3?y#R1n5jzcz_1OPP!892!16ZWq z5&5)yx7^rjYv|jNUsIW1Qklo8Y%P@q@8j5b4X`6u>&R7oIaJaPIiGJ<0{4l>w3|YG)DzXaO#inP#`psbvCqMQ~W)NNY+$6e9XF9?@C$I_1>dZ zcXZcHJ!f}G3!WSuwWezBBUR_m#{(ES;{qx3w64qO5cLm1_M1QPTTpYX@{ zK>LN*6`Vjgg~ljC9O25&t#*rgX2CwogN7 z_!4vGZ8zjYwcT32_^wO{ALzW}(SY_E-|iedp@TJ(^s^(ldI(_{0Y$??ofTLb$LA5| z5p+E2Gz20H0kdGd4$#Ublsnyg1|U{WU`sll6kJe;92{~-K%5AvI@)sStX<1!zP zlxV@pA}V6<11#|GBZE}uzeS!7mSCLpu{WTzeR!7~X-+=yo5n=`dsi$ojjT53CfHu4 zzE@J=s^XPkZz8NAI5}zqD~`=-B&y8EqJ8uRzZ$)J(s|VjNOhEy(RvF(0yvyqhE9ZQ zyu15j%J^5^TREo&vD9q;w63VyMLa^M4xA%%*5G%fQGm`z!zt3eQ*~j9GS!OSx`LZU&OZ#AYyQWbw8-`{A1_ z0-wEJ*r-pDLD;WvCIdhu#goR*A(GH1caStM?16`JSL7Tf5T{WHw2DymgKUu6F|JcE z*U&*S)zyX=A>;7Omm*pCCAuF)JVu}eH>dKMT}b(+tq`hjyWtb|%HmvCDDK+UdU@jj>w}$-Sa3B=lN5)%yG!^x|nfc zQ^}z1z1(&=3onByG7n#aPs4-QDE8TIO|gi9L3kWmAvc8kI3uQU%)s%+ILX7s#_jk` z{+@8NNj{kF0@ZV3YT}^aA!AozA{B diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 35542839b2c3d9561b16f954ca97edf495fa2d3c..ebebf1c4ce22ed9855c2fb0dbd7f89abe9f327ca 100644 GIT binary patch delta 4339 zcmcgvYfu~472dmAJ$VQrfdmL=!3H4#0@<-${2a$X?1m7VM^cm6vaDT%Kt}lPf^8uz z;$}K(hK_Nr8>cfFM@}0%Zs;UaGO4GTOx;Y9$xNL@$|y0TnLL^w{ng3fasD>#xhn}9 z;rM4)?6>!R=iJA+_wM*O-d?QOOMoiPQzZ&z1gb1gl`518 zsPY^|Vo0Mm17XP#Pn9V?6+l&r`YS4ic?0h3HDpq3RPKW)+{ht>O3B0OIMOV>r~ZybRV1PN2$>fH`s*yJBYg2IsDa!loVhH)EwIkJbsFT5x~C^CREgL)4jSg4{(u*GNbU! zUJ@¥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 delta 1762 zcmZWpYfKbZ6yCe+?9S}6GrP+oPhD14aDf#NFWJltRV1VTO^MC~)i3L`XGbK&pR#j8xI z+!S6(J1C^yOAM z{U+=Z+|oYkMS+IZG`N;oQXqOpd*?;Z1D^&QcK~3+`+t! zsA%;13>of98D~$%`TT`GWMCgMTMwYbN~~_eRl>5+Nwt~sO%#q%o{xoN5v4n%CIq^B zOIV4>2~#gF)d8xTBDf{|;qgMJAbDcck3tPj?H>6_uDzQEdcfR(7WgTf(tpYj!BHkU9n*7Zq`Dq1Fy}izb{M)anfi z$@3}Fx|msAhGDXJi*<}@6k6ez;`?Mw|8BKnG^#Jb=F(cxZ&b^u?x!#eL#4T749=Ct z$pMIz)jL?2=cyK^&!tM0(-4MmlXEsYT#`` z0B(D8Ag}xf=Q*mZrLY|#s7}I@@(Jz|u3rU5og1brHjru9UcCai%6u{iD=PzJ0CrYx zwoXwe3K2M8d7ex`{n`h%gwUr%2D)R)(HI1(o+F=PxEi=rRYg96_WBI)q;s5(J||gl zd-Vo#6@xdD*WpZcE4c{s)qZJ~2BvTnrv~_!$pr7ZGh~(~t>0L8iI5RERO>ZQP(zw< z4cw|N`PU9#qA-p-w3F*B^`W{P@-9r)Rhko~Xy~v4@w#pJ6}j~7zKmGt zvdy0X7aBU?Nke`MJASO3taYrA?8{k}%$r`(IuL8aKAhlQ8udI<14-8S|RLj w+z1yNPw%l4{EaMy(cZI>N0fj3Ok5!lJh@9@^vK!Q9#Q`BGjW-IYQ04M0o;bb82|tP diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 17137ff28f0f96e9601826e418300460a8a7b204..bd12b3adfb34ecea4c793b026372f8984a4fc870 100644 GIT binary patch delta 2143 zcmZ8iT}+c#81Csmh4w4*50z4&U=@%G@*|4MA}YedTw|8Gh_(GJWrgDVeKpMB&-7wH zI){7b%% zEvC(J%LVgci|%F*#+o+8t<#oE7L(~4`WfqWQ^@ve&Z%Ygjj1tR%ULs;3L$gOo>C90 zDJ|#J)o5%~1?`X$OM|*PIT2T+x2*iK+rJ8YwqfEo z<1+VQ>9$=AvA>Xjovi=dBWz=?6JEM|F)J-+7+~1JFi7z1(4APQ6jS0G6u&J({B|5r zj!;H^RKBlW$;v?nnZSaCI-vr!#YkG8ggwOX!7JVty44-;uNJWvuNGH{J@|9+MjKR; z6ul*Nb{W||>@I2XNhe-V6G=^1VMNn2adkq6NmA|2o+vpf$dgj`fw_YFDCd}j^!{W@ zg}tC9(EWxW;4+7Y~`TB z*MaCWNMs*Y-m@$}!h0?)WC%hcmlsP#H4W0~ap=cCsyjpm1OC1)jt|-ymMr|d<-?>c z9B1k%gV8@s4d8jduUFRetj`2Z|uIFo(k9~4$D3+`E6in{~- zot&_c846g(MsP*QXf;9fV)wga8BI@*kEGIxbVi5wNpcPE1nMd0PXle1e0Blr8322N zr^O2VDcBW~my}m4V$necJ^<_tGAh%hD^8tcYLl(W!V5pu~}d2x8_=41~XT6R8S~s$#k(E>I!!sFu zXPnxJ+A6AN=(!uGDUV0>D4b=nbuvn)%lJ;S;m5eODQP!m1;1^oPb@|H|Il)lXgm(c zHnOxF9ehd<$~QVn-s3aob~Es0-~z)%20nZ-Xr?~L&CSI|=Y7a~hP&B#bC-D&*OrD_ zX7@d!Unm}&WL<{GEbtN>Gd;*Jn%O>|U%ilDJ!h?kApW+x!u(i#;%vpn)^Jthv}w#j zigJzd)#aKtkJ<2kUNz3u7UE3ndM7s;FRUsL;G@?A=^b-t{(-A-j=xnb}^JU1DulQhWZQd`S$A%V=~I z)SmnzY=S_C6EV)roz7VWr;9-_M|HQd| iJRQ%Z)LytoJeuFEy`xPiR?+#;^c)JZ+d8)xPr!eOZs_Cy delta 2149 zcmZuyZETZO6z*-`er#p!=Gex*x~{-#!J6_gt-nR=T z9ZVC5$VY}p1A$=F5DoeZYxp4vf5c#-iPL}~uNsZfU&eqx{K6l4&MjMq@g{wG?)iAn zx##2F@8Q(`z&mASr4Cxg4=?J!MlS{aaQaTt_O!!tV8&I25!d7WDQ8aTV#;Ziux4T9 z94S}M`L@g9c#nRCwcQcZp0s^tc63Z8Ez|ZGa#9-DtW^p(uF+Z|8tKJcwio37tTZjC zqrL4-X2vafz%uzect%@S+Dt?R{me)38!hU)6|6Z^JsSw;A97z2B8)$K+C?okdY6j` zZt`vuEATb%gCc}iy{${aWM(fir4=_4&55>;>QFG(_hgaY-X+x z{&VcbtW?jig<+6kh+r-x{)p{K>Xt6eVkYqGV3T_ri8}G0;6Wl}*WU3&x54aK5|xm>>rq)f{!!av$2> z3CQf$4GHWH42FFu>sE{Hc(!h-Z!a-o0oa2d*X>xfi=|3Wbw!G|k|D{Uy5C!<6Pp3x zQ2o|mn8}ej*qy&n|E;Sr$9wJ_FbQJ9E}5?$Y{J)@x`c^0n>P0BVU>1z>%XqytRE=z+O(f`>D(d4b zKY@R@>Yl=Y;MmG#hl=svAF%C8q*8E-ZB(T=W_X1l<}Wmea`!58yBJ<(ILmO3ft}ww z3R9;EOh17tQ~@ANdq2EHsNhz9Nn4L|1D}84HB}dWR^S3_m(r~?cfGtt43kg0pgeZK&?jZ_ zVup((|BDqH@owkZ+TlA6;ja6{`&sbD;Ip&4(l>S)Uk)0xg9Z+-{+tG=>Oxd^``s_n;_s7 diff --git a/core/admin.py b/core/admin.py index 3402ea9..7e25da6 100644 --- a/core/admin.py +++ b/core/admin.py @@ -35,9 +35,14 @@ class TeacherAdmin(ActionsModelAdmin): @admin.register(Subject) class SubjectAdmin(ActionsModelAdmin): - list_display = ('name_en', 'name_ar', 'classroom', 'price', 'teacher', 'actions_column') - list_filter = ('classroom', 'teacher') + list_display = ('name_en', 'name_ar', 'classroom', 'price', 'get_teachers', 'actions_column') + list_filter = ('classroom', 'teachers') search_fields = ('name_en', 'name_ar') + filter_horizontal = ('teachers',) + + def get_teachers(self, obj): + return ", ".join([str(t) for t in obj.teachers.all()]) + get_teachers.short_description = 'Teachers' @admin.register(City) class CityAdmin(ActionsModelAdmin): @@ -67,9 +72,9 @@ class ResourceAdminForm(forms.ModelForm): @admin.register(Resource) class ResourceAdmin(ActionsModelAdmin): form = ResourceAdminForm - list_display = ('title_en', 'subject', 'created_at', 'actions_column') - list_filter = ('subject__classroom', 'subject') - fields = ('classroom', 'subject', 'title_en', 'title_ar', 'file', 'link') + list_display = ('title_en', 'subject', 'resource_type', 'created_at', 'actions_column') + list_filter = ('subject__classroom', 'subject', 'resource_type') + fields = ('classroom', 'subject', 'title_en', 'title_ar', 'resource_type', 'file', 'link') class Media: js = ('js/admin_resource.js',) diff --git a/core/migrations/0011_remove_subject_teacher_subject_teachers.py b/core/migrations/0011_remove_subject_teacher_subject_teachers.py new file mode 100644 index 0000000..7d5c73a --- /dev/null +++ b/core/migrations/0011_remove_subject_teacher_subject_teachers.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.7 on 2026-02-04 06:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_governorate_remove_student_moderate_remove_city_name_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='subject', + name='teacher', + ), + migrations.AddField( + model_name='subject', + name='teachers', + field=models.ManyToManyField(blank=True, related_name='subjects', to='core.teacher'), + ), + ] diff --git a/core/migrations/0012_subject_youtube_live_url.py b/core/migrations/0012_subject_youtube_live_url.py new file mode 100644 index 0000000..39439ae --- /dev/null +++ b/core/migrations/0012_subject_youtube_live_url.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-04 06:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0011_remove_subject_teacher_subject_teachers'), + ] + + operations = [ + migrations.AddField( + model_name='subject', + name='youtube_live_url', + field=models.URLField(blank=True, help_text='Paste the YouTube Live link here for large broadcasts (500+ students).', verbose_name='YouTube Live URL'), + ), + ] diff --git a/core/migrations/0013_resource_resource_type_alter_resource_link.py b/core/migrations/0013_resource_resource_type_alter_resource_link.py new file mode 100644 index 0000000..3cbec22 --- /dev/null +++ b/core/migrations/0013_resource_resource_type_alter_resource_link.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.7 on 2026-02-04 07:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0012_subject_youtube_live_url'), + ] + + operations = [ + migrations.AddField( + model_name='resource', + name='resource_type', + field=models.CharField(choices=[('FILE', 'File (PDF, Doc, etc.)'), ('VIDEO', 'Video Link (YouTube)'), ('LINK', 'External Link')], default='FILE', max_length=10, verbose_name='Resource Type'), + ), + migrations.AlterField( + model_name='resource', + name='link', + field=models.URLField(blank=True, help_text='YouTube URL for Video type, or external URL for Link type.', verbose_name='Link'), + ), + ] diff --git a/core/migrations/__pycache__/0011_remove_subject_teacher_subject_teachers.cpython-311.pyc b/core/migrations/__pycache__/0011_remove_subject_teacher_subject_teachers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81b93615083cc88bec2cc1639a5e0b842d87fe6b GIT binary patch literal 1020 zcmZuv&ui2`6rM@4*(AHgx(I@;)`Lj5ho-$3K?Mb|1+^&TGR+W+U_Y3MPc+DAlnhWmQev=7n2B=dA?yg=>RZ%MZ`;Y7VI z_p@FSr^1X9%EC0LI^|P|f#>NEU=K0mVTwGB;x6`d4Kbr%iGoj4{m|G4(FbssOI4Uv z1{F2fsrRkPj!Em(IQ$Nct<_WJAD>pqMk87*koK;BkfRy!jR>{8k6a| zu*MDP`=%7JSheY*)(QQ1OEftPK?^PWwJd4tya8kLCV;5D?y-=rwH;xRQVJ0ckxKKB z5h9v|yvhAgiG+-@0gGXlpvO`!&r&p=s~I23*9hxAr7Gfvc8vE-d(APta* z{1u%-2|6e4W)d+s@8ofoyKI{Ud6sbZUBb80fgdnePUZgnbGTsg^2FHEEHWONjJ>Gx zbaAl5<)JH(LAnhvoLq&8>{qzZwQJ_^Y2hGy?vpdJZXI90eSGysQPXYb1Oc2$C@f^p zN_s}oGsms@-MgdK{Fm(e!qGEw)Y|yb+8DJqin<}&0L~;74X0%epU8{PR#Mj5hOlYh zkGsht?Is0V!0jV6fB9FK)7UJ~W!QB_ O(|1lJarPfpefJMhZWI^* literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0012_subject_youtube_live_url.cpython-311.pyc b/core/migrations/__pycache__/0012_subject_youtube_live_url.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c41a52c5c0664c59c710fd4fbb939116231599f5 GIT binary patch literal 974 zcmZuv&rj4q6rO3j-E9SfXyPvpH5x;Mp^J$J6Jo*z6N7{(nz#p>w)5ES((SC%7J(Bn z9z1d|;Xm+4cuCyaZ@5&T`zT!7LPIOY8Yi4N5a2EUSza(m^UR&z96Qtf>cxVq%UYNO>8i9kkn~aAdM-!SK!iL4KMi_-6?SEA#iXIu zWNjsege|(3Z1~l>?@QbFv93q};}PF~R|JWwRg6I&Qq&N|kO_64vN6ZlyxKz;6-6l( zl{W9g;8MSnLv+y*GC6KqZd;ldaDE3k$Oc AJ^%m! literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0013_resource_resource_type_alter_resource_link.cpython-311.pyc b/core/migrations/__pycache__/0013_resource_resource_type_alter_resource_link.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d4b0636b33a372bf58317ed7a5fa363c97ad435 GIT binary patch literal 1297 zcmZuvzi-<{6h0m)N~EneQ4)A)T`*~yT7Y4t=@Jw{fS|Su+lpJHu8Rl=4ixWMqD+zM zkxXr6F^UcyJru~^A^qilqk9Gq#;u*Q$*`wPeRmQS#Oaau?!9~WzW2R*_j|SK5*+iV zKZSE9A^$3)7^R7Fb`O>3gcEK=q;Is0K5bEhQ1TVw^k>3NFnSm}9xqV)dup{zZhcGY zrB}*Xr_$|*9pPtT5~tGcCmfJ_9gHCH=! zuUr6h2)BMSc!}G*d{lX1ArYM8apb&BOk-6FFJieG%bD)X@L66xdhhM1izpr1a{2pM z;3eriEGok?tsoH~=WpNs><&xwb`OG#?I(Gbw}D0BQ((D>WJN%ldIaQBK9MFK+Irmuhaz(tB)p`y)Bi`lI5VAgaM?o%wfmRqYNA6@39EtPS%yP zPeHVk6jsG=5CPZaVM-XsE6?-n{Sz_WyG>?d?=il`>x?edAO&w=~!nHYU^0gK7+_ zG3Z5%>P*c(SXZC8S=6 literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 4fb8391..989c244 100644 --- a/core/models.py +++ b/core/models.py @@ -1,6 +1,7 @@ from django.db import models from django.contrib.auth.models import User from django.utils.translation import gettext_lazy as _ +import re class SingletonModel(models.Model): class Meta: @@ -38,7 +39,7 @@ class Classroom(models.Model): class Subject(models.Model): classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE, related_name='subjects') - teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, related_name='subjects') + teachers = models.ManyToManyField(Teacher, blank=True, related_name='subjects') name_en = models.CharField(_("Name (English)"), max_length=200) name_ar = models.CharField(_("Name (Arabic)"), max_length=200) description_en = models.TextField(_("Description (English)"), blank=True) @@ -47,21 +48,75 @@ class Subject(models.Model): image = models.ImageField(_("Image"), upload_to='subjects/', blank=True, null=True) google_drive_link = models.URLField(_("Google Drive Link"), blank=True) google_meet_link = models.URLField(_("Google Meet Link"), blank=True) + youtube_live_url = models.URLField(_("YouTube Live URL"), blank=True, help_text=_("Paste the YouTube Live link here for large broadcasts (500+ students).")) def __str__(self): return self.name_en + def get_youtube_id(self): + """Extracts the video ID from a YouTube URL.""" + if not self.youtube_live_url: + return None + # Handle various formats: + # https://youtu.be/VIDEO_ID + # https://www.youtube.com/watch?v=VIDEO_ID + # https://www.youtube.com/live/VIDEO_ID + import re + patterns = [ + r'(?:v=|\/)([0-9A-Za-z_-]{11}).*', + r'(?:youtu\.be\/)([0-9A-Za-z_-]{11})', + r'(?:live\/)([0-9A-Za-z_-]{11})', + ] + for pattern in patterns: + match = re.search(pattern, self.youtube_live_url) + if match: + return match.group(1) + return None + class Resource(models.Model): + RESOURCE_TYPES = ( + ('FILE', _('File (PDF, Doc, etc.)')), + ('VIDEO', _('Video Link (YouTube)')), + ('LINK', _('External Link')), + ) + subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='resources') title_en = models.CharField(_("Title (English)"), max_length=200) title_ar = models.CharField(_("Title (Arabic)"), max_length=200) + resource_type = models.CharField(_("Resource Type"), max_length=10, choices=RESOURCE_TYPES, default='FILE') file = models.FileField(_("File"), upload_to='resources/', blank=True, null=True) - link = models.URLField(_("External Link"), blank=True) + link = models.URLField(_("Link"), blank=True, help_text=_("YouTube URL for Video type, or external URL for Link type.")) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title_en + def get_youtube_id(self): + """Extracts the video ID from the link if it is a YouTube URL.""" + if not self.link or self.resource_type != 'VIDEO': + return None + + patterns = [ + r'(?:v=|\/)([0-9A-Za-z_-]{11}).*', + r'(?:youtu\.be\/)([0-9A-Za-z_-]{11})', + r'(?:live\/)([0-9A-Za-z_-]{11})', + ] + for pattern in patterns: + match = re.search(pattern, self.link) + if match: + return match.group(1) + return None + + def is_image(self): + if self.file: + return self.file.name.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')) + return False + + def is_pdf(self): + if self.file: + return self.file.name.lower().endswith('.pdf') + return False + class Governorate(models.Model): name_en = models.CharField(_("Name (English)"), max_length=100, default="") name_ar = models.CharField(_("Name (Arabic)"), max_length=100, default="") @@ -147,4 +202,4 @@ class PlatformSettings(SingletonModel): verbose_name_plural = "Platform Profile" def __str__(self): - return "Platform Profile" + return "Platform Profile" \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index a5f5d2e..ed0ad04 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -103,6 +103,50 @@ + +
+
+
+

{% trans "Meet Our Teachers" %}

+

{% trans "Learn from our expert instructors." %}

+
+ +
+ {% for teacher in teachers %} +
+
+
+ {% if teacher.avatar %} + {{ teacher.user.get_full_name }} + {% else %} +
+ {{ teacher.user.first_name|first|default:teacher.user.username|first|upper }} +
+ {% endif %} +
+
+ +
{{ teacher.user.get_full_name|default:teacher.user.username }}
+

{{ teacher.specialization|default:"Teacher" }}

+ +

+ {{ teacher.bio|default:"No bio available."|truncatewords:20 }} +

+ +
+ +
+
+
+ {% empty %} +
+

{% trans "No teachers found at the moment." %}

+
+ {% endfor %} +
+
+
+
diff --git a/core/templates/core/live_classroom.html b/core/templates/core/live_classroom.html index 35005d8..7cff1d0 100644 --- a/core/templates/core/live_classroom.html +++ b/core/templates/core/live_classroom.html @@ -5,53 +5,107 @@ {% block content %}
+ + {% with youtube_id=subject.get_youtube_id %} -
+ {% if subject.google_meet_link %} + +
+
+

{% trans "Live Class via Google Meet" %}

+

{% trans "This class is being held on Google Meet. Click the button below to join." %}

+ + {% trans "Join Google Meet" %} + +
+
+ + {% elif not is_teacher and youtube_id %} + +
+ +
+ +
+ +
+ +
+
+ + {% else %} + +
+ {% endif %} + + {% endwith %}
- - -{% endblock %} +{% with youtube_id=subject.get_youtube_id %} + + +{% if not subject.google_meet_link %} + {% if is_teacher or not youtube_id %} + + + {% endif %} +{% endif %} +{% endwith %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/student_dashboard.html b/core/templates/core/student_dashboard.html index 876f2b3..ef11fbd 100644 --- a/core/templates/core/student_dashboard.html +++ b/core/templates/core/student_dashboard.html @@ -124,7 +124,11 @@

{{ subject.description_en }}

- {{ subject.teacher.user.get_full_name|default:"No Teacher" }} + {% for teacher in subject.teachers.all %} + {{ teacher.user.get_full_name|default:teacher.user.username }}{% if not forloop.last %}, {% endif %} + {% empty %} + {% trans "No Teacher" %} + {% endfor %}
diff --git a/core/templates/core/subject_detail.html b/core/templates/core/subject_detail.html index 49302d7..5469111 100644 --- a/core/templates/core/subject_detail.html +++ b/core/templates/core/subject_detail.html @@ -36,17 +36,26 @@ {% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %} -
+ {% for teacher in subject.teachers.all %} +
- {{ subject.teacher.user.username|first|upper }} + {{ teacher.user.username|first|upper }}

{% trans "Teacher" %}

-
{{ subject.teacher }}
+
{{ teacher }}
+ {% empty %} +
+
+

{% trans "Teacher" %}

+
{% trans "Not Assigned" %}
+
+
+ {% endfor %} -

{% trans "About this Subject" %}

+

{% trans "About this Subject" %}

{% if LANGUAGE_CODE == 'ar' %} {{ subject.description_ar|linebreaks }} @@ -55,22 +64,65 @@ {% endif %}
-
-
-
-
🎥
-
{% trans "Live Sessions" %}
-

{% trans "Interactive online classes and group discussions." %}

-
+ +
+
+

{% trans "Course Resources" %}

+ {{ subject.resources.count }}
-
-
-
📁
-
{% trans "Study Materials" %}
-

{% trans "Comprehensive guides, notes, and practice exams." %}

+ + {% if subject.resources.all %} +
+ {% for resource in subject.resources.all %} +
+
+
+ {% if resource.resource_type == 'VIDEO' %} + + + + {% elif resource.resource_type == 'FILE' %} + + + + {% else %} + + + + + {% endif %} +
+
+
+ {% if LANGUAGE_CODE == 'ar' %}{{ resource.title_ar }}{% else %}{{ resource.title_en }}{% endif %} +
+ + {{ resource.get_resource_type_display }} + +
+
+
+ {% endfor %}
+ {% else %} +
+
📂
+
{% trans "No resources available yet." %}
+
+ {% endif %}
+
@@ -101,19 +153,102 @@

- -
-
-
{% trans "Share this course" %}
-
- - - -
-
-
+ + + + +{% endblock %} + +{% block extra_js %} + {% endblock %} \ No newline at end of file diff --git a/core/views.py b/core/views.py index b256573..0b5a69d 100644 --- a/core/views.py +++ b/core/views.py @@ -16,7 +16,8 @@ import string def index(request): levels = Classroom.objects.prefetch_related('subjects').all() - context = {'levels': levels} + teachers = Teacher.objects.select_related('user').all() + context = {'levels': levels, 'teachers': teachers} return render(request, 'core/index.html', context) def set_language(request, lang_code): @@ -253,7 +254,7 @@ def live_classroom(request, subject_id): is_teacher = False is_student = False - if hasattr(request.user, 'teacher_profile') and subject.teacher and subject.teacher.user == request.user: + if hasattr(request.user, 'teacher_profile') and request.user.teacher_profile in subject.teachers.all(): is_teacher = True if hasattr(request.user, 'student_profile') and request.user.student_profile.subscribed_subjects.filter(pk=subject_id).exists(): @@ -271,4 +272,4 @@ def live_classroom(request, subject_id): 'room_name': room_name, 'user_display_name': request.user.get_full_name() or request.user.username, 'is_teacher': is_teacher - }) + }) \ No newline at end of file