From 0f85ce4528e04879c6183bc8b63a14efb2c55cca Mon Sep 17 00:00:00 2001 From: ntkomata Date: Mon, 25 Mar 2019 15:10:06 +0800 Subject: [PATCH 0001/1137] fix student article change view --- gsoc/admin.py | 29 +++++++++++++++++++++++++++-- project.db | Bin 1302528 -> 1310720 bytes 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 7c403aa1..1ba5ad4d 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -58,13 +58,14 @@ def return_func(self, request, obj=None, **kwargs): 'is_featured', 'featured_image', 'lead_in', + )}), (_('Meta Options'), {'classes': ('collapse',), 'fields':()}), (_('Advanced Settings'), {'classes': ('collapse',), - 'fields': ()}), + 'fields': ('app_config',)}), ) self.readonly_fields = ( 'author', @@ -79,7 +80,30 @@ def return_func(self, request, obj=None, **kwargs): ) return form return return_func - +def Article_change_view(self, request, object_id, *args, **kwargs): + is_student_request = request.user.student_profile() is not None + data = request.GET.copy() + post_data = request.POST.copy() + try: + original_article = Article.objects.get(pk=object_id) + except Article.DoesNotExist: + return super(ArticleAdmin, self).change_view(request, object_id, *args, **kwargs) + if is_student_request: + timenow = original_article.publishing_date + try: + person = Person.objects.get(user=request.user) + post_data['author'] = person.pk + except Person.DoesNotExist: + person = Person.objects.create(user=request.user) + post_data['author'] = person.pk + post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' + post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' + post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' + post_data['initial-publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' + post_data['owner'] = request.user.pk + request.GET = data + request.POST = post_data + return super(ArticleAdmin, self).change_view(request, object_id, *args, **kwargs) def Article_add_view(self, request, *args, **kwargs): is_student_request = request.user.student_profile() is not None data = request.GET.copy() @@ -122,6 +146,7 @@ def Article_add_view(self, request, *args, **kwargs): ArticleAdmin.get_form = article_get_form() ArticleAdmin.add_view = Article_add_view +ArticleAdmin.change_view = Article_change_view admin.site.unregister(Article) admin.site.register(Article, ArticleAdmin) diff --git a/project.db b/project.db index e5de72a806f991a3da3989b863caf06be51b67f6..47bc78bb1dc3f1fdca337c6b479b34ce888568a7 100644 GIT binary patch delta 2024 zcmah~YfKbZ6rMXXyTk0`o*k(VWb1CgYKbiDYxWfoMU6iiP^?73P+3`^;DVF|sR+u7 zl{WoBmDKsAs7d3m7L?S9+LniDY7>n7XrrlsR&5KG3N1crq1ro(6iR`_q*Tu z<~!$R8oJgsG_3z1Ru!ZuwBn#Sjr>S5wx0?dG11zOS{QQ0>m>hg&$pUu%Y)G7_NA|vC$rzGwe$~NM!VLOPB z3<(eqFT;F%0e_BVd;g_FggAfj$s)dd6eW?xce=lcjI$p<-h$6wu$Lll&4mAfs3W9P}yJi~*239bk!g7BoF!kY@>N_=3eW$i* z4hI(~cm!kc6AZ$)&U+Z4 zWHhE_))UAe1(Wa)euH0N2yVe3fm{S>uoK10XuXcH8aW745Qe*O4KBi`Z~~gSEczhw zGpALn2zVuu{b8LMV?AR+(^}=`W?gj!;lHZzKOj=?z-=PpDml^vkp@crz`Q!Uf;4tX z2Rp_YWUW>ID2?E*P%r`aVHAdm&H=bWn7W@bU28)Lvoj^LOJ+DOFS6o6)M3uQrH{y= zMNS%M)i0faM_y>fHx=lQdc~O~C)swrbqe>Kb1?bTI}>Y`ACKzQW&(+!;5vK(N1zV2 zfDd8_gn@Vyr=c8t6ygz2*{wFO#pd<%BHssYieeEG$oIAi212Syp>V108|`6DifpJ55;b}IHmOt@`%_oi95oK-Ee z*J-hOp81>Paf?>3S3bO3C}(vnlu=LyG8}?_+0Y>5!Q#bC2Kqmx^1)^yWMEi$N9K+R zdmw%hvl7i-Or>l~=jw!oO+9S_i8)eFTXedH;fE;QYZ@azMBMz2UuEaClwdMN+_yb< z`=_r~O2zVLfBd18x>)(H@)w8V@{|g6scW_p9-qyVWcS)!Zink1C0r8fMzQ}*yG@MHmb|Fk>9dJR zHqq{KduF0dpH};v%4(;E7%@L=`WIj1o#${Rd8{Jw(&i78m6UIh?)0GaD0@O2c|aBY E7r>EJ9{>OV delta 1294 zcmZXUZA?>F7{~8hTiSbjpL=blC~U2C!w>{-d#Qa{rXmwkXO;2RmSro@7NOiq2lJ%} z6Lh8@5o)-f9ALKaP1wjhn=Zq-EOCnlGPn3)19_R5ZcL11E}QJ-pvyk!P5#Nr^ZcIQ zIXO2u-QDHgm-d~?(u4?t+}3B=ju(ZQS3ZcGF;iTBnzjTf_WkiUEMFRQ+RUCz+NoFT zrZvx`x92ymHT7iC1^WH%t>MtY_Gnw8t+laPiKZ{+2Xyk<9)_0n>gQK9rtyN!FOd|# zIjt02%7$KI-Ik0;Gi}lRO7nr1P^eXjY|5c4Np()lu#(!eQh1&$8nm_*Il@d)0-euYQ+2bH6YZF#5?tC+XHH#Pq;O~)21kRgEoz-7FgUF05;{M&m2j!y zP907Ljvftn8p1N8g)Raa#taRBC8ku@ghB0^U{it?t`hJE+=HKB2EK!ZHh_ugc5 z==vmU6EJXtfIndw7U32~ZeZXGP^XqyEt+0pH)dzI=V>*)_QC3vuyMD;zKg;%FevpnkUG zbk3@N4$v*L(Oiz%X#yU?Z?FV+U;(Bv`_+H!+$d>XWmjN8Vwk5$lr8bIgXy5Rg`ul($ghXE8Xm)_$W8Amw04pIOA#OGtizIEa zNMLue*6)x4@?NPnP~5yvX>azG?2hay@gCk2*x6KTuYOeXDpI>QR{T~&S?R%Ax#s_R zt-q)_P!B|b)SG6-9j76G)Ri!Py zvQjx7>}YBVcI>kGUJl!QRkclC#pZ6rvanpCgiGD=djAn6ob+yuCcTHfv4pcRpl)ju zb7~~nVsm*U*;C{ybQRfUm;LyQIt_jc&iPJ8i#yz|h9ePItf8gV;f~m&xIW-J>3FJs zBpy#joXu}`#B5FV4U(cBIwVSH;HY?j;b`b506{JhJ3*$c0W>cF1(G*5ypPD|g{tHP Date: Mon, 25 Mar 2019 15:11:44 +0800 Subject: [PATCH 0002/1137] restore db --- project.db | Bin 1310720 -> 1302528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 47bc78bb1dc3f1fdca337c6b479b34ce888568a7..e5de72a806f991a3da3989b863caf06be51b67f6 100644 GIT binary patch delta 1294 zcmZXUZA?>F7{~8hTiSbjpL=blC~U2C!w>{-d#Qa{rXmwkXO;2RmSro@7NOiq2lJ%} z6Lh8@5o)-f9ALKaP1wjhn=Zq-EOCnlGPn3)19_R5ZcL11E}QJ-pvyk!P5#Nr^ZcIQ zIXO2u-QDHgm-d~?(u4?t+}3B=ju(ZQS3ZcGF;iTBnzjTf_WkiUEMFRQ+RUCz+NoFT zrZvx`x92ymHT7iC1^WH%t>MtY_Gnw8t+laPiKZ{+2Xyk<9)_0n>gQK9rtyN!FOd|# zIjt02%7$KI-Ik0;Gi}lRO7nr1P^eXjY|5c4Np()lu#(!eQh1&$8nm_*Il@d)0-euYQ+2bH6YZF#5?tC+XHH#Pq;O~)21kRgEoz-7FgUF05;{M&m2j!y zP907Ljvftn8p1N8g)Raa#taRBC8ku@ghB0^U{it?t`hJE+=HKB2EK!ZHh_ugc5 z==vmU6EJXtfIndw7U32~ZeZXGP^XqyEt+0pH)dzI=V>*)_QC3vuyMD;zKg;%FevpnkUG zbk3@N4$v*L(Oiz%X#yU?Z?FV+U;(Bv`_+H!+$d>XWmjN8Vwk5$lr8bIgXy5Rg`ul($ghXE8Xm)_$W8Amw04pIOA#OGtizIEa zNMLue*6)x4@?NPnP~5yvX>azG?2hay@gCk2*x6KTuYOeXDpI>QR{T~&S?R%Ax#s_R zt-q)_P!B|b)SG6-9j76G)Ri!Py zvQjx7>}YBVcI>kGUJl!QRkclC#pZ6rvanpCgiGD=djAn6ob+yuCcTHfv4pcRpl)ju zb7~~nVsm*U*;C{ybQRfUm;LyQIt_jc&iPJ8i#yz|h9ePItf8gV;f~m&xIW-J>3FJs zBpy#joXu}`#B5FV4U(cBIwVSH;HY?j;b`b506{JhJ3*$c0W>cF1(G*5ypPD|g{tHPn7XrrlsR&5KG3N1crq1ro(6iR`_q*Tu z<~!$R8oJgsG_3z1Ru!ZuwBn#Sjr>S5wx0?dG11zOS{QQ0>m>hg&$pUu%Y)G7_NA|vC$rzGwe$~NM!VLOPB z3<(eqFT;F%0e_BVd;g_FggAfj$s)dd6eW?xce=lcjI$p<-h$6wu$Lll&4mAfs3W9P}yJi~*239bk!g7BoF!kY@>N_=3eW$i* z4hI(~cm!kc6AZ$)&U+Z4 zWHhE_))UAe1(Wa)euH0N2yVe3fm{S>uoK10XuXcH8aW745Qe*O4KBi`Z~~gSEczhw zGpALn2zVuu{b8LMV?AR+(^}=`W?gj!;lHZzKOj=?z-=PpDml^vkp@crz`Q!Uf;4tX z2Rp_YWUW>ID2?E*P%r`aVHAdm&H=bWn7W@bU28)Lvoj^LOJ+DOFS6o6)M3uQrH{y= zMNS%M)i0faM_y>fHx=lQdc~O~C)swrbqe>Kb1?bTI}>Y`ACKzQW&(+!;5vK(N1zV2 zfDd8_gn@Vyr=c8t6ygz2*{wFO#pd<%BHssYieeEG$oIAi212Syp>V108|`6DifpJ55;b}IHmOt@`%_oi95oK-Ee z*J-hOp81>Paf?>3S3bO3C}(vnlu=LyG8}?_+0Y>5!Q#bC2Kqmx^1)^yWMEi$N9K+R zdmw%hvl7i-Or>l~=jw!oO+9S_i8)eFTXedH;fE;QYZ@azMBMz2UuEaClwdMN+_yb< z`=_r~O2zVLfBd18x>)(H@)w8V@{|g6scW_p9-qyVWcS)!Zink1C0r8fMzQ}*yG@MHmb|Fk>9dJR zHqq{KduF0dpH};v%4(;E7%@L=`WIj1o#${Rd8{Jw(&i78m6UIh?)0GaD0@O2c|aBY E7r>EJ9{>OV From 6b3c808cfb24a2edc2b62fa25911f1d633a72a79 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Mon, 25 Mar 2019 15:19:29 +0800 Subject: [PATCH 0003/1137] will not choose first section for student blog post --- gsoc/admin.py | 6 ------ project.db | Bin 1302528 -> 1310720 bytes 2 files changed, 6 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 1ba5ad4d..ad942b25 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -121,12 +121,6 @@ def Article_add_view(self, request, *args, **kwargs): except Person.DoesNotExist: person = Person.objects.create(user=request.user) post_data['author'] = person.pk - try: - data['app_config'] = str(NewsBlogConfig.objects.first().pk) - post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) - print(data['app_config']) - except NewsBlogConfig.DoesNotExist: - pass post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' diff --git a/project.db b/project.db index e5de72a806f991a3da3989b863caf06be51b67f6..c43297d71408b2ac5628648715e7c5a82ddcc556 100644 GIT binary patch delta 996 zcmah{O>7%Q6rR~1+q-u5?baxfNJ1>TsIrK|+UvF5s8N%uV6~MSQW~W;3c-r)#E$br z){ae}m0ijLr_x$vPc*$$z0puhT~Uxo(*qzObq^fd1DqN_P^rI?3t}9&Rfs3OHzU3I zerDd(Hb-l<*FNcHa|q$#bDsTVGtjf~Mdq_U%6X|ldy(cXqnYqfYcpG`@Q2 zgk9~dGql1PH@;)t%|QPUrr>O&7})H2uHH=t@Qk)NJyq7S%S+3PxqLB8?eWsDW8){u z;cLzftfif>gec)Yf{JjzDZTZx(D?nrX^BYLN!D$+1J(Ts*VTA>p}X>e7x8$f@IB0d3NM2ne9#MB!cB;Sldz5n#=Sl#;|QoMjWHWG zZ+(tcOn^@e`v#t2gg=ERd-CWngz@~2L+E1Jc5=fS+?JR&{JmXmdJ^u z@en}^9unql*fHq_ilH$FJkg}!F@mS?m~?*yu5F8=X^y*EW5mmhC=C<%Hi8GRP0Zf` zCr8;4hcVYdgZW@e5=9ro??Xbt^`mo*-)D+TQa?X=dV*yWXNE==XG=?(n#q@ESITea zqS0tJmey8hE4hWpvbM5XNlhe!sfmP|IR0w>XdyF~m_H;ZCJu#Dh4E@~HBm_&JX%PG z4$r3I<7y)QN@etTGL$mDJth4R3{_WB!M$4Y#IfSEGL|1LjIZX#hK53NaBn~k2jtk! z3?qYbWFV$QG==0u1F+z8CzG}2W>v&I>P(NOLp;a=0%$eNq&C^3H%W@)$8tE z6kV-W&G^^uFMVsWo4L$v;`aP{5qu221SUbapmJyg{O@4yUwtJI`sc&$Ts9hsDXJ1F zWwnwgyHDP%2Iy=5*QZY delta 842 zcmZY7OH30{6b9g#&XiKx&YiId8bE14F^#m$%x&$%h!nJB&=JF2M1%h%4`y z+SY2uUKurD7^b1VU|^E^Qe{ToQ{|6$7ivo;+=P{^zViHPl;JI`$Ln-^dEO449+r1q z|FEijSjC$NNbM-PHGG%?8Q=sRzzSNyey~@~{mROg5nB1wY#`NF8#sp(5NH(LCO(d~ z+@=)GGyHpH(?qV5Z_V@)xr-wjgg{6{i_{=Gq!!U5b%+5mA|`}dCwIXol`PhL1L}_* zT1)?_aqy^1Ptxn8MTAE&_F%5O#MoWzm YW2{yadic^6mESV&(Wt#o&5v7u0$ip9r~m)} From 0e7fe6e909e86fd378c1a494f60ab10572cb1847 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Mon, 25 Mar 2019 15:20:43 +0800 Subject: [PATCH 0004/1137] restore db --- project.db | Bin 1310720 -> 1302528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index c43297d71408b2ac5628648715e7c5a82ddcc556..e5de72a806f991a3da3989b863caf06be51b67f6 100644 GIT binary patch delta 842 zcmZY7OH30{6b9g#&XiKx&YiId8bE14F^#m$%x&$%h!nJB&=JF2M1%h%4`y z+SY2uUKurD7^b1VU|^E^Qe{ToQ{|6$7ivo;+=P{^zViHPl;JI`$Ln-^dEO449+r1q z|FEijSjC$NNbM-PHGG%?8Q=sRzzSNyey~@~{mROg5nB1wY#`NF8#sp(5NH(LCO(d~ z+@=)GGyHpH(?qV5Z_V@)xr-wjgg{6{i_{=Gq!!U5b%+5mA|`}dCwIXol`PhL1L}_* zT1)?_aqy^1Ptxn8MTAE&_F%5O#MoWzm YW2{yadic^6mESV&(Wt#o&5v7u0$ip9r~m)} delta 996 zcmah{O>7%Q6rR~1+q-u5?baxfNJ1>TsIrK|+UvF5s8N%uV6~MSQW~W;3c-r)#E$br z){ae}m0ijLr_x$vPc*$$z0puhT~Uxo(*qzObq^fd1DqN_P^rI?3t}9&Rfs3OHzU3I zerDd(Hb-l<*FNcHa|q$#bDsTVGtjf~Mdq_U%6X|ldy(cXqnYqfYcpG`@Q2 zgk9~dGql1PH@;)t%|QPUrr>O&7})H2uHH=t@Qk)NJyq7S%S+3PxqLB8?eWsDW8){u z;cLzftfif>gec)Yf{JjzDZTZx(D?nrX^BYLN!D$+1J(Ts*VTA>p}X>e7x8$f@IB0d3NM2ne9#MB!cB;Sldz5n#=Sl#;|QoMjWHWG zZ+(tcOn^@e`v#t2gg=ERd-CWngz@~2L+E1Jc5=fS+?JR&{JmXmdJ^u z@en}^9unql*fHq_ilH$FJkg}!F@mS?m~?*yu5F8=X^y*EW5mmhC=C<%Hi8GRP0Zf` zCr8;4hcVYdgZW@e5=9ro??Xbt^`mo*-)D+TQa?X=dV*yWXNE==XG=?(n#q@ESITea zqS0tJmey8hE4hWpvbM5XNlhe!sfmP|IR0w>XdyF~m_H;ZCJu#Dh4E@~HBm_&JX%PG z4$r3I<7y)QN@etTGL$mDJth4R3{_WB!M$4Y#IfSEGL|1LjIZX#hK53NaBn~k2jtk! z3?qYbWFV$QG==0u1F+z8CzG}2W>v&I>P(NOLp;a=0%$eNq&C^3H%W@)$8tE z6kV-W&G^^uFMVsWo4L$v;`aP{5qu221SUbapmJyg{O@4yUwtJI`sc&$Ts9hsDXJ1F zWwnwgyHDP%2Iy=5*QZY From 6729f6a2e88db243130a99e375b29324ac88fe72 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 25 Mar 2019 18:28:01 +0530 Subject: [PATCH 0005/1137] Fix typo --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index c5eb1c39..aa78347a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,7 +20,7 @@ You can then access the site with the login bar with http://127.0.0.1:8000/en/?e ``` Default user/pass is `admin` for the superuser -Default student user is `Test-Student1` pass `^vM7d5*wK2R77V'` +Default student user is `Test-Student1` pass `^vM7d5*wK2R77V` ``` ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. From 753698f6b2731bb57b19b5955d90405d664b9e1b Mon Sep 17 00:00:00 2001 From: ntkomata Date: Thu, 28 Mar 2019 16:12:39 +0800 Subject: [PATCH 0006/1137] fix register page --- gsoc/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 4707dd7a..fc4e5d8c 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -130,12 +130,13 @@ def register_view(request): context['warning'] += 'Your username has been used!
' except User.DoesNotExist: pass - try: - User.objects.get(email=email) + + # Check if email's used + if email and User.objects.filter(email=email).first() is not None: info_valid = False context['warning'] += 'Your email has been used!
' - except User.DoesNotExist: - pass + + # Check password try: password_validation.validate_password(password) except ValidationError as e: From f85e943e50a8dbbc8ad4562c5610dabe985e4bd1 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Thu, 28 Mar 2019 16:17:27 +0800 Subject: [PATCH 0007/1137] fix register page --- gsoc/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 4707dd7a..1c971a7b 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -130,12 +130,13 @@ def register_view(request): context['warning'] += 'Your username has been used!
' except User.DoesNotExist: pass - try: - User.objects.get(email=email) + + + # Check if email's used + if email and User.objects.filter(email=email).first() is not None: info_valid = False context['warning'] += 'Your email has been used!
' - except User.DoesNotExist: - pass + try: password_validation.validate_password(password) except ValidationError as e: From 425cef1cd38c30dcc9153e5ff8ba67b89e2efe33 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 25 Mar 2019 19:34:32 +0530 Subject: [PATCH 0008/1137] Add CLIPBOARD_BREAK in cms toolbar --- gsoc/cms_toolbars.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index b5e3b48a..b5c6bfa1 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -1,4 +1,12 @@ -from cms.cms_toolbars import BasicToolbar +from cms.cms_toolbars import ( + BasicToolbar, + ADMIN_MENU_IDENTIFIER, + ADMINISTRATION_BREAK, + USER_SETTINGS_BREAK, + TOOLBAR_DISABLE_BREAK, + SHORTCUTS_BREAK, + CLIPBOARD_BREAK +) from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ @@ -7,14 +15,6 @@ from cms.utils.urlutils import admin_reverse -# # Identifiers for search -ADMIN_MENU_IDENTIFIER = 'admin-menu' -ADMINISTRATION_BREAK = 'Administration Break' -USER_SETTINGS_BREAK = 'User Settings Break' -TOOLBAR_DISABLE_BREAK = 'Toolbar disable Break' -SHORTCUTS_BREAK = 'Shortcuts Break' - - def add_admin_menu(self): if not self._admin_menu: self._admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, self.current_site.name) From c1da4af42572597af447bcdebcb4d58384ba49ac Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 25 Mar 2019 10:26:19 +0530 Subject: [PATCH 0009/1137] Add app_config field in UserProfile model --- gsoc/forms.py | 2 +- gsoc/models.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index 5eac8774..1acc927a 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -6,7 +6,7 @@ class UserProfileForm(ModelForm): class Meta: model = UserProfile - fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf') + fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') class ProposalUploadForm(ModelForm): diff --git a/gsoc/models.py b/gsoc/models.py index d4fff184..9fddd194 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -13,6 +13,9 @@ from django.utils import timezone from django.shortcuts import reverse +from aldryn_apphooks_config.fields import AppHookConfigField +from aldryn_newsblog.cms_appconfig import NewsBlogConfig + import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher @@ -44,6 +47,8 @@ class UserProfile(models.Model): gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) accepted_proposal_pdf = models.FileField(blank=True, null=True) + app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True) + def has_proposal(self): try: @@ -199,7 +204,6 @@ def get_help_text(self): validate_proposal_text = ProposalTextValidator() - def gen_uuid_str(): return str(uuid.uuid4()) From aad4222285fda657261e05d6a9e9af77ab673db2 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 25 Mar 2019 10:29:05 +0530 Subject: [PATCH 0010/1137] Add functionality to check for permissions before adding an Article --- gsoc/admin.py | 84 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 7c403aa1..afd67c74 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,4 +1,4 @@ -from .models import UserProfile, UserDetails, Scheduler +from .models import UserProfile, RegLink, UserDetails, Scheduler from .forms import UserProfileForm, UserDetailsForm from django.contrib.auth.models import User @@ -6,8 +6,8 @@ from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from .models import UserProfile, RegLink -from .forms import UserProfileForm +from django.core.exceptions import PermissionDenied + from aldryn_people.models import Person from aldryn_newsblog.admin import ArticleAdmin from aldryn_newsblog.models import Article @@ -59,12 +59,12 @@ def return_func(self, request, obj=None, **kwargs): 'featured_image', 'lead_in', )}), - (_('Meta Options'), - {'classes': ('collapse',), - 'fields':()}), + # (_('Meta Options'), + # {'classes': ('collapse',), + # 'fields':()}), (_('Advanced Settings'), {'classes': ('collapse',), - 'fields': ()}), + 'fields': ('app_config',)}), ) self.readonly_fields = ( 'author', @@ -97,12 +97,12 @@ def Article_add_view(self, request, *args, **kwargs): except Person.DoesNotExist: person = Person.objects.create(user=request.user) post_data['author'] = person.pk - try: - data['app_config'] = str(NewsBlogConfig.objects.first().pk) - post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) - print(data['app_config']) - except NewsBlogConfig.DoesNotExist: - pass + # try: + # data['app_config'] = str(NewsBlogConfig.objects.first().pk) + # post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) + # print(data['app_config']) + # except NewsBlogConfig.DoesNotExist: + # pass post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' @@ -120,6 +120,62 @@ def Article_add_view(self, request, *args, **kwargs): request.POST = post_data return super(ArticleAdmin, self).add_view(request, *args, **kwargs) +def Article_save_model(self, request, obj, form, change): + # checks whether user has add permission in the current + # section before adding to the blog + user = request.user + has_add_perm = False + if user.is_superuser: + has_add_perm = True + else: + userprofiles = user.userprofile_set.all() + for profile in userprofiles: + if profile.app_config == obj.app_config: + has_add_perm = True + break + + if has_add_perm: + super(ArticleAdmin, self).save_model(request, obj, form, change) + else: + raise PermissionDenied() + +def Article_delete_model(self, request, obj): + # checks whether user has delete permission in the current + # section before adding to the blog + user = request.user + has_delete_perm = False + if user.is_superuser: + has_delete_perm = True + else: + userprofiles = user.userprofile_set.all() + for profile in userprofiles: + if profile.app_config == obj.app_config: + has_delete_perm = True + break + + if has_delete_perm: + super(ArticleAdmin, self).delete_model(request, obj, form, change) + else: + raise PermissionDenied() + +def Article_get_queryset(self, request): + user = request.user + qs = Article.objects.all() + + if user.is_superuser: + return qs + else: + userprofiles = user.userprofile_set.all() + app_configs = [] + for profile in userprofiles: + app_configs.append(profile.app_config) + qs = qs.filter(app_config__in=app_configs) + print(qs) + return qs + +ArticleAdmin.save_model = Article_save_model +ArticleAdmin.delete_model = Article_delete_model +ArticleAdmin.get_queryset = Article_get_queryset ArticleAdmin.get_form = article_get_form() ArticleAdmin.add_view = Article_add_view @@ -127,7 +183,6 @@ def Article_add_view(self, request, *args, **kwargs): admin.site.register(Article, ArticleAdmin) - class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('url',)}), @@ -157,7 +212,6 @@ def get_readonly_fields(self, request, obj=None): else: return self.readonly_fields - admin.site.register(RegLink, RegLinkAdmin) From 1177387e12a715fdbdf642d93b19ace8a5fde763 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 25 Mar 2019 10:30:11 +0530 Subject: [PATCH 0011/1137] Add functionality to check for permissions before populating toolbar --- gsoc/cms_toolbars.py | 102 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index b5c6bfa1..def885b7 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -7,13 +7,18 @@ SHORTCUTS_BREAK, CLIPBOARD_BREAK ) +from cms.utils.conf import get_cms_setting +from cms.utils.urlutils import admin_reverse from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import get_language_from_request -from cms.utils.conf import get_cms_setting -from cms.utils.urlutils import admin_reverse - +from aldryn_translation_tools.utils import ( + get_admin_url, get_object_from_request, +) +from aldryn_newsblog.models import Article +from aldryn_newsblog.cms_toolbars import NewsBlogToolbar def add_admin_menu(self): if not self._admin_menu: @@ -66,4 +71,93 @@ def add_admin_menu(self): # logout self.add_logout_button(self._admin_menu) -BasicToolbar.add_admin_menu = add_admin_menu \ No newline at end of file +BasicToolbar.add_admin_menu = add_admin_menu + + +def populate(self): + config = self._NewsBlogToolbar__get_newsblog_config() + if not config: + # Do nothing if there is no NewsBlog app_config to work with + return + + user = getattr(self.request, 'user', None) + try: + view_name = self.request.resolver_match.view_name + except AttributeError: + view_name = None + + if user and view_name: + language = get_language_from_request(self.request, check_path=True) + + # If we're on an Article detail page, then get the article + if view_name == '{0}:article-detail'.format(config.namespace): + article = get_object_from_request(Article, self.request) + else: + article = None + + menu = self.toolbar.get_or_create_menu('newsblog-app', + config.get_app_title()) + + change_config_perm = user.has_perm( + 'aldryn_newsblog.change_newsblogconfig') + add_config_perm = user.has_perm( + 'aldryn_newsblog.add_newsblogconfig') + config_perms = [change_config_perm, add_config_perm] + + add_article_perm = False + userprofiles = user.userprofile_set.all() + for profile in userprofiles: + if profile.app_config == config: + add_article_perm = True + break + + delete_article_perm = user.is_superuser if article else False + change_article_perm = user.is_superuser + + article_perms = [change_article_perm, add_article_perm, + delete_article_perm, ] + + if change_config_perm: + url_args = {} + if language: + url_args = {'language': language, } + url = get_admin_url('aldryn_newsblog_newsblogconfig_change', + [config.pk, ], **url_args) + menu.add_modal_item(_('Configure addon'), url=url) + + if any(config_perms) and any(article_perms): + menu.add_break() + + if change_article_perm: + url_args = {} + if config: + url_args = {'app_config__id__exact': config.pk} + url = get_admin_url('aldryn_newsblog_article_changelist', + **url_args) + menu.add_sideframe_item(_('Article list'), url=url) + + if add_article_perm: + url_args = {'app_config': config.pk, 'owner': user.pk, } + if language: + url_args.update({'language': language, }) + url = get_admin_url('aldryn_newsblog_article_add', **url_args) + menu.add_modal_item(_('Add new article'), url=url) + + if change_article_perm and article: + url_args = {} + if language: + url_args = {'language': language, } + url = get_admin_url('aldryn_newsblog_article_change', + [article.pk, ], **url_args) + menu.add_modal_item(_('Edit this article'), url=url, + active=True) + + if delete_article_perm and article: + redirect_url = self.get_on_delete_redirect_url( + article, language=language) + url = get_admin_url('aldryn_newsblog_article_delete', + [article.pk, ]) + menu.add_modal_item(_('Delete this article'), url=url, + on_close=redirect_url) + +NewsBlogToolbar.populate = populate From 2631d37b99924648fc78bd5e2809ef12205c7528 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 26 Mar 2019 14:17:05 +0530 Subject: [PATCH 0012/1137] Add change article list option in the toolbar menu --- gsoc/cms_toolbars.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index def885b7..e0ea95ad 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -105,14 +105,15 @@ def populate(self): config_perms = [change_config_perm, add_config_perm] add_article_perm = False + change_article_perm = False userprofiles = user.userprofile_set.all() for profile in userprofiles: if profile.app_config == config: add_article_perm = True + change_article_perm = True break delete_article_perm = user.is_superuser if article else False - change_article_perm = user.is_superuser article_perms = [change_article_perm, add_article_perm, delete_article_perm, ] From 399e16cc61e36f50d673416095ab93edd73caa9d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 26 Mar 2019 14:18:06 +0530 Subject: [PATCH 0013/1137] Handle permissions for add article wizard --- gsoc/cms_wizards.py | 78 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 gsoc/cms_wizards.py diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py new file mode 100644 index 00000000..908e0885 --- /dev/null +++ b/gsoc/cms_wizards.py @@ -0,0 +1,78 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from cms.api import add_plugin +from cms.utils import permissions + +from djangocms_text_ckeditor.html import clean_html + +from aldryn_newsblog.cms_appconfig import NewsBlogConfig +from aldryn_newsblog.cms_wizards import ( + NewsBlogArticleWizard, + CreateNewsBlogArticleForm, + get_published_app_configs +) + + +def user_has_add_permission(self, user, **kwargs): + """ + Return True if the current user has permission to add an article. + :param user: The current user + :param kwargs: Ignored here + :return: True if user has add permission, else False + """ + # No one can create an Article, if there is no app_config yet. + num_configs = get_published_app_configs() + if not num_configs: + return False + + # Ensure user has permission to create articles. + if user.is_superuser or user.student_profile() is not None: + return True + + # By default, no permission. + return False + +NewsBlogArticleWizard.user_has_add_permission = user_has_add_permission + + +CreateNewsBlogArticleForm.Meta.fields = ['title'] + +def __init__(self, **kwargs): + super(CreateNewsBlogArticleForm, self).__init__(**kwargs) + + # If there's only 1 (or zero) app_configs, don't bother show the + # app_config choice field, we'll choose the option for the user. + app_configs = get_published_app_configs() + + userprofiles = self.user.userprofile_set.all() + app_config_choices = [] + for profile in userprofiles: + app_config_choices.append((profile.app_config.pk, profile.app_config.get_app_title())) + + self.fields['app_config'] = forms.ChoiceField( + label=_('Section'), + required=True, + choices=app_config_choices + ) + +def save(self, commit=True): + article = super(CreateNewsBlogArticleForm, self).save(commit=False) + article.owner = self.user + article.app_config = NewsBlogConfig.objects.filter(pk=self.cleaned_data['app_config']).first() + article.save() + + # If 'content' field has value, create a TextPlugin with same and add it to the PlaceholderField + content = clean_html(self.cleaned_data.get('content', ''), False) + if content and permissions.has_plugin_permission(self.user, 'TextPlugin', 'add'): + add_plugin( + placeholder=article.content, + plugin_type='TextPlugin', + language=self.language_code, + body=content, + ) + + return article + +CreateNewsBlogArticleForm.__init__ = __init__ +CreateNewsBlogArticleForm.save = save From 797843b4be90f64d0f988cb262ad874c772b26de Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 31 Mar 2019 01:10:37 +0530 Subject: [PATCH 0014/1137] Stop auto-redirection when selecting section in UserProfile form --- gsoc/forms.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index 1acc927a..0df51469 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,12 +1,16 @@ from .models import UserProfile, UserDetails -from django.forms import ModelForm, CheckboxSelectMultiple +from django.forms import ModelForm, CheckboxSelectMultiple, Select class UserProfileForm(ModelForm): class Meta: model = UserProfile fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + widgets = { + 'app_config': Select(), + } + class ProposalUploadForm(ModelForm): From e4706766b690b2f10af03e958137365e71fba1c8 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 Mar 2019 19:11:14 -0600 Subject: [PATCH 0015/1137] update database --- .../migrations/0011_userprofile_app_config.py | 21 ++++++++++++++++++ project.db | Bin 1302528 -> 1314816 bytes 2 files changed, 21 insertions(+) create mode 100644 gsoc/migrations/0011_userprofile_app_config.py diff --git a/gsoc/migrations/0011_userprofile_app_config.py b/gsoc/migrations/0011_userprofile_app_config.py new file mode 100644 index 00000000..62e8b545 --- /dev/null +++ b/gsoc/migrations/0011_userprofile_app_config.py @@ -0,0 +1,21 @@ +# Generated by Django 2.1.7 on 2019-03-30 19:09 + +import aldryn_apphooks_config.fields +from django.db import migrations +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('aldryn_newsblog', '0016_auto_20180329_1417'), + ('gsoc', '0010_auto_20190324_2216'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='app_config', + field=aldryn_apphooks_config.fields.AppHookConfigField(blank=True, help_text='When selecting a value, the form is reloaded to get the updated default', null=True, on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.NewsBlogConfig', verbose_name='Section'), + ), + ] diff --git a/project.db b/project.db index e5de72a806f991a3da3989b863caf06be51b67f6..0a0458a3e395a697e283add05cfa0b5b5d6b4365 100644 GIT binary patch delta 1218 zcmah}Uu;uV7{BNK>FvF@y|?#PS7mBTM>N5;+&}F~8^$b5uqNFY*)ms#-FEwj{#m=F}W?ZX1$!N%!>Z}7H6iG&1SmZ`UZ5ESD{e&;0T z`%cdH`+nc|&DQ&8YX?4T=MxA5g2;nql;=AYkoa1hX?o$;?UbNK zj~C)cr;g?mscbwN&1Yi^ceZ~891EYmm?1rFWgJr|_}vx4y5Q=xfeXtocg`0CMRZ91 zfFuSa+1(|1x@7U)^|NsOEO-XZL7wGm)NL~U- zl-<;WIL#6`Mgb2*zXgA=gdQ^BHd=d-+tAoLxXI~v;3cKf1eGRQX=0ToUTG4QrlHa# zSDHrLg6#wdMRF$l#t#c(5_wUPw<&d{ej$|W)`_f_g)l6S+UpibG zP{O65Vr24QDm*zNhkN0b8UC6i7pEhVzcBLJ!K|tbr25lCZzcx1yYCVAxFqGihO)=) z+v)Lk`L)Hj_#BSm+@wa%@(FI+HY^POFG6kp1HNFIaH8zCs>hFLU4Eb?vFTFoELcHC)kV8p0klU|s_^ zNt@4yR`SQjC$jOD#*gURs^7<6!p|Cl0~Xepfc9rY7y)xjtAe()Y8|B7jah^yiiqhJ z(@TF$lN4jH8>hKZ_7kG0D>_Pq${p1X(ssy&mP4srEMENA2M?<%nuOV delta 805 zcmZY7NlX(_7zglqohh@GnNFLC5hKLqBD9&AN9kgMVJi-Cgg~K7j6f++x&T@QS`DIv z;K8VX=^ViXgBKPh@fi~%azJkqH74HR#<9_G)cE3o1HR;U`6vJHJG^glv2}8@d3 zMG&BdIDAaSY$XI;FS~!2(a-ru+yQs6A$33CSj!wy>ShNr!}AB{UxCv37Zphw9=k`8 zkTpJhGTY}Jl_g+hqp@-}V^h^~+3Rz8e5&e%F0ZV*r}h_&{RME6gu-WoUIZQJ+9F5_ zv#pF#DB~&gbrmRpgrdoy*TDeZ_*5`V@4=t^dNH+373|g-Y6oB@jKnC+j1^(@GPT24 zNjF$B=v`(Qmz`i{X^+8JjKg@$f)!%|CZeT1>(!zBE?I<%y4ixr(F6d1B}}CFx?7^G z7Lv~sLc;Qm&+{^}j!2c<-B?yh#6r#4NYEvR;1xL%svT@eWd`;7=Gf(WZR~QWE*?=k z{$*N9&S;6+o6#o!tqANq%p<|7!BG1c?kK6~P+PXIU8@deHK+&usc^8dDjaA_>uP5* zoajvJfx2`!5sQZd{-oaIhv{%G9uMal6}_QX(F2jVmQuX^cvvrNOZED_>7L+tsyC}u z_hq#)EivNm4;eK#q|==;ELS`}8TzU`PEVBzJ?i}nv>E^Vsy^LUoAY|2<1tSnnjCg{ zV`?AH^ZK;wzkEEM&c@t>*K-La-V>Em#^|Udqx@}Yh$2aG4$+fRzj#(e#JQsrk2cZl zV<}_Br6oi!N)zH4e0?>ltau$Vbg1rfS@uEM=Ymc*gf6I>Pm#62czZ`WF|)NMp{+IB zMQaHaA{bMSELvf|upD!Vjtg(ZccvUVzG;h^sHw<92~BL->P+*Kd+ From 475f234cbbbc9beec9e80d12cc37fa8658c73f4c Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 Mar 2019 20:32:29 -0600 Subject: [PATCH 0016/1137] Revert "Student blog setup" --- gsoc/admin.py | 84 +++++++---------------------------- gsoc/cms_toolbars.py | 103 ++----------------------------------------- gsoc/cms_wizards.py | 78 -------------------------------- gsoc/forms.py | 8 +--- gsoc/models.py | 6 +-- 5 files changed, 22 insertions(+), 257 deletions(-) delete mode 100644 gsoc/cms_wizards.py diff --git a/gsoc/admin.py b/gsoc/admin.py index afd67c74..7c403aa1 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,4 +1,4 @@ -from .models import UserProfile, RegLink, UserDetails, Scheduler +from .models import UserProfile, UserDetails, Scheduler from .forms import UserProfileForm, UserDetailsForm from django.contrib.auth.models import User @@ -6,8 +6,8 @@ from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from django.core.exceptions import PermissionDenied - +from .models import UserProfile, RegLink +from .forms import UserProfileForm from aldryn_people.models import Person from aldryn_newsblog.admin import ArticleAdmin from aldryn_newsblog.models import Article @@ -59,12 +59,12 @@ def return_func(self, request, obj=None, **kwargs): 'featured_image', 'lead_in', )}), - # (_('Meta Options'), - # {'classes': ('collapse',), - # 'fields':()}), + (_('Meta Options'), + {'classes': ('collapse',), + 'fields':()}), (_('Advanced Settings'), {'classes': ('collapse',), - 'fields': ('app_config',)}), + 'fields': ()}), ) self.readonly_fields = ( 'author', @@ -97,12 +97,12 @@ def Article_add_view(self, request, *args, **kwargs): except Person.DoesNotExist: person = Person.objects.create(user=request.user) post_data['author'] = person.pk - # try: - # data['app_config'] = str(NewsBlogConfig.objects.first().pk) - # post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) - # print(data['app_config']) - # except NewsBlogConfig.DoesNotExist: - # pass + try: + data['app_config'] = str(NewsBlogConfig.objects.first().pk) + post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) + print(data['app_config']) + except NewsBlogConfig.DoesNotExist: + pass post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' @@ -120,62 +120,6 @@ def Article_add_view(self, request, *args, **kwargs): request.POST = post_data return super(ArticleAdmin, self).add_view(request, *args, **kwargs) -def Article_save_model(self, request, obj, form, change): - # checks whether user has add permission in the current - # section before adding to the blog - user = request.user - has_add_perm = False - if user.is_superuser: - has_add_perm = True - else: - userprofiles = user.userprofile_set.all() - for profile in userprofiles: - if profile.app_config == obj.app_config: - has_add_perm = True - break - - if has_add_perm: - super(ArticleAdmin, self).save_model(request, obj, form, change) - else: - raise PermissionDenied() - -def Article_delete_model(self, request, obj): - # checks whether user has delete permission in the current - # section before adding to the blog - user = request.user - has_delete_perm = False - if user.is_superuser: - has_delete_perm = True - else: - userprofiles = user.userprofile_set.all() - for profile in userprofiles: - if profile.app_config == obj.app_config: - has_delete_perm = True - break - - if has_delete_perm: - super(ArticleAdmin, self).delete_model(request, obj, form, change) - else: - raise PermissionDenied() - -def Article_get_queryset(self, request): - user = request.user - qs = Article.objects.all() - - if user.is_superuser: - return qs - else: - userprofiles = user.userprofile_set.all() - app_configs = [] - for profile in userprofiles: - app_configs.append(profile.app_config) - qs = qs.filter(app_config__in=app_configs) - print(qs) - return qs - -ArticleAdmin.save_model = Article_save_model -ArticleAdmin.delete_model = Article_delete_model -ArticleAdmin.get_queryset = Article_get_queryset ArticleAdmin.get_form = article_get_form() ArticleAdmin.add_view = Article_add_view @@ -183,6 +127,7 @@ def Article_get_queryset(self, request): admin.site.register(Article, ArticleAdmin) + class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('url',)}), @@ -212,6 +157,7 @@ def get_readonly_fields(self, request, obj=None): else: return self.readonly_fields + admin.site.register(RegLink, RegLinkAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index e0ea95ad..b5c6bfa1 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -7,18 +7,13 @@ SHORTCUTS_BREAK, CLIPBOARD_BREAK ) -from cms.utils.conf import get_cms_setting -from cms.utils.urlutils import admin_reverse from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import get_language_from_request -from aldryn_translation_tools.utils import ( - get_admin_url, get_object_from_request, -) -from aldryn_newsblog.models import Article -from aldryn_newsblog.cms_toolbars import NewsBlogToolbar +from cms.utils.conf import get_cms_setting +from cms.utils.urlutils import admin_reverse + def add_admin_menu(self): if not self._admin_menu: @@ -71,94 +66,4 @@ def add_admin_menu(self): # logout self.add_logout_button(self._admin_menu) -BasicToolbar.add_admin_menu = add_admin_menu - - -def populate(self): - config = self._NewsBlogToolbar__get_newsblog_config() - if not config: - # Do nothing if there is no NewsBlog app_config to work with - return - - user = getattr(self.request, 'user', None) - try: - view_name = self.request.resolver_match.view_name - except AttributeError: - view_name = None - - if user and view_name: - language = get_language_from_request(self.request, check_path=True) - - # If we're on an Article detail page, then get the article - if view_name == '{0}:article-detail'.format(config.namespace): - article = get_object_from_request(Article, self.request) - else: - article = None - - menu = self.toolbar.get_or_create_menu('newsblog-app', - config.get_app_title()) - - change_config_perm = user.has_perm( - 'aldryn_newsblog.change_newsblogconfig') - add_config_perm = user.has_perm( - 'aldryn_newsblog.add_newsblogconfig') - config_perms = [change_config_perm, add_config_perm] - - add_article_perm = False - change_article_perm = False - userprofiles = user.userprofile_set.all() - for profile in userprofiles: - if profile.app_config == config: - add_article_perm = True - change_article_perm = True - break - - delete_article_perm = user.is_superuser if article else False - - article_perms = [change_article_perm, add_article_perm, - delete_article_perm, ] - - if change_config_perm: - url_args = {} - if language: - url_args = {'language': language, } - url = get_admin_url('aldryn_newsblog_newsblogconfig_change', - [config.pk, ], **url_args) - menu.add_modal_item(_('Configure addon'), url=url) - - if any(config_perms) and any(article_perms): - menu.add_break() - - if change_article_perm: - url_args = {} - if config: - url_args = {'app_config__id__exact': config.pk} - url = get_admin_url('aldryn_newsblog_article_changelist', - **url_args) - menu.add_sideframe_item(_('Article list'), url=url) - - if add_article_perm: - url_args = {'app_config': config.pk, 'owner': user.pk, } - if language: - url_args.update({'language': language, }) - url = get_admin_url('aldryn_newsblog_article_add', **url_args) - menu.add_modal_item(_('Add new article'), url=url) - - if change_article_perm and article: - url_args = {} - if language: - url_args = {'language': language, } - url = get_admin_url('aldryn_newsblog_article_change', - [article.pk, ], **url_args) - menu.add_modal_item(_('Edit this article'), url=url, - active=True) - - if delete_article_perm and article: - redirect_url = self.get_on_delete_redirect_url( - article, language=language) - url = get_admin_url('aldryn_newsblog_article_delete', - [article.pk, ]) - menu.add_modal_item(_('Delete this article'), url=url, - on_close=redirect_url) - -NewsBlogToolbar.populate = populate +BasicToolbar.add_admin_menu = add_admin_menu \ No newline at end of file diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py deleted file mode 100644 index 908e0885..00000000 --- a/gsoc/cms_wizards.py +++ /dev/null @@ -1,78 +0,0 @@ -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from cms.api import add_plugin -from cms.utils import permissions - -from djangocms_text_ckeditor.html import clean_html - -from aldryn_newsblog.cms_appconfig import NewsBlogConfig -from aldryn_newsblog.cms_wizards import ( - NewsBlogArticleWizard, - CreateNewsBlogArticleForm, - get_published_app_configs -) - - -def user_has_add_permission(self, user, **kwargs): - """ - Return True if the current user has permission to add an article. - :param user: The current user - :param kwargs: Ignored here - :return: True if user has add permission, else False - """ - # No one can create an Article, if there is no app_config yet. - num_configs = get_published_app_configs() - if not num_configs: - return False - - # Ensure user has permission to create articles. - if user.is_superuser or user.student_profile() is not None: - return True - - # By default, no permission. - return False - -NewsBlogArticleWizard.user_has_add_permission = user_has_add_permission - - -CreateNewsBlogArticleForm.Meta.fields = ['title'] - -def __init__(self, **kwargs): - super(CreateNewsBlogArticleForm, self).__init__(**kwargs) - - # If there's only 1 (or zero) app_configs, don't bother show the - # app_config choice field, we'll choose the option for the user. - app_configs = get_published_app_configs() - - userprofiles = self.user.userprofile_set.all() - app_config_choices = [] - for profile in userprofiles: - app_config_choices.append((profile.app_config.pk, profile.app_config.get_app_title())) - - self.fields['app_config'] = forms.ChoiceField( - label=_('Section'), - required=True, - choices=app_config_choices - ) - -def save(self, commit=True): - article = super(CreateNewsBlogArticleForm, self).save(commit=False) - article.owner = self.user - article.app_config = NewsBlogConfig.objects.filter(pk=self.cleaned_data['app_config']).first() - article.save() - - # If 'content' field has value, create a TextPlugin with same and add it to the PlaceholderField - content = clean_html(self.cleaned_data.get('content', ''), False) - if content and permissions.has_plugin_permission(self.user, 'TextPlugin', 'add'): - add_plugin( - placeholder=article.content, - plugin_type='TextPlugin', - language=self.language_code, - body=content, - ) - - return article - -CreateNewsBlogArticleForm.__init__ = __init__ -CreateNewsBlogArticleForm.save = save diff --git a/gsoc/forms.py b/gsoc/forms.py index 0df51469..5eac8774 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,16 +1,12 @@ from .models import UserProfile, UserDetails -from django.forms import ModelForm, CheckboxSelectMultiple, Select +from django.forms import ModelForm, CheckboxSelectMultiple class UserProfileForm(ModelForm): class Meta: model = UserProfile - fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') - widgets = { - 'app_config': Select(), - } - + fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf') class ProposalUploadForm(ModelForm): diff --git a/gsoc/models.py b/gsoc/models.py index 9fddd194..d4fff184 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -13,9 +13,6 @@ from django.utils import timezone from django.shortcuts import reverse -from aldryn_apphooks_config.fields import AppHookConfigField -from aldryn_newsblog.cms_appconfig import NewsBlogConfig - import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher @@ -47,8 +44,6 @@ class UserProfile(models.Model): gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) accepted_proposal_pdf = models.FileField(blank=True, null=True) - app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True) - def has_proposal(self): try: @@ -204,6 +199,7 @@ def get_help_text(self): validate_proposal_text = ProposalTextValidator() + def gen_uuid_str(): return str(uuid.uuid4()) From 040fd1cfe15f42567c2eef05ec7d0f2de1da1c54 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 Mar 2019 20:34:27 -0600 Subject: [PATCH 0017/1137] Revert "Revert "Student blog setup"" --- gsoc/admin.py | 84 ++++++++++++++++++++++++++++------- gsoc/cms_toolbars.py | 103 +++++++++++++++++++++++++++++++++++++++++-- gsoc/cms_wizards.py | 78 ++++++++++++++++++++++++++++++++ gsoc/forms.py | 8 +++- gsoc/models.py | 6 ++- 5 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 gsoc/cms_wizards.py diff --git a/gsoc/admin.py b/gsoc/admin.py index 7c403aa1..afd67c74 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,4 +1,4 @@ -from .models import UserProfile, UserDetails, Scheduler +from .models import UserProfile, RegLink, UserDetails, Scheduler from .forms import UserProfileForm, UserDetailsForm from django.contrib.auth.models import User @@ -6,8 +6,8 @@ from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from .models import UserProfile, RegLink -from .forms import UserProfileForm +from django.core.exceptions import PermissionDenied + from aldryn_people.models import Person from aldryn_newsblog.admin import ArticleAdmin from aldryn_newsblog.models import Article @@ -59,12 +59,12 @@ def return_func(self, request, obj=None, **kwargs): 'featured_image', 'lead_in', )}), - (_('Meta Options'), - {'classes': ('collapse',), - 'fields':()}), + # (_('Meta Options'), + # {'classes': ('collapse',), + # 'fields':()}), (_('Advanced Settings'), {'classes': ('collapse',), - 'fields': ()}), + 'fields': ('app_config',)}), ) self.readonly_fields = ( 'author', @@ -97,12 +97,12 @@ def Article_add_view(self, request, *args, **kwargs): except Person.DoesNotExist: person = Person.objects.create(user=request.user) post_data['author'] = person.pk - try: - data['app_config'] = str(NewsBlogConfig.objects.first().pk) - post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) - print(data['app_config']) - except NewsBlogConfig.DoesNotExist: - pass + # try: + # data['app_config'] = str(NewsBlogConfig.objects.first().pk) + # post_data['app_config'] = str(NewsBlogConfig.objects.first().pk) + # print(data['app_config']) + # except NewsBlogConfig.DoesNotExist: + # pass post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' @@ -120,6 +120,62 @@ def Article_add_view(self, request, *args, **kwargs): request.POST = post_data return super(ArticleAdmin, self).add_view(request, *args, **kwargs) +def Article_save_model(self, request, obj, form, change): + # checks whether user has add permission in the current + # section before adding to the blog + user = request.user + has_add_perm = False + if user.is_superuser: + has_add_perm = True + else: + userprofiles = user.userprofile_set.all() + for profile in userprofiles: + if profile.app_config == obj.app_config: + has_add_perm = True + break + + if has_add_perm: + super(ArticleAdmin, self).save_model(request, obj, form, change) + else: + raise PermissionDenied() + +def Article_delete_model(self, request, obj): + # checks whether user has delete permission in the current + # section before adding to the blog + user = request.user + has_delete_perm = False + if user.is_superuser: + has_delete_perm = True + else: + userprofiles = user.userprofile_set.all() + for profile in userprofiles: + if profile.app_config == obj.app_config: + has_delete_perm = True + break + + if has_delete_perm: + super(ArticleAdmin, self).delete_model(request, obj, form, change) + else: + raise PermissionDenied() + +def Article_get_queryset(self, request): + user = request.user + qs = Article.objects.all() + + if user.is_superuser: + return qs + else: + userprofiles = user.userprofile_set.all() + app_configs = [] + for profile in userprofiles: + app_configs.append(profile.app_config) + qs = qs.filter(app_config__in=app_configs) + print(qs) + return qs + +ArticleAdmin.save_model = Article_save_model +ArticleAdmin.delete_model = Article_delete_model +ArticleAdmin.get_queryset = Article_get_queryset ArticleAdmin.get_form = article_get_form() ArticleAdmin.add_view = Article_add_view @@ -127,7 +183,6 @@ def Article_add_view(self, request, *args, **kwargs): admin.site.register(Article, ArticleAdmin) - class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('url',)}), @@ -157,7 +212,6 @@ def get_readonly_fields(self, request, obj=None): else: return self.readonly_fields - admin.site.register(RegLink, RegLinkAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index b5c6bfa1..e0ea95ad 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -7,13 +7,18 @@ SHORTCUTS_BREAK, CLIPBOARD_BREAK ) +from cms.utils.conf import get_cms_setting +from cms.utils.urlutils import admin_reverse from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import get_language_from_request -from cms.utils.conf import get_cms_setting -from cms.utils.urlutils import admin_reverse - +from aldryn_translation_tools.utils import ( + get_admin_url, get_object_from_request, +) +from aldryn_newsblog.models import Article +from aldryn_newsblog.cms_toolbars import NewsBlogToolbar def add_admin_menu(self): if not self._admin_menu: @@ -66,4 +71,94 @@ def add_admin_menu(self): # logout self.add_logout_button(self._admin_menu) -BasicToolbar.add_admin_menu = add_admin_menu \ No newline at end of file +BasicToolbar.add_admin_menu = add_admin_menu + + +def populate(self): + config = self._NewsBlogToolbar__get_newsblog_config() + if not config: + # Do nothing if there is no NewsBlog app_config to work with + return + + user = getattr(self.request, 'user', None) + try: + view_name = self.request.resolver_match.view_name + except AttributeError: + view_name = None + + if user and view_name: + language = get_language_from_request(self.request, check_path=True) + + # If we're on an Article detail page, then get the article + if view_name == '{0}:article-detail'.format(config.namespace): + article = get_object_from_request(Article, self.request) + else: + article = None + + menu = self.toolbar.get_or_create_menu('newsblog-app', + config.get_app_title()) + + change_config_perm = user.has_perm( + 'aldryn_newsblog.change_newsblogconfig') + add_config_perm = user.has_perm( + 'aldryn_newsblog.add_newsblogconfig') + config_perms = [change_config_perm, add_config_perm] + + add_article_perm = False + change_article_perm = False + userprofiles = user.userprofile_set.all() + for profile in userprofiles: + if profile.app_config == config: + add_article_perm = True + change_article_perm = True + break + + delete_article_perm = user.is_superuser if article else False + + article_perms = [change_article_perm, add_article_perm, + delete_article_perm, ] + + if change_config_perm: + url_args = {} + if language: + url_args = {'language': language, } + url = get_admin_url('aldryn_newsblog_newsblogconfig_change', + [config.pk, ], **url_args) + menu.add_modal_item(_('Configure addon'), url=url) + + if any(config_perms) and any(article_perms): + menu.add_break() + + if change_article_perm: + url_args = {} + if config: + url_args = {'app_config__id__exact': config.pk} + url = get_admin_url('aldryn_newsblog_article_changelist', + **url_args) + menu.add_sideframe_item(_('Article list'), url=url) + + if add_article_perm: + url_args = {'app_config': config.pk, 'owner': user.pk, } + if language: + url_args.update({'language': language, }) + url = get_admin_url('aldryn_newsblog_article_add', **url_args) + menu.add_modal_item(_('Add new article'), url=url) + + if change_article_perm and article: + url_args = {} + if language: + url_args = {'language': language, } + url = get_admin_url('aldryn_newsblog_article_change', + [article.pk, ], **url_args) + menu.add_modal_item(_('Edit this article'), url=url, + active=True) + + if delete_article_perm and article: + redirect_url = self.get_on_delete_redirect_url( + article, language=language) + url = get_admin_url('aldryn_newsblog_article_delete', + [article.pk, ]) + menu.add_modal_item(_('Delete this article'), url=url, + on_close=redirect_url) + +NewsBlogToolbar.populate = populate diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py new file mode 100644 index 00000000..908e0885 --- /dev/null +++ b/gsoc/cms_wizards.py @@ -0,0 +1,78 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from cms.api import add_plugin +from cms.utils import permissions + +from djangocms_text_ckeditor.html import clean_html + +from aldryn_newsblog.cms_appconfig import NewsBlogConfig +from aldryn_newsblog.cms_wizards import ( + NewsBlogArticleWizard, + CreateNewsBlogArticleForm, + get_published_app_configs +) + + +def user_has_add_permission(self, user, **kwargs): + """ + Return True if the current user has permission to add an article. + :param user: The current user + :param kwargs: Ignored here + :return: True if user has add permission, else False + """ + # No one can create an Article, if there is no app_config yet. + num_configs = get_published_app_configs() + if not num_configs: + return False + + # Ensure user has permission to create articles. + if user.is_superuser or user.student_profile() is not None: + return True + + # By default, no permission. + return False + +NewsBlogArticleWizard.user_has_add_permission = user_has_add_permission + + +CreateNewsBlogArticleForm.Meta.fields = ['title'] + +def __init__(self, **kwargs): + super(CreateNewsBlogArticleForm, self).__init__(**kwargs) + + # If there's only 1 (or zero) app_configs, don't bother show the + # app_config choice field, we'll choose the option for the user. + app_configs = get_published_app_configs() + + userprofiles = self.user.userprofile_set.all() + app_config_choices = [] + for profile in userprofiles: + app_config_choices.append((profile.app_config.pk, profile.app_config.get_app_title())) + + self.fields['app_config'] = forms.ChoiceField( + label=_('Section'), + required=True, + choices=app_config_choices + ) + +def save(self, commit=True): + article = super(CreateNewsBlogArticleForm, self).save(commit=False) + article.owner = self.user + article.app_config = NewsBlogConfig.objects.filter(pk=self.cleaned_data['app_config']).first() + article.save() + + # If 'content' field has value, create a TextPlugin with same and add it to the PlaceholderField + content = clean_html(self.cleaned_data.get('content', ''), False) + if content and permissions.has_plugin_permission(self.user, 'TextPlugin', 'add'): + add_plugin( + placeholder=article.content, + plugin_type='TextPlugin', + language=self.language_code, + body=content, + ) + + return article + +CreateNewsBlogArticleForm.__init__ = __init__ +CreateNewsBlogArticleForm.save = save diff --git a/gsoc/forms.py b/gsoc/forms.py index 5eac8774..0df51469 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,12 +1,16 @@ from .models import UserProfile, UserDetails -from django.forms import ModelForm, CheckboxSelectMultiple +from django.forms import ModelForm, CheckboxSelectMultiple, Select class UserProfileForm(ModelForm): class Meta: model = UserProfile - fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf') + fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + widgets = { + 'app_config': Select(), + } + class ProposalUploadForm(ModelForm): diff --git a/gsoc/models.py b/gsoc/models.py index d4fff184..9fddd194 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -13,6 +13,9 @@ from django.utils import timezone from django.shortcuts import reverse +from aldryn_apphooks_config.fields import AppHookConfigField +from aldryn_newsblog.cms_appconfig import NewsBlogConfig + import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher @@ -44,6 +47,8 @@ class UserProfile(models.Model): gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) accepted_proposal_pdf = models.FileField(blank=True, null=True) + app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True) + def has_proposal(self): try: @@ -199,7 +204,6 @@ def get_help_text(self): validate_proposal_text = ProposalTextValidator() - def gen_uuid_str(): return str(uuid.uuid4()) From b378dd161950402aba57de320ec700f0b8884ee6 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 31 Mar 2019 02:40:45 -0600 Subject: [PATCH 0018/1137] update permis --- project.db | Bin 1314816 -> 1314816 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 0a0458a3e395a697e283add05cfa0b5b5d6b4365..c31667588df55629bc7baa098d4e88219abb2260 100644 GIT binary patch delta 1950 zcmah~eQaCR6~Fhs5Bu4E_vE$BNK$BA2UIpePzpU^_@ zqPy)D`Zt=TXXV4Tm*nH}Gq(5Tt*}piT)u(2O97{cr**bni^_<{Ca#)sahq*3z!l0xu^~eaYe8 z#I2-UDdjfqhBhQikyLDADl?p`pIOcMq-Z)noQsU=Iej9X$mbK;4D4jriWuj{72jlD zpUCTlLLxJoCu>~XdTe~CSX)Zt9n)!KW>5C`sZ}NM69d57cR((#dKdiQe{eDkJP*webQt$3r zr8XK0`@J5uJ=7iyF8*it^-c#5#}HhC|H3IaRw0EOEj*7fA!xsas~P(&q-N~3;F|H= z!MFJZ20f18bNCECf#VGN7N}Mp6U>nORbwA_)!0ic%q0=@oDCtw!x;o$!UYCB!+`$= zqugxgjq?Zab&T{Bf-hhRK7~`v_b#|Zj$lSveeL6_uf0Uzh*TpB(Zhyi0c(`L209GW z%XEppK@Xb`E#L=emDnnT1P)^oG~ak0--fvmT{0{$-~>-Ur%Noxc534}35P0=;Oy5g zWAp2miDk95)$s~~fa4X@_5oRRP?>AS*H$HHzV#_-+aqE)kKp{G^1{ckT)`Acl4Px1 z7-1|ZN`>6_GH~FL#O$HY`)UPQN!C z@P&PTPn+8A>+l-AM{vFQn`gxxN4QqUykW^%28d&xsq#BO4)O>!U(0Xy$!!+%$!*r? z$1KQc1pkB6Y~K6OXeX5wL`cF&CN_NaLqhVwGGE8$mJwWrG81$XjP4V>#kh2Wm)ZEG zN`{x%_&KQOIkv$#4oMX>fu-t#GuLTo6gh=ehApom>6x0lRvYF%TT5{nVOZ%Z_GE?j zB$IX&Zn%z9G5hWE*^GNjqA-@7EVwg~w9aPBlDvq^-!va8OZ*5E^A2Jg>>Y+W0`67B ziev@Ls?6g41l*1*ehFmecRp2i?kL)J6zw~TjzdM6FEg?q>l17G4wmh8v-Ua4ZZPe0 zb(jBP<6sKGd+;nwvC!kdyH^Ux*p^jp;oVI&tC{+%9U>f?h{OwP8jZbKWle+3$|P8? zAewai%l^3ScIy=~L6c~#q>O~j?wqpAx*Z`rq&<2^XGU$0Cw;kxL(x>SZ9Fp>+majm z?QNopz%bV^Ha2n_YF4fJ$H1Krb_SL?r9?dgvtwXSGTOU6>#bgpe}s-u1^wsBo{qk8}J z(8l}Xk%7KMPh#zq@j*_>uTN;{b>rGVUqI^`O7(P&dV2=?{MtZwVlcUZjWPc8;FQ)i zsA}m!b*L-4b?BZxb#2CbN3Ed++`hnf4Ndj{n{f%bs$ z#1Y*1fUC0cU16uImmNjU@5x-PPFHqwQjZoY&hBhRcZS2xem!4suP;o-^i09$9E;?` d&MGdFi8*6>KB{M8kxU`)Vmb&hVzA;*-vaBIEgJv; delta 1068 zcmW-fZ%A8L7{>3(z4>#Kd(WGzwo2`4RzGx`Mw6JtDMIbsqA5f(Z4=EHiTPubc#~X= zv$480e$b4;W@n1`%b0PtAgpUU0=b`77%OYb5FG5|3Yq&dq)>-cuuJN$Jufc@UY_TD zejHA=;LaA@HyY8z*Qfi?#E*enO(vuEMJr>}y#haA_#K*YA70a`@vk_AciEpX$v$Oo zLnoVo@7Y!M4OWx48rl@P+eI!gqg630L}Mo{Za=I!QGD^v8KkzJT%gDfTKxI3vNmsQ zoj~#Y&DLqEHcT4xjPYZ#9=GOsqo{1|C0(?*@P*P~faT*N>f1F}(y5StDsqL7B`5v< z6dyi!mK;IiU!0~nKiMXovep3ELQll_$do_C$An;Nd@5Z3K52Wssjld5F9lTl1PFLv z#@oTnBC+r`F{faUuYCDiPu{(j<4gu~SGU>JZML-8%~rd`RQzY{w4q3qt58JSWMP9U zn@B5uBB~3>rG*Ii@gd&FPo!W0eT4IdfL`2d(oxdZUDSzWVO3P$M=?3@OPs4dKvMOA zLj9(yh1nv&Dluh6kvaBAThA1o8*)lzXtk_XQ3nJZ6uJA)KstC%;;aTig@6kCRAK~b z7gT4{vFV6ks$8cu*<(|CsjRL*u5w-}RnDt4y%MEY{s+?0vTC%U#`LbiZ11+2yKUAs zo84lyc8Kmr=!EpyrfTUC*}|-gYC-Ll3HzAb`n+0NpVx%`E>glCFj}M~jt@-}Y^hhH z(o=^79LiwNfl^T%Eme;wT2H+s;N|O~noyD)9Z8n96-Y0kZABGGmS&5neAHChqp3iV z3j7N?4Z(6Wuxuo0_^xbN%4(Ogx}_{r%Cgj+L>;?wD2wF=31#(US*w6eLjHZ5kdZMH zfIb&LO>r$6;#|Fw;AoeaO>vEMx9~}jkA;jU#kmw`Y+z+>O4~!=IP-^YRog=q6jL}( zMDtw0E)5FYO^qm|xgV})oUyC1P{%MIa&%3OjSfehaiP;0zYrTrrek9rgR{PbS@87_ zr+uU0Xei+^1ufHjFy^#-XPxHAKy*52$_#}~!xz20%Qoii;)5Q?jO)?|ncx6#zdYbb z_$++JSg^R0Xy&u+=4~uhqr*nyZASJN7moK;+?zPLvi~mSEr=UkQb0RKgXTQ cZQSGJu8}tG)sDMW2*Lz}Ww{T;Fw}kXKfJ(Ni~s-t From 9b32b7cd9aa6fe8d18817ca9a2d09f37bb955754 Mon Sep 17 00:00:00 2001 From: Arinze ugwuanyi Date: Mon, 1 Apr 2019 00:46:03 +0100 Subject: [PATCH 0019/1137] initial commit --- docs/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/README.md b/docs/README.md index c5eb1c39..4eb4d9cc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,3 +32,9 @@ To see diff's on the database you will need to run the following command :- git config --local include.path ../.gitconfig ``` Also make sure sqlite3 is available. + +## Usage +'''' python +pip install pipenv +it allow you to have a virtual machine on your local machine, to seprate this project package from the package you have on your local machine. +'''' \ No newline at end of file From 420b16e25ec38a0fff077ce377e2dce304166560 Mon Sep 17 00:00:00 2001 From: karna98 Date: Sun, 31 Mar 2019 17:15:01 +0530 Subject: [PATCH 0020/1137] UI inconsistencies : 1) Login UI Improved 2) Student 'Upload proposal' message UI fixed --- gsoc/templates/base.html | 85 +++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index ebf71717..93a1df92 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -9,7 +9,10 @@ Python GSoC – Splash @@ -158,4 +179,4 @@ {% block content %}{% endblock %} {% render_block 'js' %} - \ No newline at end of file + From 4d197896f24fa8bb9bf49ab9fe30955ba0790f21 Mon Sep 17 00:00:00 2001 From: Vedant Wakalkar Date: Tue, 2 Apr 2019 17:56:29 +0530 Subject: [PATCH 0021/1137] Fixed the Typo and added link for env setup. --- docs/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 21d2c9d7..4ee897d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,8 @@ git config --local include.path ../.gitconfig Also make sure sqlite3 is available. ## Usage -'''' python +``` python pip install pipenv -it allow you to have a virtual machine on your local machine, to seprate this project package from the package you have on your local machine. -'''' \ No newline at end of file +``` +It is recommended to set up virtual environment on your local machine, to seprate this project package from the package you have on your local machine. +More setup information can be found [here](https://docs.python.org/3/tutorial/venv.html) From 852295e92a2d7a355dbece7b341ad2d2bdcb4507 Mon Sep 17 00:00:00 2001 From: Vedant Wakalkar Date: Tue, 2 Apr 2019 22:55:30 +0530 Subject: [PATCH 0022/1137] Fixed Typo and Added virtual environment list. --- docs/README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 4ee897d0..dab11ce8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,8 +34,16 @@ git config --local include.path ../.gitconfig Also make sure sqlite3 is available. ## Usage -``` python -pip install pipenv -``` + It is recommended to set up virtual environment on your local machine, to seprate this project package from the package you have on your local machine. -More setup information can be found [here](https://docs.python.org/3/tutorial/venv.html) + +#### For virtualenv +[Installation](https://virtualenv.pypa.io/en/latest/userguide/) [Setup Guide](https://virtualenv.pypa.io/en/latest/userguide/) +#### For virtualenvwrapper +[Installation](https://virtualenvwrapper.readthedocs.io/en/latest/install.html) [Setup Guide](https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html) +#### For pyenv +[Installation](https://github.com/pyenv/pyenv#installation) [Setup Guide](https://github.com/pyenv/pyenv#command-reference) +#### For pipenv +[Installation](https://pipenv.readthedocs.io/en/latest/#install-pipenv-today) [Setup Guide](https://pipenv.readthedocs.io/en/latest/#pipenv-usage) +#### For anaconda +[Installation](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) [Setup Guide](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) From 951bf5fd359f89a74bb9928527e0a8b8133d9950 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Wed, 3 Apr 2019 10:49:40 +0800 Subject: [PATCH 0023/1137] Add file size check and frontend file check --- gsoc/templates/myprofile.html | 31 ++++++++++++++++++++++++++----- gsoc/views.py | 4 +++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index 91624ad0..4e84aba1 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -64,7 +64,7 @@

Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!

{% csrf_token %} - +
- Lost password? - + + {% if not user.is_authenticated %} +
+ + + + +
+
{% endif %} +
+ {% if user and user.is_current_year_student and not user.has_proposal %} +
+ You haven't uploaded your proposal yet. Please click here to upload! +
+ {% endif %} + {% if request.current_page %} + {% if request.user.is_authenticated %} + {% if not request.current_page.get_title_obj.published or request.current_page.get_title_obj.publisher_state %} +
+ Alert! The page has not been published yet. +
+ {% endif %} + {% endif %} + {% endif %} +
+ + {% block content %}{% endblock %} - - - {% if user and user.is_current_year_student and not user.has_proposal %} -
- You haven't uploaded your proposal yet. Please click - - here - - to upload! -
-
- {% endif %} - - {% block content %}{% endblock %} {% render_block 'js' %} diff --git a/gsoc/templates/gettingstarted.html b/gsoc/templates/gettingstarted.html index 19dd68f0..ea7527c8 100644 --- a/gsoc/templates/gettingstarted.html +++ b/gsoc/templates/gettingstarted.html @@ -7,9 +7,9 @@ {% block content %}
-

How do I get started?

+

How do I get started?

Choose an organization. diff --git a/gsoc/templates/schedule.html b/gsoc/templates/schedule.html index e4a612f1..3a1abd43 100644 --- a/gsoc/templates/schedule.html +++ b/gsoc/templates/schedule.html @@ -10,9 +10,13 @@

Schedule

-

Next Deadline - Students can start signing up March 12th. -

+
+
+

Next Deadline + Students can start signing up March 12th.

+
+
+
Dates diff --git a/project.db b/project.db index c31667588df55629bc7baa098d4e88219abb2260..523b8f1f9e2b99df08ad0a062a9b6c2ae37cbbe2 100644 GIT binary patch delta 4675 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 WtFh9qL_8fUVx>3NT-?}c%l-#!3!>5h From c7c2e48a3d4f62229cb1d31444b1d44e31375691 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 4 Apr 2019 09:27:37 +0530 Subject: [PATCH 0029/1137] Fix responsiveness of topbar and side-nav --- gsoc/static/css/python-gsoc.css | 2 +- gsoc/static/css/side-menu.css | 16 ++----- gsoc/static/js/menu.js | 54 +++++++++++++++++----- gsoc/templates/base.html | 80 ++++++++++++++++++--------------- 4 files changed, 92 insertions(+), 60 deletions(-) diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index 05f2883c..93b84e06 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -166,7 +166,7 @@ div.problem { display: none; } -@media screen and (max-width: 600px) { +@media screen and (max-width: 48em) { .large-screen { display: none; } diff --git a/gsoc/static/css/side-menu.css b/gsoc/static/css/side-menu.css index 04ddab45..09cb78d8 100644 --- a/gsoc/static/css/side-menu.css +++ b/gsoc/static/css/side-menu.css @@ -51,7 +51,7 @@ The content `
` is where all your content goes. margin: 0; color: #333; text-align: center; - padding: 2em 2em; + padding: 2.5em 2em 0; border-bottom: 1px solid #eee; } .header h1 { @@ -67,7 +67,7 @@ The content `
` is where all your content goes. } .content-subhead { - margin: 30px 0; + margin: 50px 0 20px 0; font-weight: 300; color: #888; } @@ -94,11 +94,6 @@ appears on the left side of the page. /* All anchors inside the menu should be styled like this. */ - - .opened { - margin-left: 0 !important; - } - #menu a { color: #999; border: none; @@ -134,7 +129,7 @@ appears on the left side of the page. */ #menu .pure-menu-selected, #menu .pure-menu-heading { - background: #16536e; + background: #1f8dd6; } /* This styles a link within a selected menu item `
  • `. @@ -166,7 +161,7 @@ small screens. .menu-link { position: fixed; display: block; /* show this only on small screens */ - top: 0; + top: 46px; left: 0; /* "#menu width" */ background: #000; background: rgba(0,0,0,0.7); @@ -206,9 +201,6 @@ small screens. margin-top: 0.6em; } -.sub-menu { - margin-left: 20px; -} /* -- Responsive Styles (Media Queries) ------------------------------------- */ diff --git a/gsoc/static/js/menu.js b/gsoc/static/js/menu.js index 4429a387..7ace2710 100644 --- a/gsoc/static/js/menu.js +++ b/gsoc/static/js/menu.js @@ -1,12 +1,46 @@ -function toggleMenu() { - var menu = document.getElementById("menu"); - var classes = menu.className.split(" "); - if (classes.indexOf("opened") == -1) { - classes.push("opened"); - menu.className = classes.join(" "); +(function (window, document) { + + var layout = document.getElementById('layout'), + menu = document.getElementById('menu'), + menuLink = document.getElementById('menuLink'), + content = document.getElementById('main'); + + function toggleClass(element, className) { + var classes = element.className.split(/\s+/), + length = classes.length, + i = 0; + + for(; i < length; i++) { + if (classes[i] === className) { + classes.splice(i, 1); + break; + } + } + // The className is not found + if (length === classes.length) { + classes.push(className); + } + + element.className = classes.join(' '); } - else { - classes.splice(classes.indexOf("opened"), 1) - menu.className = classes.join(" "); + + function toggleAll(e) { + var active = 'active'; + + e.preventDefault(); + toggleClass(layout, active); + toggleClass(menu, active); + toggleClass(menuLink, active); } -} \ No newline at end of file + + menuLink.onclick = function (e) { + toggleAll(e); + }; + + content.onclick = function(e) { + if (menu.className.indexOf('active') !== -1) { + toggleAll(e); + } + }; + +}(this, this.document)); \ No newline at end of file diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index e87ce5c2..0d725c6f 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -16,7 +16,6 @@ - {% block head %}{% endblock %} {% render_block "css" %} @@ -25,9 +24,14 @@ {% include 'cms_toolbar.html'%} {% endif %} + + + + +
    -
  • Remind your students that your sub-org name must be in the title of their applications! -
  • Here's a link to the student application +
  • Here's a link to the student application information for Python diff --git a/gsoc/templates/students.html b/gsoc/templates/students.html index e0e18539..40c8ac44 100644 --- a/gsoc/templates/students.html +++ b/gsoc/templates/students.html @@ -69,7 +69,7 @@

    Students

    -
    +

    How to apply

    Short application checklist:

      From f95a4a35d7a888bc5e659f797924a5e87e41bbf4 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Wed, 10 Apr 2019 08:37:24 +0800 Subject: [PATCH 0053/1137] remove unused variables --- gsoc/common/utils/commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 1fcb90fc..58829d3c 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -31,9 +31,8 @@ def send_email(scheduler: Scheduler): content = data['template'].render(context_dict) if isinstance(data['send_to'], str): data['send_to'] = [data['send_to']] - recipients_count = len(data['send_to']) try: - sent_count = send_mail( + send_mail( message=content, subject=settings.EMAIL_SUBJECT_PREFIX + data['subject'], from_email=settings.SERVER_EMAIL, From f6b6e742572bc3b86b19c3ee84813d5e07abae6c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 10 Apr 2019 14:56:52 +0530 Subject: [PATCH 0054/1137] Add codeblocks in README --- docs/README.md | 54 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/docs/README.md b/docs/README.md index cf45a8b8..d3f31b93 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,11 +4,12 @@ Blog and management platform for PSF for running GSoC ## Installation -Requires python 3.6+ -To install development dependncies:- +- Requires python 3.6+ -```bash -pip install -r requirements.txt +To install development dependncies: + +``` +$ pip install -r requirements.txt ``` ## Usage @@ -17,55 +18,66 @@ python manage.py runserver 0.0.0.0:8000 ``` You can then access the site with the login bar with http://127.0.0.1:8000/en/?edit -``` + Default user/pass is `admin` for the superuser Default student user is `Test-Student1` pass `^vM7d5*wK2R77V` -``` + ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. ## Git -To see diff's on the database you will need to run the following command :- -```bash -git config --local include.path ../.gitconfig +To see diff's on the database you will need to run the following command: +``` +$ git config --local include.path ../.gitconfig ``` Also make sure sqlite3 is available. -## Usage -A virtual environment is a tool that helps to keep dependencies required by different projects separate by creating isolated python virtual environments for them. -This means that each project can have its own dependencies, regardless of what dependencies every other project has. -We use a module named virtualenv which is a tool to create isolated Python environments. -virtualenv creates a folder which contains all the necessary executables to use the packages that a Python project would need. +## Virtualenv + +A virtual environment is a tool that helps to keep dependencies required by different projects separate by creating isolated python virtual environments for them. This means that each project can have its own dependencies, regardless of what dependencies every other project has. We use a module named `virtualenv` which is a tool to create isolated Python environments. `virtualenv` creates a folder which contains all the necessary executables to use the packages that a Python project would need. -Installing virtualenv: +### Installing virtualenv +```bash $ pip install virtualenv +``` -Test your installation: +### Test your installation +```bash $ virtualenv --version +``` -Using virtualenv +### Using virtualenv You can create a virtualenv using the following command: -$ virtualenv my_name +```bash +$ virtualenv virtualenv_name +``` After running this command, a directory named my_name will be created. This is the directory which contains all the necessary executables to use the packages that a Python project would need. This is where Python packages will be installed. -Now after creating virtual environment, you need to activate it. Remember to activate the relevant virtual environment every time you work on the project. This can be done using the following command:- +Now after creating virtual environment, you need to activate it. Remember to activate the relevant virtual environment every time you work on the project. This can be done using the following command: + +``` $ source virtualenv_name/bin/activate +``` Once the virtual environment is activated, the name of your virtual environment will appear on left side of terminal. This will let you know that the virtual environment is currently active. Now you can install dependencies related to the project in this virtual environment. For example if you are using Django 1.9 for a project, you can install it like you install other packages. -(virtualenv_name)$ pip install Django==1.9 +``` +(virtualenv_name) $ pip install Django==1.9 +``` Once you are done with the work, you can deactivate the virtual environment by the following command: -(virtualenv_name)$ deactivate +``` +(virtualenv_name) $ deactivate +``` Now you will be back to system’s default Python installation. From 65469ab1e6e2cf4b8f2dc70f41725c8c75d1c9c3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 11 Apr 2019 17:03:17 +0530 Subject: [PATCH 0055/1137] Add hidden field to UserProfile model to hide users --- gsoc/admin.py | 63 ++++++++++++++++++++++++++++++++++++-------------- gsoc/forms.py | 18 ++++++++++----- gsoc/models.py | 31 +++++++++++++++++++++---- 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index d171323b..eb794ddb 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -11,7 +11,6 @@ from aldryn_people.models import Person from aldryn_newsblog.admin import ArticleAdmin from aldryn_newsblog.models import Article -from aldryn_newsblog.cms_appconfig import NewsBlogConfig class UserProfileInline(admin.TabularInline): @@ -31,6 +30,7 @@ class UserAdmin(DjangoUserAdmin): admin.site.unregister(User) admin.site.register(User, UserAdmin) + def article_get_form(): """ Makes some admin-only fields readonly or hidden for students. @@ -38,6 +38,7 @@ def article_get_form(): ori_get_form = ArticleAdmin.get_form ori_fieldsets = None ori_readonly_fields = None + def return_func(self, request, obj=None, **kwargs): nonlocal ori_readonly_fields, ori_fieldsets is_request_by_student = request.user.student_profile() is not None @@ -58,15 +59,14 @@ def return_func(self, request, obj=None, **kwargs): 'is_featured', 'featured_image', 'lead_in', - - )}), + )}), # (_('Meta Options'), # {'classes': ('collapse',), # 'fields':()}), (_('Advanced Settings'), {'classes': ('collapse',), 'fields': ('app_config',)}), - ) + ) self.readonly_fields = ( 'author', 'publishing_date', @@ -77,9 +77,11 @@ def return_func(self, request, obj=None, **kwargs): 'meta_description', 'meta_keywords', 'owner', - ) + ) return form return return_func + + def Article_change_view(self, request, object_id, *args, **kwargs): is_student_request = request.user.student_profile() is not None data = request.GET.copy() @@ -104,6 +106,8 @@ def Article_change_view(self, request, object_id, *args, **kwargs): request.GET = data request.POST = post_data return super(ArticleAdmin, self).change_view(request, object_id, *args, **kwargs) + + def Article_add_view(self, request, *args, **kwargs): is_student_request = request.user.student_profile() is not None data = request.GET.copy() @@ -139,6 +143,7 @@ def Article_add_view(self, request, *args, **kwargs): request.POST = post_data return super(ArticleAdmin, self).add_view(request, *args, **kwargs) + def Article_save_model(self, request, obj, form, change): # checks whether user has add permission in the current # section before adding to the blog @@ -158,6 +163,7 @@ def Article_save_model(self, request, obj, form, change): else: raise PermissionDenied() + def Article_delete_model(self, request, obj): # checks whether user has delete permission in the current # section before adding to the blog @@ -177,6 +183,7 @@ def Article_delete_model(self, request, obj): else: raise PermissionDenied() + def Article_get_queryset(self, request): user = request.user qs = Article.objects.all() @@ -192,6 +199,7 @@ def Article_get_queryset(self, request): print(qs) return qs + ArticleAdmin.save_model = Article_save_model ArticleAdmin.delete_model = Article_delete_model ArticleAdmin.get_queryset = Article_get_queryset @@ -207,31 +215,32 @@ class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('url',)}), ("Configure user to be registered", - {'fields': ( - "user_role", - "user_suborg", - "user_gsoc_year", - )}), - ) + {'fields': ( + "user_role", + "user_suborg", + "user_gsoc_year", + )}), + ) readonly_fields = ( 'url', - ) + ) list_display = ('reglink_id', 'url', 'is_used', 'created_at') list_filter = [ 'is_used', 'created_at', - ] + ] def get_readonly_fields(self, request, obj=None): if obj and obj.is_used: return self.readonly_fields + ( - "user_role", - "user_suborg", - "user_gsoc_year", - ) + "user_role", + "user_suborg", + "user_gsoc_year", + ) else: return self.readonly_fields + admin.site.register(RegLink, RegLinkAdmin) @@ -242,3 +251,23 @@ class SchedulerAdmin(admin.ModelAdmin): admin.site.register(Scheduler, SchedulerAdmin) + + +class HiddenUserProfileAdmin(admin.ModelAdmin): + list_display = ('user', 'gsoc_year', 'suborg_full_name', 'hidden') + list_filter = ('hidden', ) + readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + fieldsets = ( + ('Unhide', { + 'fields': ('hidden', ) + }), + ('User Profile Details', { + 'fields': ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + }) + ) + + def get_queryset(self, request): + return UserProfile.all_objects.all() + + +admin.site.register(UserProfile, HiddenUserProfileAdmin) diff --git a/gsoc/forms.py b/gsoc/forms.py index 0df51469..c533647a 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,16 +1,22 @@ -from .models import UserProfile, UserDetails +from .models import UserDetails, UserProfile -from django.forms import ModelForm, CheckboxSelectMultiple, Select +from django.forms import ModelForm, Select class UserProfileForm(ModelForm): class Meta: model = UserProfile - fields = ('role', 'suborg_full_name', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + fields = ( + 'role', + 'suborg_full_name', + 'gsoc_year', + 'accepted_proposal_pdf', + 'app_config', + 'hidden' + ) widgets = { 'app_config': Select(), - } - + } class ProposalUploadForm(ModelForm): @@ -22,4 +28,4 @@ class Meta: class UserDetailsForm(ModelForm): class Meta: model = UserDetails - fields = ('deactivation_date',) \ No newline at end of file + fields = ('deactivation_date',) diff --git a/gsoc/models.py b/gsoc/models.py index 9fddd194..e43de5d3 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -14,6 +14,7 @@ from django.shortcuts import reverse from aldryn_apphooks_config.fields import AppHookConfigField + from aldryn_newsblog.cms_appconfig import NewsBlogConfig import phonenumbers @@ -34,6 +35,11 @@ def __str__(self): return str(self.gsoc_year) +class UserProfileManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(hidden=False) + + class UserProfile(models.Model): ROLES = ( (0, 'Others'), @@ -48,15 +54,21 @@ class UserProfile(models.Model): suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) accepted_proposal_pdf = models.FileField(blank=True, null=True) app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True) + hidden = models.BooleanField(name='hidden', default=False) + + objects = UserProfileManager() + all_objects = models.Manager() def has_proposal(self): try: self.userprofile_set.get(role=3).accepted_proposal_pdf.path return True + except: return False + def is_current_year_student(self): try: profile = self.userprofile_set.get(role=3) @@ -66,12 +78,14 @@ def is_current_year_student(self): except UserProfile.DoesNotExist: return False + def student_profile(self): try: return self.userprofile_set.get(role=3) except UserProfile.DoesNotExist: return None + auth.models.User.add_to_class('has_proposal', has_proposal) auth.models.User.add_to_class('is_current_year_student', is_current_year_student) auth.models.User.add_to_class('student_profile', student_profile) @@ -90,6 +104,7 @@ def auto_delete_proposal_on_delete(sender, instance, **kwargs): if os.path.isfile(filepath): os.remove(filepath) + @receiver(models.signals.pre_save, sender=UserProfile) def auto_delete_proposal_on_change(sender, instance, **kwargs): """ @@ -120,7 +135,7 @@ class Meta: def save(self, *args, **kwargs): if self.deactivation_date: s = Scheduler(command='deactivate_user', data=self.user.pk, - activation_date=self.deactivation_date) + activation_date=self.deactivation_date) s.save() super(UserDetails, self).save(*args, **kwargs) @@ -131,7 +146,7 @@ class Scheduler(models.Model): ('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user'), - ) + ) id = models.AutoField(primary_key=True) command = models.CharField(name='command', max_length=20, choices=commands) @@ -163,6 +178,7 @@ def find_all_emails(self, text): except: pass return real_emails + def find_all_possible_phone_numbers(self, text): """ Returns all possible phone numbers in a list. @@ -182,8 +198,10 @@ def find_all_possible_phone_numbers(self, text): except: pass return all_number_strings + def find_all_locations(self, text): return [] + def validate(self, text): emails = self.find_all_emails(text) possible_phone_numbers = self.find_all_possible_phone_numbers(text) @@ -195,8 +213,10 @@ def validate(self, text): "locations": locations, } raise ValidationError(message=message) + def __call__(self, text): self.validate(text) + def get_help_text(self): return _("The text in a proposal should not contain any private data.") @@ -216,15 +236,18 @@ class RegLink(models.Model): choices=UserProfile.ROLES, default=0, null=True, blank=False, ) user_suborg = models.ForeignKey(SubOrg, name="user_suborg", - on_delete=models.CASCADE, null=True, blank=False) + on_delete=models.CASCADE, null=True, blank=False) user_gsoc_year = models.ForeignKey(GsocYear, name="user_gsoc_year", - on_delete=models.CASCADE, null=True, blank=False) + on_delete=models.CASCADE, null=True, blank=False) + @property def url(self): return f'{reverse("register")}?reglink_id={self.reglink_id}' + def is_usable(self): timenow = timezone.now() return (not self.is_used) and self.created_at < timenow + def create_user(self, *args, is_staff=True, **kwargs): user = User.objects.create(*args, is_staff=is_staff, **kwargs) UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, suborg_full_name=self.user_suborg) From aa085de8138020c095ab682bbacf9f584e48a9b4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 13 Apr 2019 10:01:04 +0530 Subject: [PATCH 0056/1137] Add page specific notifications --- gsoc/admin.py | 30 +++++++++++++++++++++++++++++- gsoc/models.py | 28 ++++++++++++++++++++++++++++ gsoc/templates/base.html | 13 +++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index eb794ddb..a7d94b43 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,4 +1,4 @@ -from .models import UserProfile, RegLink, UserDetails, Scheduler +from .models import UserProfile, RegLink, UserDetails, Scheduler, PageNotification from .forms import UserProfileForm, UserDetailsForm from django.contrib.auth.models import User @@ -12,6 +12,8 @@ from aldryn_newsblog.admin import ArticleAdmin from aldryn_newsblog.models import Article +from cms.models import Page, PagePermission + class UserProfileInline(admin.TabularInline): model = UserProfile @@ -271,3 +273,29 @@ def get_queryset(self, request): admin.site.register(UserProfile, HiddenUserProfileAdmin) + + +class PageNotificationAdmin(admin.ModelAdmin): + list_display = ('message', 'user', 'page') + list_filter = ('user', 'page') + + def get_fieldsets(self, request, obj=None): + if request.user.is_superuser: + return ( + (None, { + 'fields': ('user', 'page', 'message') + }), ) + else: + return ((None, {'fields': ('page', 'message')}), ) + + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if db_field.name == "page": + kwargs['queryset'] = Page.objects.filter(publisher_is_draft=True) + if not request.user.is_superuser: + pp = PagePermission.objects.filter(user=request.user) + pages = [_.page.pk for _ in pp] + kwargs['queryset'] = kwargs['queryset'].filter(pk__in=pages) + return super().formfield_for_foreignkey(db_field, request, **kwargs) + + +admin.site.register(PageNotification, PageNotificationAdmin) diff --git a/gsoc/models.py b/gsoc/models.py index f0ee2301..6c107a3b 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -17,6 +17,8 @@ from aldryn_newsblog.cms_appconfig import NewsBlogConfig +from cms.models import Page, PagePermission + import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher @@ -160,6 +162,32 @@ def __str__(self): return self.command +class PageNotification(models.Model): + message = models.TextField(name='message') + user = models.ForeignKey(User, name='user', + on_delete=models.CASCADE) + page = models.ForeignKey(Page, name='page', related_name='notifications', + on_delete=models.CASCADE) + pubished_page = models.ForeignKey(Page, name='published_page', + related_name='notifications_for_published', + on_delete=models.CASCADE) + + def save(self, *args, **kwargs): + if self.page and self.page.publisher_is_draft: + page = self.page + published_page = Page.objects.filter(node_id=page.node_id, + publisher_is_draft=False).first() + self.published_page = published_page + perm = PagePermission.objects.filter(page=page).filter(user=self.user).first() + + if self.user.is_superuser or (perm and perm.can_change): + super().save(*args, **kwargs) + else: + raise ValidationError(message='User does not have permissions on this page') + else: + raise ValidationError(message='Add notification on unpublished page') + + class ProposalTextValidator: def find_all_emails(self, text): """ diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index cb692b32..146e32ba 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -82,6 +82,19 @@ No such user found! Please verify your credentials and enter again.
    {% endif %} + {% if request.current_page.publisher_is_draft %} + {% for notification in request.current_page.notifications.all %} +
    + {{ notification.message }} +
    + {% endfor %} + {% else %} + {% for notification in request.current_page.notifications_for_published.all %} +
    + {{ notification.message }} +
    + {% endfor %} + {% endif %}
    {% block content %}{% endblock %} From b00685ac87a5958256056912fc01a2386295713a Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 13 Apr 2019 16:04:32 +0800 Subject: [PATCH 0057/1137] wip --- gsoc/cms_toolbars.py | 18 +++++++++ gsoc/templates/add_students.html | 67 +++++++++++++++++++++++++++++++ gsoc/urls.py | 6 +++ gsoc/views.py | 25 +++++++++++- project.db | Bin 1314816 -> 1318912 bytes 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 gsoc/templates/add_students.html diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index e0ea95ad..8b5a5a5b 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -10,6 +10,7 @@ from cms.utils.conf import get_cms_setting from cms.utils.urlutils import admin_reverse +from django.shortcuts import reverse from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language_from_request @@ -20,6 +21,11 @@ from aldryn_newsblog.models import Article from aldryn_newsblog.cms_toolbars import NewsBlogToolbar + +from cms.toolbar_base import CMSToolbar +from cms.toolbar_pool import toolbar_pool + + def add_admin_menu(self): if not self._admin_menu: self._admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, self.current_site.name) @@ -162,3 +168,15 @@ def populate(self): on_close=redirect_url) NewsBlogToolbar.populate = populate + +@toolbar_pool.register +class AddUser(CMSToolbar): + def populate(self): + menu = self.toolbar.get_or_create_menu( + key='add_students_or_mentors', + verbose_name='Add Users' + ) + menu.add_modal_item( + name='Add Students', + url=reverse('toolbar-add-students') + ) diff --git a/gsoc/templates/add_students.html b/gsoc/templates/add_students.html new file mode 100644 index 00000000..3d2163b2 --- /dev/null +++ b/gsoc/templates/add_students.html @@ -0,0 +1,67 @@ + + + Add Students + + +{% if message %} +

    {{ message }}

    +{% endif %} +
    + + + {% csrf_token %} + +
    + + + + diff --git a/gsoc/urls.py b/gsoc/urls.py index 95cc9074..0fb29d89 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -52,3 +52,9 @@ url('upload-proposal/', gsoc.views.upload_proposal_view, name='upload-proposal'), url('cancel_proposal_upload/', gsoc.views.cancel_proposal_upload_view, name='cancel-proposal-upload'), ] + + +# Toolbar add users +urlpatterns += [ + url('toolbar_add_students/', gsoc.views.toolbar_add_students, name='toolbar-add-students'), +] diff --git a/gsoc/views.py b/gsoc/views.py index 961a384f..db3e8e22 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -4,9 +4,9 @@ from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User from .forms import ProposalUploadForm -from .models import validate_proposal_text, RegLink, UserProfile +from .models import validate_proposal_text, RegLink, SubOrg from django import shortcuts -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponseForbidden from django.core.validators import validate_email from django.core.exceptions import ValidationError @@ -168,3 +168,24 @@ def register_view(request): else: context['done_registeration'] = False return shortcuts.render(request, 'registration/register.html', context) + + +def is_admin(user): + return True + + +@decorators.login_required +def toolbar_add_students(request): + if not is_admin(request.user): + return HttpResponseForbidden() + suborgs = SubOrg.objects.all() + suborg_info = {} + for suborg in suborgs: + suborg_info[suborg.suborg_name] = suborg.pk + context = dict() + context['suborgs'] = suborg_info + context.update({'message': 'Students successfully created'}) + if request.method == 'GET': + return shortcuts.render(request, 'add_students.html', context) + if request.method == 'POST': + return shortcuts.render(request, 'add_students.html', context) diff --git a/project.db b/project.db index e15995dd3dc04a69e33a096730fc8ed3caf7a21c..52355ba6a822ec01eced0d1057f6743734e433ba 100644 GIT binary patch delta 809 zcmajdTSyd97zglkW@eq)nQbmRUdYNOZ|l0wI#f{4KNQkJE=u9qw# z+%oK8G2Mj@QK1=84}r!?(_@71X_ROLDZOk_7(&oPL`R~h9(wqFaQNW-58wF)CmIJ| zG>&eP<^h2FFd5(i0JC$f%5R$oUM=*r?Z;M;{m#bNX?C1*aSyo>ZX_c`7VVk+m30u7 zr^h_`uq^%9a~Do!YO7j_bbe$LBisbV9`XQ0-N!c+Wj=Yo#+H z<63rnI6Dq80wXaBvtctz^q?0v4Zk_uy24;~jKw(2fo;HeOt4y4#Cy9l z()6m3RwB|2q!f#4WPS?IA`&I$+sP2%I~;GgTP&c5aw@4g<|P@z&oZornDm9HxF&P9 z`s-#m8Vy8a{`kd!f1vBq>3ECY6Yp#})pjm!1Uh0Z0~f5+73l=bHB{gB^{M#HkQwj> zbv>}%XU+9W2A6B+X8`|8C=o0n#m@-ug-(9S5;gG%k2*%AgJq>;J^Wt^XDy9L9*MN` zpDlVq`pB0Qkzx3EI{!v<>un_VONGO(DG760t||GQvW21n3nu}$pS{o0b`DN5{q$>k zV@{lMQ7*g?Auoo9tw=H3_6PX`>dKHb$FR?Lk|t(2CR^JropDE!Ll;SW9m< zSg403HVt+!N+P8o_*3hG<57wSA(x1vjd*DBQvWc5f`?F?C_3=m-f#HkWu~7EO&5pe z0;~i8>;?Z1-j{3dw3k5C|6*n2SJ16y$xo6dk4QmnRhQHyixt-ttFP@igpK)J#0z)i z??!IHht|>dq|M>a1f(v-EI;>b!}JDdiAV#`96#x5vOY$?raiHPq1fK;c)UB2h<0|y zVzK?#HVdKa0oKrEa1i-=>SZc@;V;Ruk?JLO>SU85s07)PQfK;0_DjK z_w7T(RezBi0Q$pX)lbw(pbQ{{3b0A^aTSH!kk$aLK@oveaEaJD#s6Zwc8X!Q7o#%J z#+=>f`V5hjDnOCwb!X!);E*IGc4bih8nClIgb1pImXVI;<_)r^E`wc9aPlVRQ10zNpR&7(v>Cv$=g(alIWskSIhD*LjFx)rDQq@NQN8$NT}L4co&HW${I~3S+rI%4 C=Da-s From fa2d72cab4b84aa0cb1fd3f4a69d7794ae3641de Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 13 Apr 2019 17:25:24 +0800 Subject: [PATCH 0058/1137] finish add student --- gsoc/templates/add_students.html | 7 +++- gsoc/views.py | 62 +++++++++++++++++++++++++++++-- project.db | Bin 1318912 -> 1318912 bytes 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/gsoc/templates/add_students.html b/gsoc/templates/add_students.html index 3d2163b2..fc8d3717 100644 --- a/gsoc/templates/add_students.html +++ b/gsoc/templates/add_students.html @@ -3,6 +3,9 @@ Add Students +{% if create_message %} +

    {{ create_message|safe }}

    +{% endif %} {% if message %}

    {{ message }}

    {% endif %} @@ -27,7 +30,7 @@

    {{ message }}

    Sub-org

    GSoC Year
    -
    +
    Name

    @@ -41,7 +44,7 @@

    {{ message }}

    const stu_form = document.querySelector('#stu_form'); const inputs = document.querySelectorAll('input'); for (let index in inputs){ - if (inputs[index].value.replace(/\s+/, '').length <= 0) { + if (inputs[index]===undefined || inputs[index] === '') { alert("Please fill all fields!"); return } diff --git a/gsoc/views.py b/gsoc/views.py index db3e8e22..65a02807 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,10 +1,10 @@ import io - +from django.utils import timezone from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User from .forms import ProposalUploadForm -from .models import validate_proposal_text, RegLink, SubOrg +from .models import validate_proposal_text, RegLink, SubOrg, UserProfile, GsocYear from django import shortcuts from django.http import JsonResponse, HttpResponseForbidden from django.core.validators import validate_email @@ -171,7 +171,7 @@ def register_view(request): def is_admin(user): - return True + return user.is_superuser @decorators.login_required @@ -184,8 +184,62 @@ def toolbar_add_students(request): suborg_info[suborg.suborg_name] = suborg.pk context = dict() context['suborgs'] = suborg_info - context.update({'message': 'Students successfully created'}) + context['year'] = str(timezone.now().year) + context['create_message'] = '' + context.update({'message': ''}) if request.method == 'GET': return shortcuts.render(request, 'add_students.html', context) if request.method == 'POST': + data = request.POST + current_user = 1 + while True: + c = str(current_user) + username = data.get('username' + c, '') + password = data.get('password' + c, '') + suborg_pk = data.get('suborg' + c, '') + firstname = data.get('firstname' + c, '') + lastname = data.get('lastname' + c, '') + email = data.get('email' + c, '') + if not username: + break + try: + validators.UnicodeUsernameValidator()(firstname) + validators.UnicodeUsernameValidator()(lastname) + validators.UnicodeUsernameValidator()(username) + so = SubOrg.objects.filter(pk=suborg_pk).first() + if not so: + raise ValidationError + validate_email(email) + except ValidationError: + context.update({'message': 'Student' + c + "'s data is not complete. Please check again."}) + return shortcuts.render(request, 'add_students.html', context) + try: + password_validation.validate_password(password) + except ValidationError as e: + context.update({'message': 'Student' + c + "'s password is too simple!"}) + context['message'] += '
    '.join(e.messages) + return shortcuts.render(request, 'add_students.html', context) + username_used = User.objects.filter(username=username).first() is not None + if username_used: + context.update({'message': 'Student' + c + "'s username has been used."}) + return shortcuts.render(request, 'add_students.html', context) + email_used = User.objects.filter(email=email).first() is not None + if email_used: + context.update({'message': 'Student' + c + "'s email has been used."}) + return shortcuts.render(request, 'add_students.html', context) + user = User.objects.create(username=username, email=email, + first_name=firstname, last_name=lastname, + is_staff=True + ) + user.set_password(password) + user.save() + role_dict = {k: v for v, k in UserProfile.ROLES} + so = SubOrg.objects.filter(pk=suborg_pk).first() + gsocyear = GsocYear.objects.filter(gsoc_year=timezone.now().year).first() + UserProfile.objects.create(user=user, role=role_dict.get('Student', 0), + suborg_full_name=so, + gsoc_year=gsocyear) + current_user += 1 + context['create_message'] += f'
    Student {username} is created!
    ' + return shortcuts.render(request, 'add_students.html', context) diff --git a/project.db b/project.db index 52355ba6a822ec01eced0d1057f6743734e433ba..7abac005819e8ca1ebf5a599a32b085909f8a5fa 100644 GIT binary patch delta 1972 zcmai!ZA@Eb6vyxFb9;OHb|1!u&-HXS_JtTczJ)@pk)V*_EIoo>BB~!n@ID4=h zuhg8wDzPWl;>Y#3agwTZ0G68j_L6qX?57R44rohpTXS`w0S}7tk)vtRpG+jvRnS7|v#7Z6>px;VdQ- z6WBOZE(*El6a}ot-4;q|-?fevHikln$5TT`dLuox9X)1mj2{fM4!>jEVIS(qGQs-b zfXAJv@9FOLnPPUUVKUJY$tI2zuwrqEEuBHjP^1$C#251M0?!NaPq>aNFZ#?buXCcu=p3o_`a12M4s$#?=oDF_V`3y`U?+!yz3z}0cR3oTj45lA%bRvO zBjQH+gg7t8Bq_8QX*17n(on``vg#QtXSHlpOXPDEgcegvQ?`4{Bc3i(k{dOR4m*w6 zU~PC<7;OzTCi)`ovB8nnSg5LTa#JOnpoCqnf+~k=T-_H$KWma5Bvchhws9#;YaWd^rsEL6ExV17c==dssc0? z+{Z^;Q3(vCphts6>cGB@;EjgLwP1H)M=B|y0^2u^0V%ZKFQys}r_zEymNLjF@Qjid zUVU)k^n(j{{=o&AqO4p2Z(wj0-iP<#UHB{f3En`EU%|QM&+(d5OR`f-;KL&d$@;E z$N`QkNJ&Y#q;f)10$qUPK$c(qrc5c(uV9a{ufi2X|0SIJ>!yM*Lj~f%NlE0A5Ryo2 zxQqa}5~|RnSOJ$YxQ4oP5B?5s!ew|1CAhMlKwiDh0M;3Zbq3iwgACZ7O^Xqr;l-)NiRU#YyrYu|$0VZ1~#j#b#sPozN}d`(Jrp zBX^yY+f9Cd%90uNXA{P3CKHS^k*VNBw#gTr3MN9)NGAW;%Q^xtH(I1;ZDGPpMvKK{ z$lFBSRn0aPeFme?&<|k^%)@i^CAb2k^fK&3zrjt<_&-&xqQgGWu7$Kc=a z7kC|Bg`dC$IFAy%4tcFYiQ#BzF^U2ZXh_M$Bv%1|MqctrKxl}PCkHYOD0u{ssbt8b RFeN$%G`RE)5{!}o_#ej;H`)LI delta 781 zcmX}qOGq0*7zgm(&BI+2ce2__snSZ@K&wRdk=?ipMa5Q|LSmYlY~rPPn>E=OjgJ^8 zwbYVZY7I2E_EK-6EeY~XS~HbQr79Jw6XiSIF3h}IEqAw~?QKu3IvX*On~FY~%f)B1GgApA z9j|`qIYf_?1HDnELR~m`fn4j+Inl~=pyf*Su(uvqUi?=C!lz` zgF!m&bVpBw&TBtG@vPqf8KEW!hX|+{GC^jj1tKA!o%LJrr9W9P!$SPcHRyFH0k#GR zy=6^Lc{IHTKLcAKN^fZ=bP_^Nn!lS~H6u;S#%}#L{F6?CW7;)$mfAp{jQd44Ec-d< zWY*!#xasl0pfajB7o5t166L(<=&+ay2E57h^TfmdH8m{f)6qqz5a;NeQ}x?p_wFYH ztT!1OxvPw3X2nE+zRl8%XrnnB%XTpiiKZon?iDzZ?-Q1LS`4mYd@$1p@aeZ^Hsr`)QKaTO*OgDhOKH|>@mFr%`|9+ZRjV8NAl zvo3m6_OY;s4USdV$CQK1co$=`58lo_8)d|WsOtYWsT!2?g>g7dfE{LHBgsT8;7&)= zVpfxK)EDywf~+CrFK``bV3n+aeV~$inuWG_$;xDjvRc7DV(oLx^Ql6Ljfd4ZpP5c6 zs#1vA!*h;^V|LP!$WvOaM0u!71bKvjAK){13!Z}!a1#(9K^~Fo@RO{NmGuqk)742yhGxIUXBr1R%k6AZBS6FcC^uUIc!A1F#?XO)Vl*fno$BlQMe&ErUSnLw~ztq From d53a06482c93b6c3e0b3266f2b1d41d2a07802ac Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 13 Apr 2019 17:26:07 +0800 Subject: [PATCH 0059/1137] restore db --- project.db | Bin 1318912 -> 1314816 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 7abac005819e8ca1ebf5a599a32b085909f8a5fa..e15995dd3dc04a69e33a096730fc8ed3caf7a21c 100644 GIT binary patch delta 1106 zcmX|=YiL_#7{|{^PLgv;&YROy+e+t>uB)||#FKM!X#&!fUD^zi&bl^98MJ9G&1o)8 zZ)qBkEm(;{sB6P^zPXOMPu6x?-4R5P>Ib31IuvbXNGAkzez1bF!D8{v=Jvk)zC7>$ zeQrF@rLWFiT0M8Uk=aHNv9aDKT{P;DdThHpO(KQid4CGsCKP2M7}QLWTX>gFah zx=WN!wR;rWl~SqwU-U?6xudp@%^z2qi1C@p(8%0OWI8=P85I+e(ia`?qU)QdJ4aBp zqVA+pz@@vN>e$kkrn2_Z;QA0Fxr5ndrm0f5^((L0?C1&)GLg+pYAs{4# zf@mQ+2rY;1gX_OMbq!tWiT}f>FeOTW+b0N2{~5+q45Y4Fr5-`R-2S7`r8U#gA z0#PWzCkL2OkAeyUDkzko0HT8h>;wFnOs0ewgdY!qCKEak=IELL8%RR23(+TsIt)Y z1r?TWE;dVRQzms-*3|;iQanUbXLQdGpR*={x(QzDqj|_Yeeahh{{eAPD)X%ZC~HJ;X|RzY+e|9=+=C zs*LJ&)sot*yQ;pRzHr?zv8pV09tz0RUoDppJwy+bFSpeLD z+NSG&w(jKWNwoq28QrSxsP^kxrRLM>t7-)V`kP8$Y9_TvsTCpj?xvMW-`?`srG{s_ zgF&j>HD9EPGuf8ZDCZUij&-~FxvXDk?-}b(q@w*)-qYn63{s=QfG5Djn1Pfl*Yyl1 zgf~7R&(<%23&VGwd^#)~otd#t@+mt-dn}a0LNg}HgiD8s zDwb`Jn^gLCZ(t?t@%X8k-0124WN*u{Ue`#P8%{7Ck&c-T-{`R-6>EzP^#wC+y*)i4 zXWGZobD8#JF>|_#mF6Y3d;vc67F{?D`Qjli%yD5po_{hS?X`QXP8YS4^)Oo#OKplO zAKh%L@tyMciqn~VU@$$?(UKVP1(Kdo*W76#-rbexo17hT75syI@>M{| z>*}7*^w-kHheMno0ou3LfykW#7lL1LKK!wGtkuJK>^m6e*0jftpuamV>tcP`PW=r5o4Y(FjOI5bV_=x9(GwYdhPL28P#aK&XEIid2 zw`B&B!Rg`2&UCz|oes0n-u`$blb%hbi<8mRP;Mj}VFFd2CTmJo*Kdh_X2Ac6Y zTvo=(Fs!@$r=?x>BshbByWpST9dHJ`27V5{13nKgZWKJL!}g+E(Lx@w0kc(ykC!>o zh|x8tN5!QuVCGHd%|n%Iu|3r{Qd)5pwtaINCbfT#We_$#;#*1%mD;Ejy{@}><2Y=Z&cV32Jv z$Y2a5DyuyBw;nfLH@toyluZ+c8z^q7;72vLfOzM=wyE;bxIv+RN`*c^bP3I`)L&2` z#5u(we1-fm=0zXCRN5=SjNux3@VTco^1wN{&l!p2+=Zz~F=H0&e+qJ?ZK6bUD@ z4mObq73M07XAEO#y~84ffH%b{wGR(Xvu=B3C1-G`pHk^ABD#yZk3ci1fJNO6a0{e# zYv4G{Nl+&yE*SRvTd)B7Nu(Od>62*Gm9-0oHUnJqEd-YF8{jM81#k=;1WFLq{YCc{ zT=SAnyn4;h%2tX0d()aG%!h6j+W&7zE&VO(XAL{fo0kpH>MG`C<2$C^IDx3pR}pom z>J=5CRHLtwv&1in?TRdJ#?5febEx_px6ndtvsh#=3F4uf#sg@3rT?aJhivy@)4r*A z7~ai&rV!6hn}WTArr_z5CtKxRi0QI#7yjIig;w!1Z`QSneK(Eu;uqhnGndb*w~2v8 zy-WP|qP|WPEk=`gV#Q)?khf~yM=fyGRunHaFB}v<_iN*8>XLK8Ol*r;WF<~4 ztr{Ceo7MOwcnc?P8?WFc!@_=%w;A)5`>RG+(g=d-VE_%bcnSX+e#dC~Ca#m|^-Bo& z5c~nGf}er!foot1y1WcHEulnE_-GN5!~mvINJB*VhD*G38q*Wk609Fu9V+6c#F VDj6J+h!W1jG^q3s6oiyv;6FML!D9db From c5dc2ea7e3683d60045015d484cb30fb2cc7d781 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 13 Apr 2019 17:31:53 +0800 Subject: [PATCH 0060/1137] mark message as safe --- gsoc/templates/add_students.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/add_students.html b/gsoc/templates/add_students.html index fc8d3717..7ed359a7 100644 --- a/gsoc/templates/add_students.html +++ b/gsoc/templates/add_students.html @@ -7,7 +7,7 @@

    {{ create_message|safe }}

    {% endif %} {% if message %} -

    {{ message }}

    +

    {{ message| safe}}

    {% endif %}
    From 759effa63d118bb8141fb896ee844e0f5386acd8 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 13 Apr 2019 17:33:24 +0800 Subject: [PATCH 0061/1137] pep8 fix --- gsoc/cms_toolbars.py | 2 ++ gsoc/views.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 8b5a5a5b..c29e25ba 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -167,8 +167,10 @@ def populate(self): menu.add_modal_item(_('Delete this article'), url=url, on_close=redirect_url) + NewsBlogToolbar.populate = populate + @toolbar_pool.register class AddUser(CMSToolbar): def populate(self): diff --git a/gsoc/views.py b/gsoc/views.py index 65a02807..f2dcd957 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -211,7 +211,9 @@ def toolbar_add_students(request): raise ValidationError validate_email(email) except ValidationError: - context.update({'message': 'Student' + c + "'s data is not complete. Please check again."}) + context.update({'message': + 'Student' + c + + "'s data is not complete. Please check again."}) return shortcuts.render(request, 'add_students.html', context) try: password_validation.validate_password(password) From 0660f2892a2aff150d010e250ba1b1c447c26631 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 13 Apr 2019 17:46:13 +0800 Subject: [PATCH 0062/1137] fix bug --- gsoc/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gsoc/views.py b/gsoc/views.py index f2dcd957..cf989a6c 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -190,6 +190,7 @@ def toolbar_add_students(request): if request.method == 'GET': return shortcuts.render(request, 'add_students.html', context) if request.method == 'POST': + created = [] data = request.POST current_user = 1 while True: @@ -216,6 +217,8 @@ def toolbar_add_students(request): "'s data is not complete. Please check again."}) return shortcuts.render(request, 'add_students.html', context) try: + if not password: + raise ValidationError("Empty password") password_validation.validate_password(password) except ValidationError as e: context.update({'message': 'Student' + c + "'s password is too simple!"}) @@ -242,6 +245,9 @@ def toolbar_add_students(request): suborg_full_name=so, gsoc_year=gsocyear) current_user += 1 + created.append(user) context['create_message'] += f'
    Student {username} is created!
    ' + if not created: + context['message'] = 'No user created' return shortcuts.render(request, 'add_students.html', context) From 960232b44edaf826bbfb484ffa0f3461f4d2b1d3 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Tue, 16 Apr 2019 14:13:03 +0800 Subject: [PATCH 0063/1137] add user button: switch to send mail --- gsoc/settings.py | 4 +++ gsoc/templates/add_students.html | 7 ---- gsoc/views.py | 56 +++++++++++++------------------- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 5cb92135..abca38d0 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -55,6 +55,10 @@ EMAIL_PORT = 587 EMAIL_HOST_USER = "realemail@realdomain.tld" EMAIL_HOST_PASSWORD = "supersecretpassword" +if DEBUG: + INETLOCATION = 'http://localhost:8000' +else: + INETLOCATION = 'https://python-gsoc.org' # Application definition ROOT_URLCONF = 'gsoc.urls' diff --git a/gsoc/templates/add_students.html b/gsoc/templates/add_students.html index 7ed359a7..99cfac54 100644 --- a/gsoc/templates/add_students.html +++ b/gsoc/templates/add_students.html @@ -23,17 +23,10 @@

    {{ message| safe}}

    const elements = `
    Adding students - User name
    -
    - Password
    -
    - Sub-org

    GSoC Year

    Name
    - -
    Email Address
    `; diff --git a/gsoc/views.py b/gsoc/views.py index cf989a6c..55f5657a 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,10 +1,11 @@ import io - +from django.conf import settings from django.utils import timezone from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User from .forms import ProposalUploadForm -from .models import validate_proposal_text, RegLink, SubOrg, UserProfile, GsocYear +from .models import validate_proposal_text, RegLink, SubOrg, UserProfile, GsocYear, Scheduler +from gsoc.common.utils.commands import build_send_mail_json from django import shortcuts from django.http import JsonResponse, HttpResponseForbidden from django.core.validators import validate_email @@ -195,18 +196,11 @@ def toolbar_add_students(request): current_user = 1 while True: c = str(current_user) - username = data.get('username' + c, '') - password = data.get('password' + c, '') suborg_pk = data.get('suborg' + c, '') - firstname = data.get('firstname' + c, '') - lastname = data.get('lastname' + c, '') email = data.get('email' + c, '') - if not username: + if not email: break try: - validators.UnicodeUsernameValidator()(firstname) - validators.UnicodeUsernameValidator()(lastname) - validators.UnicodeUsernameValidator()(username) so = SubOrg.objects.filter(pk=suborg_pk).first() if not so: raise ValidationError @@ -216,37 +210,33 @@ def toolbar_add_students(request): 'Student' + c + "'s data is not complete. Please check again."}) return shortcuts.render(request, 'add_students.html', context) - try: - if not password: - raise ValidationError("Empty password") - password_validation.validate_password(password) - except ValidationError as e: - context.update({'message': 'Student' + c + "'s password is too simple!"}) - context['message'] += '
    '.join(e.messages) - return shortcuts.render(request, 'add_students.html', context) - username_used = User.objects.filter(username=username).first() is not None - if username_used: - context.update({'message': 'Student' + c + "'s username has been used."}) - return shortcuts.render(request, 'add_students.html', context) email_used = User.objects.filter(email=email).first() is not None if email_used: context.update({'message': 'Student' + c + "'s email has been used."}) return shortcuts.render(request, 'add_students.html', context) - user = User.objects.create(username=username, email=email, - first_name=firstname, last_name=lastname, - is_staff=True - ) - user.set_password(password) - user.save() role_dict = {k: v for v, k in UserProfile.ROLES} so = SubOrg.objects.filter(pk=suborg_pk).first() gsocyear = GsocYear.objects.filter(gsoc_year=timezone.now().year).first() - UserProfile.objects.create(user=user, role=role_dict.get('Student', 0), - suborg_full_name=so, - gsoc_year=gsocyear) + reg_link = RegLink.objects.create(user_role=role_dict.get('Student', ''), + user_suborg=so, + user_gsoc_year=gsocyear + ) + scheduler_data = build_send_mail_json(email, + template='invite.html', + subject='Your GSoC 2019 invite', + template_data={ + 'register_link': + settings.INETLOCATION + + reg_link.url + } + ) + Scheduler.objects.create(command='send_email', + activation_date=timezone.now(), + data=scheduler_data + ) current_user += 1 - created.append(user) - context['create_message'] += f'
    Student {username} is created!
    ' + created.append(email) + context['create_message'] += f'
    Student invite has been sent to {email}
    ' if not created: context['message'] = 'No user created' From ec65c9305e5e76bc3625f985526867bd5bb78462 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Tue, 16 Apr 2019 14:23:10 +0800 Subject: [PATCH 0064/1137] remove add users button --- gsoc/cms_toolbars.py | 16 ++++------------ gsoc/views.py | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index c29e25ba..58c1e8cd 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -53,7 +53,10 @@ def add_admin_menu(self): # cms users settings self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change')) self._admin_menu.add_break(USER_SETTINGS_BREAK) - + self._admin_menu.add_modal_item( + name='Add Students', + url=reverse('toolbar-add-students') + ) # clipboard if self.toolbar.edit_mode_active: # True if the clipboard exists and there's plugins in it. @@ -171,14 +174,3 @@ def populate(self): NewsBlogToolbar.populate = populate -@toolbar_pool.register -class AddUser(CMSToolbar): - def populate(self): - menu = self.toolbar.get_or_create_menu( - key='add_students_or_mentors', - verbose_name='Add Users' - ) - menu.add_modal_item( - name='Add Students', - url=reverse('toolbar-add-students') - ) diff --git a/gsoc/views.py b/gsoc/views.py index 55f5657a..3404ed6b 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -238,6 +238,6 @@ def toolbar_add_students(request): created.append(email) context['create_message'] += f'
    Student invite has been sent to {email}
    ' if not created: - context['message'] = 'No user created' + context['message'] = 'No email sent' return shortcuts.render(request, 'add_students.html', context) From 6e2051ffb97a003afe105ed601789c4b1628b8aa Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Thu, 18 Apr 2019 22:16:04 -0600 Subject: [PATCH 0065/1137] migrate db --- gsoc/migrations/0012_auto_20190418_2214.py | 23 +++++++++++++++++++++ project.db | Bin 1314816 -> 1318912 bytes 2 files changed, 23 insertions(+) create mode 100644 gsoc/migrations/0012_auto_20190418_2214.py diff --git a/gsoc/migrations/0012_auto_20190418_2214.py b/gsoc/migrations/0012_auto_20190418_2214.py new file mode 100644 index 00000000..ace12772 --- /dev/null +++ b/gsoc/migrations/0012_auto_20190418_2214.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.8 on 2019-04-18 22:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0011_userprofile_app_config'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='hidden', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='scheduler', + name='last_error', + field=models.TextField(blank=True, default=None, null=True), + ), + ] diff --git a/project.db b/project.db index e15995dd3dc04a69e33a096730fc8ed3caf7a21c..36214ba27e816fd178572cd5f737f82e2dc38d89 100644 GIT binary patch delta 2949 zcmai03ve6N72UTV?XINVx2tumIEn3zoZ$E)S$|n_XyPUeO^9p(&Xi9)vLh+}AWO!Q z^D!lfLW+TULSoJEnlj)FGifQ5CXK=CI0;>!I*&+Ul5bx$HEDHiSVv)Q2e#96+S6EBovEQcoK$;!^U@w zJ=vA&B9s0LeYgIwe#lTR7z_sudyEec%PmKEX30t_2vFt$M%9s(I_g${7DvD}!^z6U z090i6RTY9|*~hEyV3!wWXv)Q-lcyOaYpphe!`bVq80KbT)&$Bc~E@vnJm^$s?Z$TK~^%ICa40>ttZE*qz zbG@jiDIP4(-RkE}zZo^LV_@ zx=am2nCt5E3C3v0f1&N3??pSk4xjf285^Uuz0Wo$ABZVVRk70bJRsWWu_oBi)|tO{Bkc(>R$TUy4V6&$QI0a8*>~W};kQ^b%KyV4M1o=Hm`lrHrJPddk z@es7Bu(&Aa`**z~NwWX)4pF1+Di*;}SXPn;ThwKiHa4i1tGQ-QSvJPHXy(}(20!I8bvl@$Pv zoRxI=13ycC0U0`1#d#8W2FO<$2gf;$3&}mDU?I9IDcf`l7((WQXlGJh$OpQ6H^w_6 z)}owfG5S0yONBf&NQj-r*9{Np33j)xl0M5lM-67>7Pr>WEmu-Iz-__Z*Vg-LquWy3 zS`%Ap6ES;qV|Qyuk9}92feG$r>LZ=bK5t)l_r|E*)9LT-xib<;Cw<=4>n!rYHW&j23JQs!Omzr z*w^h_6Zf}AQTaC6gtqj_PZTHY*UoT?$7*+e%li(S1M`vJp`A_3ZbJbtK23<h8%MZy~`61KWQmJk&{$?Mggs=Hmc|G?dwp|>BC+J7O-yU zoU(>QhPRXsgKjObvSycyyk`sg<36bxDCov>ih}fm(khg+DnpvTLir8AI8cH%oH9kT zI}PTX;DFZgqNz&<%F!Dynaa>pFPM(?Kl8Fld*)?j9VpX92@#wjV6*tV@FMNj-@?Df z-@uK7Gh7whuX|ZgjU?udCxKyi>wuRvTdm}MDO7n*SxOT3YuJGWWO21>Y437IMCD>TtX5LhY5!lM0PLXyfYyJOnqx3UOK-5$_TWLels;&hMD9JTrf}o~W10GCv0V zY(dX;cTWJHH)?V3_qoxd7nGtMmR#Gj1XN21pY_!L9QG`@7Lpq~W07=D*pN)b)IdOO zilx%lo726xMA8nmy)_k3^SIVTRE@?`kys+ynn<29WK0n+6x8LUX zyGWN8b$y_epf5jA{H56DrjA%Vn!+}3x3qS5ha<_vrj9mCGgioun&BJe63`J6LFA7B TA3?zYxcspQcM%Fcwp02y%e!dd delta 1670 zcmYk63rt*98prRs_so6HJ?Gvzz@X61Fwn;U1D7%|bh0iqWs_DdOk#`}YnTedqa7Kf zg(az}jCRYSq%EB+$5#Zc#QNyNot0ZHtyGO}(paf&w$d(A_mO4S8Zm3DHGO!%UE@u@ zAm=MeOOEZeYK`fg9DYn#CbzW5V_l6ciFk9YYk9YDPwI=}rF0Jv z#ni=O%a!@_#{PekS`xTh$VMZncz`EQ&JHkZ` ztSPI?TUHsaC<|8vODZZ?1%u@@@-LFvzvF;t?>Hbme*R@({c#c)X)u4wvu`gHsz4K! zF3FFi%;=YP(--oKJJPUo9y?W_E>1;JIDNcE77cif!`IU0k*_fnp{dw6Zb8Mc#i)K2 zvt_Jfe2NE#!ZZM1H{4}Yg*FPh<^x0$BpsOzskJ>xXEVGw9RL7&?9I9QlNP)djPurwx?SXVUl+JL5RM{N78G#`s?2RyE+qrwoyO zz)9l=JYJ(fV?XR;gE!3U)x!~4wbh8(=>gevd%`Rc-44!FHqgF=nWKa|yB~_THwCg5 zUggnm6Q+?R!zm7JcAnFQRZiL|7Q!j{J>e$vRM4*LGz*2aH`)N~scmLZ%a+kJhi>A0 zY~y|SulQj!jn}crXMU(dbn|ZWPKeiW%a}ONp5AR1nSzWJTN*Y0jv#K~JVR@8=w2PF zG^@wV=wETzv{xK~6Np0vTJ`N|vQb!ML;7(KTr0Y3)=PV%* zHh?$mJN)DT(DbvgTyh5j{H_>H{DoxFug?&J2U#}wl)Mfce*G+eFRlB~)yzonmE;bE z_+5zw(gInOF-z&lMWWD0&uV3M%}G}S0Dk(z$F5^`+efYr5&Vsw|JaqE9czuZcZh>I z;^L7?`Uh{9+z*uUN&4U{+28-xX_x)hX>vEnk>i}|=;fUATCMt`Dk+-ik$dn)wBOOo zrnAujllg=Bv|xtRvg){$c Date: Thu, 18 Apr 2019 22:40:55 -0600 Subject: [PATCH 0066/1137] Signed-off-by: cyberempires@gmail.com --- gsoc/migrations/0013_pagenotification.py | 27 +++++++++++++++++++++++ project.db | Bin 1318912 -> 1318912 bytes 2 files changed, 27 insertions(+) create mode 100644 gsoc/migrations/0013_pagenotification.py diff --git a/gsoc/migrations/0013_pagenotification.py b/gsoc/migrations/0013_pagenotification.py new file mode 100644 index 00000000..5172564b --- /dev/null +++ b/gsoc/migrations/0013_pagenotification.py @@ -0,0 +1,27 @@ +# Generated by Django 2.1.8 on 2019-04-18 22:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('cms', '0022_auto_20180620_1551'), + ('gsoc', '0012_auto_20190418_2214'), + ] + + operations = [ + migrations.CreateModel( + name='PageNotification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.TextField()), + ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='cms.Page')), + ('published_page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications_for_published', to='cms.Page')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/project.db b/project.db index 36214ba27e816fd178572cd5f737f82e2dc38d89..7414521e574b1cc1b4e54153bf17b25c3569ad7a 100644 GIT binary patch delta 1749 zcma)5Z)_7~7{B-KdhfNjt?Qk$vm1C+yLL!zxBj{P0ilcvsFaHtI!(;hKezUht(*Hx zB-z$2VJOuMoA?p~j9^UoG859wQv*pCR1y<`5u!^pZXfu-$0mO9i-~V3L0k{VCBMA6 zd!FBOzvuTn?@U3SDaZ>AmU{#N_7jgDw&%<)AGmj=Pc*|89I~CJ^7H0vjo?Z?yz6xU zW`VI&8=SrEy8>iv$fJxABML2`5`J{(Od>7g;TUyVn{)SlPS5`r7-1X#*h`aGR&nHB?4CxuE% zyxtaH?`Hk2-j;T^*Y9p=6I)w5{N4_~?`5CAP4czQ-``*{j2I>i4xULcsVmB&T+9wh}amvPl5z2VSXkrh+RLwW$YxD)?hnfcJ5*X387x-?V`R@u|60z5L z6?y*W)CXv6s+j2 z02YKT>W>o(mo9W-900!Al02Y;#-t4Qzxu?i{XUA!nu z(-}v05464`k(scXPAJDqTV65P2OoOXL~k$<^ZA1@MK=qCy50?u~uEp(_sJ8i2m2v%~iKhUqQru1hVbWnsk$ysG8P(`$c$( jre|&0&eDx();zxpX8}zRWIL+5&MO+eRJV6rDvJLA8%$J= delta 753 zcmXAnUr19?9LMju_wJs%UAMb?PC41kmPvo7o0%+>K&&XWa*$9Eg<--pq;=J;gwgC z|CS3305Sm3u~<%?+Oog%D104|G~JkD67{SZTJBbZ;eN8PrL&Ucwr(H!`Iy_I`Nx$P zeh{6}AsY}Y*@iY>x9^^I1fZlI%*7S&tTrs6kuvTC93{|&d~~8xsExYOK*TEY3C{RW z|5a}gFTi%wtUS*r=;Aq1D@maiKvF~eAq{=Q0YF}+QKuv{f^ul7s$xe5$x_Vh0GPPe zaWTP9%{3%YjwiLmNAw1;poCohga*mh5UV3SIp48}?g-#8S^JF6lFWo+BU2N~{qW2( z(q@+NMQ~7PV@zO$k#CsN#zp?5;hKI`-z28Mig+A`g=M3?AQ#4KpjEG8AIm>Xd<9tUJ9jdry34}AK7!I-Mz$ZS~jy From ed1ae8e1b36520147cb0e1aa999cdc8ae52ff505 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 19 Apr 2019 14:56:03 +0530 Subject: [PATCH 0067/1137] Update db file --- project.db | Bin 1318912 -> 1318912 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 7414521e574b1cc1b4e54153bf17b25c3569ad7a..3b5c3c871ec467e29e28caa117e6ac379979d1e3 100644 GIT binary patch delta 7505 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 delta 4187 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 From 1cf47f26b44549920379e66be57095e2feeb7afe Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 19 Apr 2019 15:18:15 +0530 Subject: [PATCH 0068/1137] Update README to add another Student user/pass --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index d3f31b93..07e5d1d2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ You can then access the site with the login bar with http://127.0.0.1:8000/en/?e Default user/pass is `admin` for the superuser -Default student user is `Test-Student1` pass `^vM7d5*wK2R77V` +Default student users are `Test-Student1`, `Test-Student2` with pass `^vM7d5*wK2R77V` ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. From f48a43b5f84bae75f3bf4552080bbb9997388135 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Fri, 19 Apr 2019 21:10:56 +0800 Subject: [PATCH 0069/1137] finish add users button --- gsoc/admin.py | 27 +++++++++++-- gsoc/cms_toolbars.py | 4 +- gsoc/common/utils/commands.py | 18 +++------ gsoc/common/utils/tools.py | 12 ++++++ gsoc/forms.py | 7 +++- gsoc/models.py | 43 ++++++++++++++++++++ gsoc/urls.py | 6 --- gsoc/views.py | 72 ---------------------------------- project.db | Bin 1318912 -> 1347584 bytes 9 files changed, 91 insertions(+), 98 deletions(-) create mode 100644 gsoc/common/utils/tools.py diff --git a/gsoc/admin.py b/gsoc/admin.py index a7d94b43..d6668fff 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,5 +1,5 @@ -from .models import UserProfile, RegLink, UserDetails, Scheduler, PageNotification -from .forms import UserProfileForm, UserDetailsForm +from .models import UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog +from .forms import UserProfileForm, UserDetailsForm, RegLinkForm from django.contrib.auth.models import User from django.contrib import admin @@ -215,18 +215,23 @@ def Article_get_queryset(self, request): class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( - (None, {'fields': ('url',)}), + (None, {'fields': ('url', 'is_sent',)}), ("Configure user to be registered", {'fields': ( "user_role", "user_suborg", "user_gsoc_year", + "email", + 'adduserlog', )}), ) readonly_fields = ( 'url', + 'adduserlog', + 'is_sent', + 'adduserlog', ) - list_display = ('reglink_id', 'url', 'is_used', 'created_at') + list_display = ('reglink_id', 'url', 'is_used', 'is_sent', 'created_at') list_filter = [ 'is_used', 'created_at', @@ -238,6 +243,7 @@ def get_readonly_fields(self, request, obj=None): "user_role", "user_suborg", "user_gsoc_year", + 'email', ) else: return self.readonly_fields @@ -299,3 +305,16 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs): admin.site.register(PageNotification, PageNotificationAdmin) + + +class RegLinkInline(admin.TabularInline): + model = RegLink + form = RegLinkForm + + +class AddUserLogAdmin(admin.ModelAdmin): + readonly_fields = ('log_id', ) + inlines = (RegLinkInline, ) + + +admin.site.register(AddUserLog, AddUserLogAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 58c1e8cd..3e1cb20a 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -54,8 +54,8 @@ def add_admin_menu(self): self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change')) self._admin_menu.add_break(USER_SETTINGS_BREAK) self._admin_menu.add_modal_item( - name='Add Students', - url=reverse('toolbar-add-students') + name='Add Users', + url=admin_reverse('gsoc_adduserlog_add') ) # clipboard if self.toolbar.edit_mode_active: diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 58829d3c..207aa5b2 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -1,26 +1,18 @@ +import json from smtplib import SMTPResponseException, SMTPSenderRefused + from django.core.mail import send_mail from django.conf import settings + from django.template.loader import get_template from django.template import TemplateDoesNotExist -from django.template import Template, Context +from django.template import Template from gsoc.models import Scheduler -import json -from collections.abc import Sequence + from django.contrib.auth.models import User from .irc import send_message -def build_send_mail_json(send_to, - template: str, - subject: str, - template_data: dict = None): - if not isinstance(send_to, Sequence) and not isinstance(send_to, str): - raise TypeError('send_to must be a sequence of email addresses ' - 'or one email address as str!') - return json.dumps(locals()) - - def send_email(scheduler: Scheduler): data = json.loads(scheduler.data) try: diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py new file mode 100644 index 00000000..f6c61a20 --- /dev/null +++ b/gsoc/common/utils/tools.py @@ -0,0 +1,12 @@ +import json +from collections.abc import Sequence + + +def build_send_mail_json(send_to, + template: str, + subject: str, + template_data: dict = None): + if not isinstance(send_to, Sequence) and not isinstance(send_to, str): + raise TypeError('send_to must be a sequence of email addresses ' + 'or one email address as str!') + return json.dumps(locals()) diff --git a/gsoc/forms.py b/gsoc/forms.py index c533647a..e1f08edf 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,4 +1,4 @@ -from .models import UserDetails, UserProfile +from .models import UserDetails, UserProfile, RegLink from django.forms import ModelForm, Select @@ -29,3 +29,8 @@ class UserDetailsForm(ModelForm): class Meta: model = UserDetails fields = ('deactivation_date',) + +class RegLinkForm(ModelForm): + class Meta: + model = RegLink + fields = ('email', 'user_role', 'user_suborg', 'user_gsoc_year') diff --git a/gsoc/models.py b/gsoc/models.py index 6c107a3b..3c11fe54 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -12,6 +12,7 @@ from django.core.validators import validate_email from django.utils import timezone from django.shortcuts import reverse +from django.conf import settings from aldryn_apphooks_config.fields import AppHookConfigField @@ -22,6 +23,8 @@ import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher +from gsoc.common.utils.tools import build_send_mail_json + class SubOrg(models.Model): suborg_name = models.CharField(name='suborg_name', max_length=80) @@ -256,6 +259,11 @@ def gen_uuid_str(): return str(uuid.uuid4()) +class AddUserLog(models.Model): + log_id = models.CharField(max_length=36, + default=gen_uuid_str) + + class RegLink(models.Model): is_used = models.BooleanField(default=False, editable=False) reglink_id = models.CharField(max_length=36, default=gen_uuid_str, editable=False) @@ -267,10 +275,23 @@ class RegLink(models.Model): on_delete=models.CASCADE, null=True, blank=False) user_gsoc_year = models.ForeignKey(GsocYear, name="user_gsoc_year", on_delete=models.CASCADE, null=True, blank=False) + adduserlog = models.ForeignKey(AddUserLog, on_delete=models.CASCADE, null=True, blank=True, related_name='reglinks') + email = models.CharField(null=False, blank=False, default='', max_length=300, validators=[validate_email]) + scheduler = models.ForeignKey(Scheduler, null=True, blank=True, on_delete=models.CASCADE, editable=False) @property def url(self): return f'{reverse("register")}?reglink_id={self.reglink_id}' + @property + def is_sent(self): + return self.scheduler is not None and self.scheduler.success + def __str__(self): + sent = self.is_sent + if sent: + sent_str = 'Sent.' + else: + sent_str = 'Not sent.' + return f"Register Link {self.url} for {self.email}. {sent_str}" def is_usable(self): timenow = timezone.now() @@ -280,3 +301,25 @@ def create_user(self, *args, is_staff=True, **kwargs): user = User.objects.create(*args, is_staff=is_staff, **kwargs) UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, suborg_full_name=self.user_suborg) return user + + def create_scheduler(self, trigger_time=timezone.now()): + validate_email(self.email) + scheduler_data = build_send_mail_json(self.email, + template='invite.html', + subject='Your GSoC 2019 invite', + template_data={ + 'register_link': + settings.INETLOCATION + + self.url + } + ) + s = Scheduler.objects.create(command='send_email', + activation_date=trigger_time, + data=scheduler_data) + self.scheduler = s + self.save() + +@receiver(models.signals.post_save, sender=RegLink) +def create_send_reglink_schedulers(sender, instance, **kwargs): + if instance.adduserlog is not None and instance.scheduler is None: + instance.create_scheduler() diff --git a/gsoc/urls.py b/gsoc/urls.py index 0fb29d89..95cc9074 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -52,9 +52,3 @@ url('upload-proposal/', gsoc.views.upload_proposal_view, name='upload-proposal'), url('cancel_proposal_upload/', gsoc.views.cancel_proposal_upload_view, name='cancel-proposal-upload'), ] - - -# Toolbar add users -urlpatterns += [ - url('toolbar_add_students/', gsoc.views.toolbar_add_students, name='toolbar-add-students'), -] diff --git a/gsoc/views.py b/gsoc/views.py index 3404ed6b..aee3ba98 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -5,7 +5,6 @@ from django.contrib.auth.models import User from .forms import ProposalUploadForm from .models import validate_proposal_text, RegLink, SubOrg, UserProfile, GsocYear, Scheduler -from gsoc.common.utils.commands import build_send_mail_json from django import shortcuts from django.http import JsonResponse, HttpResponseForbidden from django.core.validators import validate_email @@ -170,74 +169,3 @@ def register_view(request): context['done_registeration'] = False return shortcuts.render(request, 'registration/register.html', context) - -def is_admin(user): - return user.is_superuser - - -@decorators.login_required -def toolbar_add_students(request): - if not is_admin(request.user): - return HttpResponseForbidden() - suborgs = SubOrg.objects.all() - suborg_info = {} - for suborg in suborgs: - suborg_info[suborg.suborg_name] = suborg.pk - context = dict() - context['suborgs'] = suborg_info - context['year'] = str(timezone.now().year) - context['create_message'] = '' - context.update({'message': ''}) - if request.method == 'GET': - return shortcuts.render(request, 'add_students.html', context) - if request.method == 'POST': - created = [] - data = request.POST - current_user = 1 - while True: - c = str(current_user) - suborg_pk = data.get('suborg' + c, '') - email = data.get('email' + c, '') - if not email: - break - try: - so = SubOrg.objects.filter(pk=suborg_pk).first() - if not so: - raise ValidationError - validate_email(email) - except ValidationError: - context.update({'message': - 'Student' + c + - "'s data is not complete. Please check again."}) - return shortcuts.render(request, 'add_students.html', context) - email_used = User.objects.filter(email=email).first() is not None - if email_used: - context.update({'message': 'Student' + c + "'s email has been used."}) - return shortcuts.render(request, 'add_students.html', context) - role_dict = {k: v for v, k in UserProfile.ROLES} - so = SubOrg.objects.filter(pk=suborg_pk).first() - gsocyear = GsocYear.objects.filter(gsoc_year=timezone.now().year).first() - reg_link = RegLink.objects.create(user_role=role_dict.get('Student', ''), - user_suborg=so, - user_gsoc_year=gsocyear - ) - scheduler_data = build_send_mail_json(email, - template='invite.html', - subject='Your GSoC 2019 invite', - template_data={ - 'register_link': - settings.INETLOCATION + - reg_link.url - } - ) - Scheduler.objects.create(command='send_email', - activation_date=timezone.now(), - data=scheduler_data - ) - current_user += 1 - created.append(email) - context['create_message'] += f'
    Student invite has been sent to {email}
    ' - if not created: - context['message'] = 'No email sent' - - return shortcuts.render(request, 'add_students.html', context) diff --git a/project.db b/project.db index 3b5c3c871ec467e29e28caa117e6ac379979d1e3..13d310aef54e25151d7d8c457d003a60a8fe1e83 100644 GIT binary patch delta 7763 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 KaN<07miu3XABcPa delta 1673 zcmaizdrTBp6o==|o%im}-I<{*0hJX&0bO18!Ma#m@Daguw@}dnrd1IH)(6WPd|oEA2p4siDGSyiDG-HiAmExdXw)abIv{Y z{O&nt8rxoJJY2XjNlB$BFphe*gp!sKZZ?$~ncCP=FqMl72XHDew=W`F$ry{&LV3hKmoC_+Jol#* zmO7;!rePM7te7^Kw;;dR7gNITh>E0OFuZggr5>0WuRNcM^3Ps?igVM z`(hJEqymtvha5veVXVb5iU#TWkx}DeAs^Iv=h|4SvxC#^;rLk4cR(Q4fX?UcBr@B% zSYXoUf^eIp0fW))m|S=PCHg@esSO3}Rt6-~!xq(4E}qdZntE^lOx7vN(ed}8`WxFz8S<4)3hmP69n1NJz(PecDE;iVX$i9`=GQE6IkWr@r^}J$ zvuF8oyxts-e}5a}gy}+gjPEI;$(<4xE5ESoIGiFQ%(sD$h-sDu_a>VdL)#1P0pcjf zeCI!oE{jqACD;WdfF6;=AQwG~DZO{m>b}!}KocN=A+Q9Fz!L<5NRS97f|+0;$OL5c zowo8}gURI?D~%o-iH+7@QA;6{C;^@V0EiG$qNP6@>Y-2%T8fqui3<=|O`bgk_tYw$ z1PR=v^EvaY{gFlC;Dq43Kxo?Bmy0Jhl!i(@ z!G?k*Ws{1Q1SZUO2SbzRmKGN^loeNp0niL%rXDgJn zcBePTo1Nozjr91OF27GTPVH6vc@WcC2H&eVf$l3Bh>PzjCHkJCl}6U@${EWW-PA_l zrlc;ZO=^-Fq$;UM8Y2yp6!D>WRXibfiQB|gVx9Q9m@m4-K|-H!QTSBQg?Yj_A%XAZ zPxHI^)qEwN$0u_4x$n3x4s#9M49?35>?O97UBkY{x>%99#eB`QF)Nsv%!|w*_!qnb ze}dn@E|~N_jKVoE0LQ?gP@$jDcj+tiDf%GYPB(4Zp!$*`bP1hHXVMCI2(E*Zpc8Bc zD?k+p0QaVAYL2OL#-f^v%G!lwc#Hy|3ZjmS*7g4}6UkuYu>%<3qj6`p8gHbT)Jt)| zVt^h9tuUMGz`RqPPnzlBjE-!zbnRenGGzi0D1I+WLOx$De}=wbwsYNb65ItM)InLr zDTmc!{PS5Y3AdcnGEJ7V@E6|ZaL{YU;}5TBX*9^gJI`wxzIH{67=ydj5zz2wXnO!- zIZG|Ww=QZKc%VZInxe@~19;o?G&)*y)H81|!T95C)4Q(eHgc~R>IL@DhTY*7o5iod$RCg97*)scADadr3q$juf0 d^ZJ Date: Fri, 19 Apr 2019 21:11:50 +0800 Subject: [PATCH 0070/1137] restore db --- project.db | Bin 1347584 -> 1318912 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 13d310aef54e25151d7d8c457d003a60a8fe1e83..3b5c3c871ec467e29e28caa117e6ac379979d1e3 100644 GIT binary patch delta 1673 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 KaN<07miu3XABcPa From d8bd9b914cdf96a8c6f44409804c8eb49c9a1fa9 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Fri, 19 Apr 2019 21:20:21 +0800 Subject: [PATCH 0071/1137] purge unused files and update --- gsoc/admin.py | 5 ++- gsoc/models.py | 5 +++ gsoc/templates/add_students.html | 63 -------------------------------- gsoc/views.py | 2 - 4 files changed, 8 insertions(+), 67 deletions(-) delete mode 100644 gsoc/templates/add_students.html diff --git a/gsoc/admin.py b/gsoc/admin.py index d6668fff..0e09f409 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -215,14 +215,14 @@ def Article_get_queryset(self, request): class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( - (None, {'fields': ('url', 'is_sent',)}), + (None, {'fields': ('url', 'is_sent', + 'adduserlog', 'has_scheduler')}), ("Configure user to be registered", {'fields': ( "user_role", "user_suborg", "user_gsoc_year", "email", - 'adduserlog', )}), ) readonly_fields = ( @@ -230,6 +230,7 @@ class RegLinkAdmin(admin.ModelAdmin): 'adduserlog', 'is_sent', 'adduserlog', + 'has_scheduler' ) list_display = ('reglink_id', 'url', 'is_used', 'is_sent', 'created_at') list_filter = [ diff --git a/gsoc/models.py b/gsoc/models.py index 3c11fe54..b55fb2bd 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -262,6 +262,8 @@ def gen_uuid_str(): class AddUserLog(models.Model): log_id = models.CharField(max_length=36, default=gen_uuid_str) + def __str__(self): + return self.log_id class RegLink(models.Model): @@ -279,6 +281,9 @@ class RegLink(models.Model): email = models.CharField(null=False, blank=False, default='', max_length=300, validators=[validate_email]) scheduler = models.ForeignKey(Scheduler, null=True, blank=True, on_delete=models.CASCADE, editable=False) + @property + def has_scheduler(self): + return self.scheduler is not None @property def url(self): return f'{reverse("register")}?reglink_id={self.reglink_id}' diff --git a/gsoc/templates/add_students.html b/gsoc/templates/add_students.html deleted file mode 100644 index 99cfac54..00000000 --- a/gsoc/templates/add_students.html +++ /dev/null @@ -1,63 +0,0 @@ - - - Add Students - - -{% if create_message %} -

    {{ create_message|safe }}

    -{% endif %} -{% if message %} -

    {{ message| safe}}

    -{% endif %} - - - - {% csrf_token %} - - - - - - diff --git a/gsoc/views.py b/gsoc/views.py index aee3ba98..dd7d4c51 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,6 +1,4 @@ import io -from django.conf import settings -from django.utils import timezone from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User from .forms import ProposalUploadForm From 16ad9dffa4cd56a6945a0adde25186f0893c0b9f Mon Sep 17 00:00:00 2001 From: ntkomata Date: Fri, 19 Apr 2019 21:23:26 +0800 Subject: [PATCH 0072/1137] prettify code --- gsoc/cms_toolbars.py | 2 -- gsoc/forms.py | 1 + gsoc/models.py | 14 +++++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 3e1cb20a..bfbbabed 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -172,5 +172,3 @@ def populate(self): NewsBlogToolbar.populate = populate - - diff --git a/gsoc/forms.py b/gsoc/forms.py index e1f08edf..a7c4e921 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -30,6 +30,7 @@ class Meta: model = UserDetails fields = ('deactivation_date',) + class RegLinkForm(ModelForm): class Meta: model = RegLink diff --git a/gsoc/models.py b/gsoc/models.py index b55fb2bd..93d52556 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -262,6 +262,7 @@ def gen_uuid_str(): class AddUserLog(models.Model): log_id = models.CharField(max_length=36, default=gen_uuid_str) + def __str__(self): return self.log_id @@ -277,16 +278,20 @@ class RegLink(models.Model): on_delete=models.CASCADE, null=True, blank=False) user_gsoc_year = models.ForeignKey(GsocYear, name="user_gsoc_year", on_delete=models.CASCADE, null=True, blank=False) - adduserlog = models.ForeignKey(AddUserLog, on_delete=models.CASCADE, null=True, blank=True, related_name='reglinks') - email = models.CharField(null=False, blank=False, default='', max_length=300, validators=[validate_email]) + adduserlog = models.ForeignKey(AddUserLog, on_delete=models.CASCADE, + null=True, blank=True, related_name='reglinks') + email = models.CharField(null=False, blank=False, + default='', max_length=300, validators=[validate_email]) scheduler = models.ForeignKey(Scheduler, null=True, blank=True, on_delete=models.CASCADE, editable=False) @property def has_scheduler(self): return self.scheduler is not None + @property def url(self): return f'{reverse("register")}?reglink_id={self.reglink_id}' + @property def is_sent(self): return self.scheduler is not None and self.scheduler.success @@ -315,15 +320,14 @@ def create_scheduler(self, trigger_time=timezone.now()): template_data={ 'register_link': settings.INETLOCATION + - self.url - } - ) + self.url}) s = Scheduler.objects.create(command='send_email', activation_date=trigger_time, data=scheduler_data) self.scheduler = s self.save() + @receiver(models.signals.post_save, sender=RegLink) def create_send_reglink_schedulers(sender, instance, **kwargs): if instance.adduserlog is not None and instance.scheduler is None: From 7051ee50eeb10c0bc2290e00f4709b22033266a0 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Fri, 19 Apr 2019 21:24:34 +0800 Subject: [PATCH 0073/1137] prettify code --- gsoc/models.py | 7 ++++--- gsoc/views.py | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 93d52556..3e659ad7 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -282,7 +282,8 @@ class RegLink(models.Model): null=True, blank=True, related_name='reglinks') email = models.CharField(null=False, blank=False, default='', max_length=300, validators=[validate_email]) - scheduler = models.ForeignKey(Scheduler, null=True, blank=True, on_delete=models.CASCADE, editable=False) + scheduler = models.ForeignKey(Scheduler, null=True, + blank=True, on_delete=models.CASCADE, editable=False) @property def has_scheduler(self): @@ -322,8 +323,8 @@ def create_scheduler(self, trigger_time=timezone.now()): settings.INETLOCATION + self.url}) s = Scheduler.objects.create(command='send_email', - activation_date=trigger_time, - data=scheduler_data) + activation_date=trigger_time, + data=scheduler_data) self.scheduler = s self.save() diff --git a/gsoc/views.py b/gsoc/views.py index dd7d4c51..1e4a13c9 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -166,4 +166,3 @@ def register_view(request): else: context['done_registeration'] = False return shortcuts.render(request, 'registration/register.html', context) - From e480690ca5abbd75232cd701cf1cccf1e6bfd0a0 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Fri, 19 Apr 2019 21:25:09 +0800 Subject: [PATCH 0074/1137] prettify code --- gsoc/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/models.py b/gsoc/models.py index 3e659ad7..25f26751 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -296,6 +296,7 @@ def url(self): @property def is_sent(self): return self.scheduler is not None and self.scheduler.success + def __str__(self): sent = self.is_sent if sent: From 391cc737af8b707278951129f31711d49cefb9be Mon Sep 17 00:00:00 2001 From: ntkomata Date: Fri, 19 Apr 2019 22:43:13 +0800 Subject: [PATCH 0075/1137] trying to fix permission --- gsoc/models.py | 25 +++++++++++++++++++++++-- project.db | Bin 1318912 -> 1343488 bytes 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 25f26751..12093b63 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -19,12 +19,15 @@ from aldryn_newsblog.cms_appconfig import NewsBlogConfig from cms.models import Page, PagePermission +from cms import api +from cms.utils.conf import get_cms_setting import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher from gsoc.common.utils.tools import build_send_mail_json +NewsBlogConfig.__str__ = lambda self: self.app_title class SubOrg(models.Model): suborg_name = models.CharField(name='suborg_name', max_length=80) @@ -58,7 +61,9 @@ class UserProfile(models.Model): gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) accepted_proposal_pdf = models.FileField(blank=True, null=True) - app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True) + app_config = AppHookConfigField(NewsBlogConfig, + verbose_name=_('Section'), + blank=True, null=True,) hidden = models.BooleanField(name='hidden', default=False) objects = UserProfileManager() @@ -310,8 +315,24 @@ def is_usable(self): return (not self.is_used) and self.created_at < timenow def create_user(self, *args, is_staff=True, **kwargs): + namespace = str(uuid.uuid4()) user = User.objects.create(*args, is_staff=is_staff, **kwargs) - UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, suborg_full_name=self.user_suborg) + blogname = f"{user.username}'s Blog" + app_config = NewsBlogConfig.objects.create(namespace=namespace) + app_config.app_title = blogname + app_config.save() + UserProfile.objects.create(user=user, role=self.user_role, + gsoc_year=self.user_gsoc_year, + suborg_full_name=self.user_suborg, + app_config=app_config) + page = api.create_page(f'{blogname} + ({namespace})', + get_cms_setting('TEMPLATES')[0][0], + 'en', published=True, + publication_date=timezone.now(),) + page.application_namespace = namespace + page.save() + admin = User.objects.filter(username='admin').first() + api.publish_page(page, admin, 'en') return user def create_scheduler(self, trigger_time=timezone.now()): diff --git a/project.db b/project.db index 3b5c3c871ec467e29e28caa117e6ac379979d1e3..7664c0c195a8fc89095286b057a8b8a1a9a55974 100644 GIT binary patch delta 8546 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 delta 3964 zcma)84OA3YmaeK-zg=B*tGf{x1!+)GQK0z~!JXhwvi>A65SdvuXGD-zL4*cGCuWSG zlaOTHw4~O&*<=;Q88)ZldcWlD&4P2*INP4mJRqFw;LvgYf#amb4XU-(U)y8FOw=!+j zKYPfRu8?b|t6d*$=%z=1*8KwHX}3B(p?}cqTVwxL7E)v~m#2vf`DD8;B}ve?+IC8% zm_SCU_BSbGc*Pz$ZtalgxM;`ipFj4#al@iwtrnGx;ukJ!EL+|X4A$0F2Hp0jE?Y2~(OroceER`PJZTRfpq*f;i5XQf# zN|GAcu&FWF&=_34ysCC_qcA#&%;xC@k2(S|)ud(e#BNU;p`i(vG>**VY5g2yxJ;5J zkq09?CfH_r6rXR8NT0;V+GXkGFvcAs@sW)~<%@%{_L1})Qmho&O5o|v=K?Rwwm%fz zS(Lk1MQNd}O#TICX{+2>IQp@ zJhmglPsXSfol0+sfFhUNS#fg9K{DF}s&w!#p{U%T{au>Ke6R5XJT6pFWoKkzx6xYh z7SAf)61MQF!!Czeou2+rfn7f$n3kI1p3gzHyNjCVh4Gni2{uCuRKj%Q7557Bexuf$ zZ#?DR=C+NChTAyk9x!ehqs<{kfthB^fg`XFoqwUT`B#O1AtV|erb@q%in8HAAHFTT zj-tmmD#Ve@f(S&q7RXu@ucnF>kPY@yQ(`@9Nd5wh_6i5rv8JBTIiOei$oJ4~6#|U7%>i0uiiiHgE9|hF65vG{RCi=w^Xe&0Uogmh`QOv{>(ywO&A|$&XVwNWF9LOTySQs;B$OLYe&GOg z!#>!BVZ8*KFmQi|7=34h9TAvokW>+v58f%+x-PNBLa={BmEOpL3iRx4USZSDiEld# zi!s}gg)E@GeY(t+-4N&Uue?Uy{ zLKnOaKZmW@d5=RqYRv{#-zC|8)Y{C!SI~!gXOJt0;SJaiJCVpvXEX!b+$Svx#6_Du zl6l6*J!x`&A}YTpIW#msG~a_O*b(o;GI$G0 z%`eSAu#v^`eGz4f%4Ia8Og=VPG3(Emf+hd|rImy4(Jxt@V*kzbz2{Z2 z8C5>u-~+gfD!&KzL7kRjp{$>Suc04D%}0Zzpf_&w#vV};EcqG-{{`1@>GB{ge1b$g zTCmpP@1HmxnNhUA&yqZMxh=p|9!JB~2o^mSweLLa>Jcs00kVyQZEy(JHv!#N=9=FZB8CG*x%@ataZD7M{C_~MMfPTEgHH@w9axBW^qm+#3u`+2xm|& z`s(x{&aPJ-Zacx8A4~)NJgj@M$p^hj1(VrIJ%wlGE$(=;ZMuhr!|tXK#(j2=VjKk-ec(&a0!jW0*X&Y) zkywn>IK#nz;%qpB2PD=yQtjUYaqP~lF-D^T9?FA9(lN~QNjS+8&l_DpPlSzhbajR) z8;st&zRt8qYz}j96fR?OzQM6H00V4jz`V$J`a{Pe2ew3ecNmu9@E#v_d5012jLrX} zOP{fx>ktvhCGI=t<4w4U!5;=e;@G7=Bhl}b&0QRvF?Yd>=5d&3UWZszc+sT0hMU#= zRNE7}kIhRkU$KxfCpc_|e*Pvi1LkI++w1hP>_mZ?9>7`;IBoA-6+VX6fi)(+zy{V> zzR7zkEH);S1Ho2d;OA#gQAz<$MOW2I9U-brWSi{f{TY<4tjA4KZ?L= zfp0}jvz4?2iSuZW(B?W%oRLxHTn_U?O%*j&3!e`}6|E7H3tN9UWo3C*D6f7=c6rs3 zrP)ovl?_dKjXzT?sjxNvo@>h*D{HHAmMzI^SP?3^zo@3L`bSHDQ2M~Cxz%&Ci&oA2 z$-L<$KPkL_VOCM~tfg~HOIFP*T~b|`Id93lznhsow{&Layy{Ty%$mFU(@kQdCF zP+nafs92eqo3$!8kei!b7n+!nl{+zSk(Y&^^*pI5N=$tY-_k_i?w5wrj%!jpJ@*WW zqAOqUG~u06tfQZuww`2*{>ighj&+$xt&8vu{0g3c=`aQ~SY&=`9!9Sl%mlc|-8Xo- z%u3D7@dtA9b0*~HWTs{31~T(l4_XK!5w&KPd9<&zbJ`(ox3)=pQmfbIYcsS=ZMb?%J*DnZ zZM8<7s>Uk)$`R#NrBw+kQ$5N)GiJyqS6+6Vo z#RX!4IGo%jpOFv9zmwf$*gumNvWOItNhFzggl~mD;f!!t*e7fi)^B{)n>(yQm?KOU zQUnkG6@P*6;dk=S^N;h3`9eNx<9Tns9xPi?RT->nboy#0?^QLMmOSHJ%^yIvO_Ab6 zK3AYSCwQaTDBXKHiZ>YFONd1#$S>m=>0O42%$C{96TEYuPLyYHI^RIF_cWI}Ls??} z8~jGxEFUn3ku7`!x6ky_q;791U47g)j0R5lQgq`OxunFTrV39@qb=RuQ35}Oww?3& z=)P`m14}&M9Zy)^Sl@PDG>&nP(N9kK#?tsyUy<07us)8~pYV0C=YQv|mUYBCLy1XG z7uHtMP2+rV6sw=8w?waxS7LrRN@)2$?N9N=&>wYs{de7epO`OmY{Mb1#M8n<-f4S# zz2bRIa3yo@2VKb;ZL_@TIDuL<`pz4^NqFfQ!;*jPJ1a5N`WBL2;7sl)rXdL9a>8JzVH<|SxzIvX$a@se* zi!I9f99sScOk^?d;IYO&I^{dSV?;up8q;E6NVMyu6iYum=uM+r4tihxD`8FMBbjO6 ad)_Bn#4pz4-O@J&%QRMG^ Date: Sat, 20 Apr 2019 00:32:25 +0800 Subject: [PATCH 0076/1137] fix permission issues --- gsoc/models.py | 13 ++++++++++++- gsoc/settings.py | 4 +++- project.db | Bin 1343488 -> 1318912 bytes 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 12093b63..fafcae19 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -3,6 +3,7 @@ import datetime import uuid +from django.contrib.auth.models import Group, Permission from django.contrib import auth from django.db import models from django.contrib.auth.models import User @@ -325,11 +326,21 @@ def create_user(self, *args, is_staff=True, **kwargs): gsoc_year=self.user_gsoc_year, suborg_full_name=self.user_suborg, app_config=app_config) - page = api.create_page(f'{blogname} + ({namespace})', + page = api.create_page(blogname, get_cms_setting('TEMPLATES')[0][0], 'en', published=True, publication_date=timezone.now(),) page.application_namespace = namespace + page.application_urls = 'NewsBlogApp' + + group = Group.objects.get(name='students') + user.groups.add(group) + permissions = list() + permissions.append(Permission.objects.filter(codename='add_article').first()) + permissions.append(Permission.objects.filter(codename='change_article').first()) + permissions.append(Permission.objects.filter(codename='delete_article').first()) + permissions.append(Permission.objects.filter(codename='view_article').first()) + user.user_permissions.set(permissions) page.save() admin = User.objects.filter(username='admin').first() api.publish_page(page, admin, 'en') diff --git a/gsoc/settings.py b/gsoc/settings.py index abca38d0..16210f51 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -130,7 +130,8 @@ 'cms.middleware.user.CurrentUserMiddleware', 'cms.middleware.page.CurrentPageMiddleware', 'cms.middleware.toolbar.ToolbarMiddleware', - 'cms.middleware.language.LanguageCookieMiddleware' + 'cms.middleware.language.LanguageCookieMiddleware', + ) INSTALLED_APPS = ( @@ -414,3 +415,4 @@ # Disable page cache so that CSRF token can be updated CMS_PAGE_CACHE = False +ALDRYN_NEWSBLOG_DEFAULT_PUBLISHED = True diff --git a/project.db b/project.db index 7664c0c195a8fc89095286b057a8b8a1a9a55974..3b5c3c871ec467e29e28caa117e6ac379979d1e3 100644 GIT binary patch delta 3964 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 From 30fe55aa2a9ceacbb31d8eee15a70c593d8f68a4 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 20 Apr 2019 11:30:05 +0800 Subject: [PATCH 0077/1137] send reload signal after adding a new blog --- gsoc/models.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index fafcae19..d4f968e5 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -22,6 +22,7 @@ from cms.models import Page, PagePermission from cms import api from cms.utils.conf import get_cms_setting +from cms.utils.apphook_reload import mark_urlconf_as_changed import phonenumbers from phonenumbers.phonenumbermatcher import PhoneNumberMatcher @@ -30,6 +31,7 @@ NewsBlogConfig.__str__ = lambda self: self.app_title + class SubOrg(models.Model): suborg_name = models.CharField(name='suborg_name', max_length=80) @@ -266,6 +268,11 @@ def gen_uuid_str(): class AddUserLog(models.Model): + class Meta: + verbose_name = 'Add Users' \ + '(The invites will be sent to the emails on save)' + verbose_name_plural = 'Add Users' \ + '(The invites will be sent to the emails on save)' log_id = models.CharField(max_length=36, default=gen_uuid_str) @@ -318,10 +325,14 @@ def is_usable(self): def create_user(self, *args, is_staff=True, **kwargs): namespace = str(uuid.uuid4()) user = User.objects.create(*args, is_staff=is_staff, **kwargs) + role = {k: v for v, k in UserProfile.ROLES} + if self.user_role != role.get('Student', 3): + return user blogname = f"{user.username}'s Blog" app_config = NewsBlogConfig.objects.create(namespace=namespace) app_config.app_title = blogname app_config.save() + UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, suborg_full_name=self.user_suborg, @@ -329,9 +340,11 @@ def create_user(self, *args, is_staff=True, **kwargs): page = api.create_page(blogname, get_cms_setting('TEMPLATES')[0][0], 'en', published=True, - publication_date=timezone.now(),) - page.application_namespace = namespace - page.application_urls = 'NewsBlogApp' + publication_date=timezone.now(), + apphook=app_config.cmsapp, + apphook_namespace=namespace) + admin = User.objects.filter(username='admin').first() + api.publish_page(page, admin, 'en') group = Group.objects.get(name='students') user.groups.add(group) @@ -341,9 +354,8 @@ def create_user(self, *args, is_staff=True, **kwargs): permissions.append(Permission.objects.filter(codename='delete_article').first()) permissions.append(Permission.objects.filter(codename='view_article').first()) user.user_permissions.set(permissions) - page.save() - admin = User.objects.filter(username='admin').first() - api.publish_page(page, admin, 'en') + + mark_urlconf_as_changed() return user def create_scheduler(self, trigger_time=timezone.now()): From 3d353fa68f63b11d8e134036252d24a6adbe7c23 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 20 Apr 2019 15:18:35 +0800 Subject: [PATCH 0078/1137] prevent refresh after add reglinks; order GSoC years from 2019 to 2005; remove email input field in register page --- gsoc/cms_toolbars.py | 6 +++--- gsoc/models.py | 17 +++++++++++------ gsoc/templates/registration/register.html | 4 ++-- gsoc/views.py | 16 +++------------- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index bfbbabed..12eb985e 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -22,8 +22,7 @@ from aldryn_newsblog.cms_toolbars import NewsBlogToolbar -from cms.toolbar_base import CMSToolbar -from cms.toolbar_pool import toolbar_pool +from cms.constants import FOLLOW_REDIRECT def add_admin_menu(self): @@ -55,7 +54,8 @@ def add_admin_menu(self): self._admin_menu.add_break(USER_SETTINGS_BREAK) self._admin_menu.add_modal_item( name='Add Users', - url=admin_reverse('gsoc_adduserlog_add') + url=admin_reverse('gsoc_adduserlog_add'), + on_close=None, ) # clipboard if self.toolbar.edit_mode_active: diff --git a/gsoc/models.py b/gsoc/models.py index d4f968e5..7150c264 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -40,6 +40,8 @@ def __str__(self): class GsocYear(models.Model): + class Meta: + ordering = ['-gsoc_year'] gsoc_year = models.IntegerField(name='gsoc_year') def __str__(self): @@ -324,19 +326,22 @@ def is_usable(self): def create_user(self, *args, is_staff=True, **kwargs): namespace = str(uuid.uuid4()) - user = User.objects.create(*args, is_staff=is_staff, **kwargs) + email = kwargs.get('email', self.email) + user = User.objects.create(*args, is_staff=is_staff, + email=email, **kwargs) role = {k: v for v, k in UserProfile.ROLES} + profile = UserProfile.objects.create(user=user, role=self.user_role, + gsoc_year=self.user_gsoc_year, + suborg_full_name=self.user_suborg) if self.user_role != role.get('Student', 3): return user + blogname = f"{user.username}'s Blog" app_config = NewsBlogConfig.objects.create(namespace=namespace) app_config.app_title = blogname app_config.save() - - UserProfile.objects.create(user=user, role=self.user_role, - gsoc_year=self.user_gsoc_year, - suborg_full_name=self.user_suborg, - app_config=app_config) + profile.app_config = app_config + profile.save() page = api.create_page(blogname, get_cms_setting('TEMPLATES')[0][0], 'en', published=True, diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 14946f1c..dff7bd57 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -16,8 +16,8 @@

    Welcome to registeration page!




    - -
    + +
    {% endif %} diff --git a/gsoc/views.py b/gsoc/views.py index 1e4a13c9..5fac6cee 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -107,20 +107,15 @@ def register_view(request): if reglink_usable is False: context['can_register'] = False context['warning'] = 'Your registeration link is invalid! Please check again!' + else: + context['email'] = reglink.email return shortcuts.render(request, 'registration/register.html', context) if request.method == 'POST': username = request.POST.get('username', '') password = request.POST.get('password', '') password2 = request.POST.get('password2', '') - email = request.POST.get('email', '') - email = email.strip() info_valid = True registeration_success = True - try: - validate_email(email) - except ValidationError: - context['warning'] += 'Invalid Email!
    ' - info_valid = False if password != password2: context['warning'] += 'Your password didn\'t match!
    ' info_valid = False @@ -131,11 +126,6 @@ def register_view(request): except User.DoesNotExist: pass - # Check if email's used - if email and User.objects.filter(email=email).first() is not None: - info_valid = False - context['warning'] += 'Your email has been used!
    ' - # Check password try: password_validation.validate_password(password) @@ -149,7 +139,7 @@ def register_view(request): info_valid = False if info_valid: - user = reglink.create_user(username=username, email=email) + user = reglink.create_user(username=username) user.set_password(password) user.save() else: From 5c03dadfc20170aec9888bcae7d6ab39470d16a1 Mon Sep 17 00:00:00 2001 From: "Odin Finch(Wang Zhao)" Date: Sat, 20 Apr 2019 15:33:23 +0800 Subject: [PATCH 0079/1137] Prettify model.py --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 7150c264..aadbf246 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -274,7 +274,7 @@ class Meta: verbose_name = 'Add Users' \ '(The invites will be sent to the emails on save)' verbose_name_plural = 'Add Users' \ - '(The invites will be sent to the emails on save)' + '(The invites will be sent to the emails on save)' log_id = models.CharField(max_length=36, default=gen_uuid_str) From e0ce44df552146eb544907102ea24206eb628a01 Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Sat, 20 Apr 2019 01:40:52 -0600 Subject: [PATCH 0080/1137] migrate --- gsoc/migrations/0014_auto_20190420_0140.py | 46 +++++++++++++++++++++ project.db | Bin 1318912 -> 1335296 bytes 2 files changed, 46 insertions(+) create mode 100644 gsoc/migrations/0014_auto_20190420_0140.py diff --git a/gsoc/migrations/0014_auto_20190420_0140.py b/gsoc/migrations/0014_auto_20190420_0140.py new file mode 100644 index 00000000..26c8f6f3 --- /dev/null +++ b/gsoc/migrations/0014_auto_20190420_0140.py @@ -0,0 +1,46 @@ +# Generated by Django 2.1.8 on 2019-04-20 01:40 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import gsoc.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0013_pagenotification'), + ] + + operations = [ + migrations.CreateModel( + name='AddUserLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('log_id', models.CharField(default=gsoc.models.gen_uuid_str, max_length=36)), + ], + options={ + 'verbose_name': 'Add Users(The invites will be sent to the emails on save)', + 'verbose_name_plural': 'Add Users(The invites will be sent to the emails on save)', + }, + ), + migrations.AlterModelOptions( + name='gsocyear', + options={'ordering': ['-gsoc_year']}, + ), + migrations.AddField( + model_name='reglink', + name='email', + field=models.CharField(default='', max_length=300, validators=[django.core.validators.EmailValidator()]), + ), + migrations.AddField( + model_name='reglink', + name='scheduler', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Scheduler'), + ), + migrations.AddField( + model_name='reglink', + name='adduserlog', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reglinks', to='gsoc.AddUserLog'), + ), + ] diff --git a/project.db b/project.db index 3b5c3c871ec467e29e28caa117e6ac379979d1e3..ba7e3799a7513b0425f58c8a5713893cc5600b6b 100644 GIT binary patch delta 2160 zcmbtVdu&rx7{BM-_wClZ-n*{E;kX-)t!tro?}sj|i~(~miW?F&bX|Mdnw4&@UC}6+ z-88tQ!j^$I8f1zI5rYwFvI=VC4VlTD8gL3hOaxq@Z1T(L_noi5 z?|Htya(MMChi!eyx=aiME3kVXrnTRkOJ-tA=AHh;HJ=9s13|t=i}T~i0<7R~^EdhN zu#j+pr;uV>Fni$U0>#kI?dJwH8SxsePO~-cZN8Yj!`bkVS3aC8Rl80o~c2SuJJNC zpTYsO0rTWxnmn>54`WZ$*p{X-fB+G21OkCXpb%&T27yK35HtvJ2=NG71RfzF(ln;q zZNPVdeGz(GCxdti^58c36`TP_!9I2QHC>Bp;{<;=bxjV8thuh+uj#U3ZvYwNO1TV9 z%idrwu)}Pa-N|lY{p@;n6`RM3%vI(qW-rsttYeli6n%+4Lcc@5L_bZ-G)w(L4O1Uc zeN-n^MWv9}$dlwQ(nqc$7ZCptKN3U4b|OGD5P5{2Anf z&m({Z+9u17VX=~Zy3N28$pr$#5~zDVbtxybRV5L?1Z@GgvMJWiJW1eead?}R*o&-* zHro(otkyW60EJN9Umzq!(lx@l#Gz4~NWe?T0+p79jSN*sj+6al0qXl9qHA5ZnZkf6 zsJq{7xx*_(Bay5wm(+kJ)0bJ_qDXFU!0lIj(k8dZBQ+_KPx1OCf1Bh-cgjYG+vAhk zypqomP>j!Yn-H}nsGq7tOpsCXiSl$Q|A#+oihovVqWqJ}htPLDApW)8M#OIn^7mCZ zN{(v&5atIE=XYn47$~%^4!9%X0-?4y6^%eV7Qi(BY6`hdb_dhP)X+9EmGI$L@D1=B z7Esd;3-yuw??eR>K{}MLIu7zhky}5A93);fhqBdOXT;Cd^=5IN+NfieMM6gf0jgho zEtE$-9T7+nX*ngdQbVHxH%@_tXhrD5y78-g1{RNhjPYO*#H}DDe zG$V%Ly|keL1+r2dJt-K}(h(tro>=u@Vbp=7ZXOXHQv)VZtA02lXwY+pm(<^~#22kW zF;q<(o=S(o;@L^uOSn0jMZPP?xxvxoh>d6}fuy-D)upF|B znaF24$ooW4UcRQnT3;d6TbET=O!Z^hdcYc&|0w!HLM$B?!f=f?Y=g5+rU6Lo4&4D(Sr(75DFB5Vq=6NDO8Ve2@j}Iu7qnvYo!=5 z!K8AM+SZ(vx@kwN^bbkRCDce=?Ao-(GkStGv0x3oTGSe2V$!5ijYu@8!;3G*Nj?wr z%gp?~^BYP>htlmML9!8T#3 zC@^j~zV@uKx*p{8tQ7M7G6=)sf!_GDT|0YsJt4jJZcOe&>$GkM&qSfPPu1b>?7r?S z5TJkrSx^L3z=9?afeO0d5xjy=Fa*Eboi)b-h{GxuFPN3Uu8ZdJj$3A@eLA|=t@_2B z^**1GMnJVhC+P{Aq=R$^jnYlDn3`mXd`G6qBpD?ylHKG{(n2ruU3CmzfdPtt`4eQYK^KX*OUomzw(H(K_T)_@+a~Md5;{E@0Hh} zztK(fHTo1yqTrh-fwrRv+Jx>vCj1Nj48MV&!}IVIOddYS>Vo~S6E?t7Xo4m1J-7%a zz!9(q>;Mr^efT=NTaR}Q^!CJ`dRmOtZ3RptHjlm}i(RvA#u}B74C)}CsAaisklS2K8FK4OtkXdDUY4HY zk$J0x=apM+dLo<*;lRU1Fwx2vO0A82;EJ`)J@OfQT-8PH796OkfG_m&(L1dW-~PH; zASZH@d3~uf$l(>#i8t-lG3~=!!EebWUbx6HNLfO z0Xcz_HGJDSW1}1Rm{I7q&RZEEa>9BoknoD2{KKrWiqB86^?YoKjsIWRfcBqH$KJYb GHT(m%h%wgy From 70f00c3ad9056aa16dfb199b74fd837c0c92538b Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Sat, 20 Apr 2019 18:15:07 +0530 Subject: [PATCH 0081/1137] Split Settings --- .gitignore | 2 +- gsoc/settings.py | 34 ++++++-------------------------- gsoc/settings_local.py.template | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 29 deletions(-) create mode 100644 gsoc/settings_local.py.template diff --git a/.gitignore b/.gitignore index ead0505a..058ed691 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # If you need to exclude files such as those generated by an IDE, use # $GIT_DIR/info/exclude or the core.excludesFile configuration variable as # described in https://git-scm.com/docs/gitignore - +settings_local.py # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/gsoc/settings.py b/gsoc/settings.py index 16210f51..661e817a 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -14,6 +14,11 @@ import django.utils.timezone as tz import os import datetime +try: + from settings_local import * +except ImportError: + raise Exception('Missing settings_local.py. Did you create it from the template?') + gettext = lambda s: s DATA_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -24,12 +29,6 @@ # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '0ngo5cy%zk_8g_sw%%7vc3(xn4pjm(zuu1!nvff!iri1cwa2)@' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - ALLOWED_HOSTS = ['*'] INTERNAL_IPS = ('127.0.0.1',) @@ -37,7 +36,7 @@ # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/2.1/ref/settings/#email # TODO: Update it with real settings -ADMINS = [('Admin 1', 'placeholder@python-gsoc.org')] + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_SUBJECT_PREFIX = '[Python-GSoC] ' if DEBUG: @@ -224,27 +223,6 @@ CMS_PLACEHOLDER_CONF = {} -DATABASES = { - 'default': { - 'CONN_MAX_AGE': 0, - 'ENGINE': 'django.db.backends.sqlite3', - 'HOST': 'localhost', - 'NAME': 'project.db', - 'PASSWORD': '', - 'PORT': '', - 'USER': '' - }, - 'auth_db': { - 'CONN_MAX_AGE': 0, - 'ENGINE': 'django.db.backends.sqlite3', - 'HOST': 'localhost', - 'NAME': 'users.db', - 'PASSWORD': '', - 'PORT': '', - 'USER': '' - }, -} - DATABASE_APPS_MAPPING = { 'auth': 'auth_db', 'admin': 'auth_db', diff --git a/gsoc/settings_local.py.template b/gsoc/settings_local.py.template new file mode 100644 index 00000000..0b3513f7 --- /dev/null +++ b/gsoc/settings_local.py.template @@ -0,0 +1,35 @@ +# Local settings. Copy this file to settings_local.py and modify, but do not add to repository. + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True +TEMPLATE_DEBUG = True + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'set to some long unique string' + +#ADMINS = ( +# ('Ad Min', 'admin@example.com'), +# ) + +DATABASES = { + 'default': { + 'CONN_MAX_AGE': 0, + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'NAME': 'project.db', # Or path to database file if using sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + 'USER': '' # Not used with sqlite3. + }, + 'auth_db': { + 'CONN_MAX_AGE': 0, + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'NAME': 'users.db', # Or path to database file if using sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + 'USER': '' # Not used with sqlite3. + }, +} + + From 0cbc26236369a8822fe996bfaf6bb82d179b5384 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 23 Apr 2019 13:53:20 +0530 Subject: [PATCH 0082/1137] Add proposal link to blogs list page --- blogs_list/templates/list_view.html | 6 ++++++ blogs_list/views.py | 5 ++++- gsoc/static/css/python-gsoc.css | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/blogs_list/templates/list_view.html b/blogs_list/templates/list_view.html index 3813d337..1c31a6d9 100644 --- a/blogs_list/templates/list_view.html +++ b/blogs_list/templates/list_view.html @@ -14,6 +14,12 @@

    GSoC {{ blogset.0 }}

    {{ blog.title }}

    {{ blog.student }}
    {{ blog.suborg }}
    + {% if blog.proposal %} + Proposal + {% else %} + No proposal available + {% endif %} +
    diff --git a/blogs_list/views.py b/blogs_list/views.py index 8038ee5a..532fcaff 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -28,13 +28,16 @@ def list_blogs(request): url = page.get_absolute_url() student_name = profile.user.get_full_name() student_username = profile.user.username + proposal_name = profile.accepted_proposal_pdf.name + proposal_path = '/media/{}'.format(proposal_name) blogset.append({ 'title': profile.app_config.app_title, 'url': url, 'student': student_name if student_name else student_username, 'suborg': profile.suborg_full_name.suborg_name, - 'color': random.choice(['umber', 'khaki', 'wine', 'straw']) + 'color': random.choice(['umber', 'khaki', 'wine', 'straw']), + 'proposal': proposal_path if proposal_name else None, }) if flag: diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index ac7d10a5..1d8dcce2 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -234,3 +234,21 @@ div.problem { .straw { color: #6D213C; } + +.card .proposal-link { + font-size: 12px; + color: blue; +} + +.card .proposal-link:hover { + text-decoration: underline; +} + +.card .proposal-link:visited { + color: purple; +} + +.card .no-proposal { + font-size: 12px; + color: red; +} \ No newline at end of file From 2e510a7bd23054c6c7f51313d74f763aa3a8c1ad Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 23 Apr 2019 14:32:34 +0530 Subject: [PATCH 0083/1137] Add support for dynamic proposals path in media --- blogs_list/views.py | 5 ++++- gsoc/models.py | 3 ++- gsoc/settings.py | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/blogs_list/views.py b/blogs_list/views.py index 532fcaff..50c7eb41 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -1,3 +1,4 @@ +import os import random from django.shortcuts import render @@ -7,6 +8,8 @@ GsocYear, UserProfile ) +from gsoc.settings import MEDIA_URL + from cms.models import Page @@ -29,7 +32,7 @@ def list_blogs(request): student_name = profile.user.get_full_name() student_username = profile.user.username proposal_name = profile.accepted_proposal_pdf.name - proposal_path = '/media/{}'.format(proposal_name) + proposal_path = os.path.join(MEDIA_URL, proposal_name) blogset.append({ 'title': profile.app_config.app_title, diff --git a/gsoc/models.py b/gsoc/models.py index aadbf246..3da9ec90 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -28,6 +28,7 @@ from phonenumbers.phonenumbermatcher import PhoneNumberMatcher from gsoc.common.utils.tools import build_send_mail_json +from gsoc.settings import PROPOSALS_PATH NewsBlogConfig.__str__ = lambda self: self.app_title @@ -65,7 +66,7 @@ class UserProfile(models.Model): role = models.IntegerField(name='role', choices=ROLES, default=0) gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) - accepted_proposal_pdf = models.FileField(blank=True, null=True) + accepted_proposal_pdf = models.FileField(blank=True, null=True, upload_to=PROPOSALS_PATH) app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True,) diff --git a/gsoc/settings.py b/gsoc/settings.py index 661e817a..070ec314 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -82,6 +82,8 @@ MEDIA_ROOT = os.path.join(DATA_DIR, 'media') STATIC_ROOT = os.path.join(DATA_DIR, 'static') +PROPOSALS_PATH = 'proposals/' + STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'gsoc', 'static'), ) From db48f03eb9f4487ab16b76b19610c1e16b269b7d Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Apr 2019 15:15:56 -0700 Subject: [PATCH 0084/1137] update settings --- gsoc/migrations/0015_auto_20190423_1031.py | 18 ++++++++++++++++++ gsoc/settings.py | 22 ---------------------- gsoc/settings_local.py.template | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 gsoc/migrations/0015_auto_20190423_1031.py diff --git a/gsoc/migrations/0015_auto_20190423_1031.py b/gsoc/migrations/0015_auto_20190423_1031.py new file mode 100644 index 00000000..06034677 --- /dev/null +++ b/gsoc/migrations/0015_auto_20190423_1031.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.8 on 2019-04-23 10:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0014_auto_20190420_0140'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='accepted_proposal_pdf', + field=models.FileField(blank=True, null=True, upload_to='proposals/'), + ), + ] diff --git a/gsoc/settings.py b/gsoc/settings.py index 070ec314..bccbf2a3 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -32,28 +32,6 @@ ALLOWED_HOSTS = ['*'] INTERNAL_IPS = ('127.0.0.1',) -# EMAIL CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/2.1/ref/settings/#email -# TODO: Update it with real settings - -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_SUBJECT_PREFIX = '[Python-GSoC] ' -if DEBUG: - EMAIL_USE_TLS = True - SERVER_EMAIL = 'psfgsoctestmail@airmail.cc' - EMAIL_HOST = 'mail.cock.li' - EMAIL_PORT = 587 - EMAIL_HOST_USER = "psfgsoctestmail@airmail.cc" - EMAIL_HOST_PASSWORD = "psfgsoctestmail" -else: - DEFAULT_FROM_EMAIL = 'placeholder@python-gsoc.org' - EMAIL_USE_TLS = True - SERVER_EMAIL = DEFAULT_FROM_EMAIL - EMAIL_HOST = 'smtp.gmail.com' - EMAIL_PORT = 587 - EMAIL_HOST_USER = "realemail@realdomain.tld" - EMAIL_HOST_PASSWORD = "supersecretpassword" if DEBUG: INETLOCATION = 'http://localhost:8000' else: diff --git a/gsoc/settings_local.py.template b/gsoc/settings_local.py.template index 0b3513f7..5b11f3ce 100644 --- a/gsoc/settings_local.py.template +++ b/gsoc/settings_local.py.template @@ -32,4 +32,25 @@ DATABASES = { }, } +# EMAIL CONFIGURATION +# ------------------------------------------------------------------------------ +# See: https://docs.djangoproject.com/en/2.1/ref/settings/#email +# TODO: Update it with real settings +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_SUBJECT_PREFIX = '[Python-GSoC] ' +if DEBUG: + EMAIL_USE_TLS = True + SERVER_EMAIL = 'psfgsoctestmail@airmail.cc' + EMAIL_HOST = 'mail.cock.li' + EMAIL_PORT = 587 + EMAIL_HOST_USER = "psfgsoctestmail@airmail.cc" + EMAIL_HOST_PASSWORD = "psfgsoctestmail" +else: + DEFAULT_FROM_EMAIL = 'placeholder@python-gsoc.org' + EMAIL_USE_TLS = True + SERVER_EMAIL = DEFAULT_FROM_EMAIL + EMAIL_HOST = 'smtp.gmail.com' + EMAIL_PORT = 587 + EMAIL_HOST_USER = "realemail@realdomain.tld" + EMAIL_HOST_PASSWORD = "supersecretpassword" From cfa9b22ec1f7dfa029dd4585183e88cf3ce9c5c9 Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Tue, 23 Apr 2019 16:24:15 -0600 Subject: [PATCH 0085/1137] update registration email --- gsoc/settings.py | 2 +- gsoc/templates/email/invite.html | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index bccbf2a3..650a9da0 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -35,7 +35,7 @@ if DEBUG: INETLOCATION = 'http://localhost:8000' else: - INETLOCATION = 'https://python-gsoc.org' + INETLOCATION = 'https://blogs.python-gsoc.org' # Application definition ROOT_URLCONF = 'gsoc.urls' diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index eb683d2a..db9b2f8e 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1 +1,8 @@ -Here is your register link! {{ register_link }} +Welcome to GSoC with the Python Software Foundation! + +To register you will need to goto {{ register_link }} + +If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org + +Thank you +Python GSoC Team! \ No newline at end of file From 3a8421b4f2b268ef9ae45f4388dbf46422b2ae9c Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Tue, 23 Apr 2019 16:34:00 -0600 Subject: [PATCH 0086/1137] update email templates --- gsoc/templates/email/invite.html | 16 ++++++++-------- gsoc/templates/registration/register.html | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index db9b2f8e..c6fe5693 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,8 +1,8 @@ -Welcome to GSoC with the Python Software Foundation! - -To register you will need to goto {{ register_link }} - -If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org - -Thank you -Python GSoC Team! \ No newline at end of file +Welcome to GSoC with the Python Software Foundation!
    +
    +To register you will need to goto {{ register_link }}
    +
    +If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org
    +
    +Thank you
    +Python GSoC Team!
    \ No newline at end of file diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index dff7bd57..595773bf 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -10,11 +10,11 @@

    Welcome to registeration page!


    {% csrf_token %} - +
    - +
    - +

    From 0a118430c2a6b1a68a704053b77771f7fa479884 Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Tue, 23 Apr 2019 19:38:15 -0600 Subject: [PATCH 0087/1137] set reply-to --- gsoc/common/utils/commands.py | 1 + gsoc/settings_local.py.template | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 207aa5b2..d1bbe0fb 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -28,6 +28,7 @@ def send_email(scheduler: Scheduler): message=content, subject=settings.EMAIL_SUBJECT_PREFIX + data['subject'], from_email=settings.SERVER_EMAIL, + headers = {'Reply-To': settings.REPLY_EMAIL}, recipient_list=data['send_to'], fail_silently=False, html_message=content, diff --git a/gsoc/settings_local.py.template b/gsoc/settings_local.py.template index 5b11f3ce..a799c4b8 100644 --- a/gsoc/settings_local.py.template +++ b/gsoc/settings_local.py.template @@ -39,18 +39,11 @@ DATABASES = { EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_SUBJECT_PREFIX = '[Python-GSoC] ' -if DEBUG: - EMAIL_USE_TLS = True - SERVER_EMAIL = 'psfgsoctestmail@airmail.cc' - EMAIL_HOST = 'mail.cock.li' - EMAIL_PORT = 587 - EMAIL_HOST_USER = "psfgsoctestmail@airmail.cc" - EMAIL_HOST_PASSWORD = "psfgsoctestmail" -else: - DEFAULT_FROM_EMAIL = 'placeholder@python-gsoc.org' - EMAIL_USE_TLS = True - SERVER_EMAIL = DEFAULT_FROM_EMAIL - EMAIL_HOST = 'smtp.gmail.com' - EMAIL_PORT = 587 - EMAIL_HOST_USER = "realemail@realdomain.tld" - EMAIL_HOST_PASSWORD = "supersecretpassword" + +EMAIL_USE_TLS = False +SERVER_EMAIL = 'no-reply@python-gsoc.org' +EMAIL_HOST = 'localhost' +EMAIL_PORT = 25 +#EMAIL_HOST_USER = "" +#EMAIL_HOST_PASSWORD = "" +REPLY_EMAIL = 'gsoc-admins@python.org' \ No newline at end of file From 3623666fd15b1128a19a22e34508d049d50d195a Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Apr 2019 19:09:35 -0700 Subject: [PATCH 0088/1137] update favicon and robots --- gsoc/templates/base.html | 2 ++ gsoc/templates/robots.txt | 5 +++++ gsoc/urls.py | 4 ++++ 3 files changed, 11 insertions(+) create mode 100644 gsoc/templates/robots.txt diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 146e32ba..eccabf47 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -16,6 +16,8 @@ + {% load static %} + {% block head %}{% endblock %} {% render_block "css" %} diff --git a/gsoc/templates/robots.txt b/gsoc/templates/robots.txt new file mode 100644 index 00000000..716a208b --- /dev/null +++ b/gsoc/templates/robots.txt @@ -0,0 +1,5 @@ +# robots.txt +User-agent: * +Disallow: +Crawl-delay: 5 +Sitemap: https://blogs.python-gsoc.org/sitemap.xml diff --git a/gsoc/urls.py b/gsoc/urls.py index 95cc9074..e4afe7c1 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -11,6 +11,8 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views.static import serve from django.urls import path,include +from django.views.generic import TemplateView +from django.views.generic.base import RedirectView import gsoc.views @@ -22,6 +24,8 @@ 'cmspages': CMSSitemap, } }), + url(r'^robots.txt$', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots_file"), + url(r'^favicon.ico$', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), ] #Add Django site authentication urls (for login, logout, password management) From 9c6c7efe611fa695eb7c42544c0bea3dca40ae38 Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Tue, 23 Apr 2019 20:22:30 -0600 Subject: [PATCH 0089/1137] update password reset --- gsoc/templates/registration/password_reset_email.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/registration/password_reset_email.html b/gsoc/templates/registration/password_reset_email.html index 37467b86..c186d5b8 100644 --- a/gsoc/templates/registration/password_reset_email.html +++ b/gsoc/templates/registration/password_reset_email.html @@ -1,2 +1,3 @@ Someone asked for password reset for email {{ email }}. Follow the link below: -{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + +{% url 'password_reset_confirm' uidb64=uid token=token %} From 7ba194477bd25a970fbd61b16b0e0e9d750aca73 Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Tue, 23 Apr 2019 20:24:44 -0600 Subject: [PATCH 0090/1137] update password reset template --- gsoc/templates/registration/password_reset_email.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/registration/password_reset_email.html b/gsoc/templates/registration/password_reset_email.html index c186d5b8..6cd0a1fa 100644 --- a/gsoc/templates/registration/password_reset_email.html +++ b/gsoc/templates/registration/password_reset_email.html @@ -1,3 +1,3 @@ Someone asked for password reset for email {{ email }}. Follow the link below: -{% url 'password_reset_confirm' uidb64=uid token=token %} +{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} From 651cdbd7ae3deddac665fa957887cb600475e43d Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Apr 2019 20:07:21 -0700 Subject: [PATCH 0091/1137] set reply-to header --- gsoc/common/utils/commands.py | 14 +++++++------- gsoc/management/commands/runcron.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index d1bbe0fb..563a1442 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -1,7 +1,7 @@ import json from smtplib import SMTPResponseException, SMTPSenderRefused -from django.core.mail import send_mail +from django.core.mail import EmailMessage from django.conf import settings from django.template.loader import get_template @@ -24,15 +24,15 @@ def send_email(scheduler: Scheduler): if isinstance(data['send_to'], str): data['send_to'] = [data['send_to']] try: - send_mail( - message=content, + send_email = EmailMessage( + body=content, subject=settings.EMAIL_SUBJECT_PREFIX + data['subject'], from_email=settings.SERVER_EMAIL, - headers = {'Reply-To': settings.REPLY_EMAIL}, - recipient_list=data['send_to'], - fail_silently=False, - html_message=content, + reply_to = settings.REPLY_EMAIL, + to=data['send_to'], ) + send_email.content_subtype = "html" + send_email.send() except SMTPSenderRefused as e: last_error = json.dumps({ "message": str(e), diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index ee62ce43..33e4ca4f 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -15,7 +15,7 @@ class Command(BaseCommand): requires_system_checks = False # for debugging #cleanup sessions - Session.objects.all().delete() + #Session.objects.all().delete() def add_arguments(self, parser): parser.add_argument( From b5bdaed336826b9f73b54a9282cc3aec092d5404 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Wed, 24 Apr 2019 11:12:32 +0800 Subject: [PATCH 0092/1137] fix NoneType error for publishing page --- gsoc/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 3da9ec90..e949f6cd 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -349,8 +349,8 @@ def create_user(self, *args, is_staff=True, **kwargs): publication_date=timezone.now(), apphook=app_config.cmsapp, apphook_namespace=namespace) - admin = User.objects.filter(username='admin').first() - api.publish_page(page, admin, 'en') + su = User.objects.filter(is_superuser=True).first() + api.publish_page(page, su, 'en') group = Group.objects.get(name='students') user.groups.add(group) From 9f3342b90e67fa5479c35b3f89311c52b1b24f4e Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Apr 2019 22:01:36 -0700 Subject: [PATCH 0093/1137] fix issue with adding user --- gsoc/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index e949f6cd..58dded7b 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -352,8 +352,6 @@ def create_user(self, *args, is_staff=True, **kwargs): su = User.objects.filter(is_superuser=True).first() api.publish_page(page, su, 'en') - group = Group.objects.get(name='students') - user.groups.add(group) permissions = list() permissions.append(Permission.objects.filter(codename='add_article').first()) permissions.append(Permission.objects.filter(codename='change_article').first()) From 2519963ee964f729c0be3d8c6ce549fd80c7a507 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 24 Apr 2019 16:32:42 +0530 Subject: [PATCH 0094/1137] Add goto blog button for students --- gsoc/cms_toolbars.py | 24 +++++++++++++++++++++++- gsoc/models.py | 2 ++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 12eb985e..522dc9e8 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -21,8 +21,8 @@ from aldryn_newsblog.models import Article from aldryn_newsblog.cms_toolbars import NewsBlogToolbar - from cms.constants import FOLLOW_REDIRECT +from cms.models import Page def add_admin_menu(self): @@ -80,7 +80,29 @@ def add_admin_menu(self): # logout self.add_logout_button(self._admin_menu) + +def add_goto_blog_button(self): + user = getattr(self.request, 'user', None) + if user and user.is_current_year_student(): + profile = user.student_profile() + ns = profile.app_config.app_title + page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) + url = page.get_absolute_url() + self.toolbar.add_button(_('My Blog'), url, side=self.toolbar.RIGHT) + + +def populate(self): + if not self.page: + self.init_from_request() + self.clipboard = self.request.toolbar.user_settings.clipboard + self.add_admin_menu() + self.add_language_menu() + self.add_goto_blog_button() + + BasicToolbar.add_admin_menu = add_admin_menu +BasicToolbar.add_goto_blog_button = add_goto_blog_button +BasicToolbar.populate = populate def populate(self): diff --git a/gsoc/models.py b/gsoc/models.py index 58dded7b..397a1e64 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -97,6 +97,8 @@ def is_current_year_student(self): def student_profile(self): try: + # TODO: will raise MultipleObjectsReturned when there are more than + # one student profiles, fix this return self.userprofile_set.get(role=3) except UserProfile.DoesNotExist: return None From 8487ee92e3843223f4cd90686b1498f6f7810639 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 24 Apr 2019 06:01:45 -0600 Subject: [PATCH 0095/1137] Update README.md --- docs/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/README.md b/docs/README.md index 07e5d1d2..0f7adf80 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,12 @@ To install development dependncies: ``` $ pip install -r requirements.txt ``` + +To setup settings copy settings_local.py.template to the root of the dir +``` +cp gsoc\settings_local.py.template settings_local.py +``` + ## Usage ```python From 1747d2f9def362cb06a184545ae91741b5290056 Mon Sep 17 00:00:00 2001 From: "cyberempires@gmail.com" Date: Wed, 24 Apr 2019 06:07:26 -0600 Subject: [PATCH 0096/1137] move template --- gsoc/settings_local.py.template => settings_local.py.template | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gsoc/settings_local.py.template => settings_local.py.template (100%) diff --git a/gsoc/settings_local.py.template b/settings_local.py.template similarity index 100% rename from gsoc/settings_local.py.template rename to settings_local.py.template From d38d95186b75cc26dcaf87ef71a449defb91f5bc Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 24 Apr 2019 06:10:54 -0600 Subject: [PATCH 0097/1137] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 0f7adf80..b6395eee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,7 +14,7 @@ $ pip install -r requirements.txt To setup settings copy settings_local.py.template to the root of the dir ``` -cp gsoc\settings_local.py.template settings_local.py +cp settings_local.py.template settings_local.py ``` ## Usage From 1713361f21e1ebe878b133e0c72bf5f35d2f6bef Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 25 Apr 2019 08:52:48 +0530 Subject: [PATCH 0098/1137] Add error handling if sending email fails --- gsoc/common/utils/commands.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 563a1442..976e2cf4 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -41,7 +41,7 @@ def send_email(scheduler: Scheduler): scheduler.last_error = last_error scheduler.success = False scheduler.save() - return None + return str(e) except SMTPResponseException as e: last_error = json.dumps({ "message": str(e), @@ -50,7 +50,7 @@ def send_email(scheduler: Scheduler): scheduler.last_error = last_error scheduler.success = False scheduler.save() - return None + return str(e) except Exception as e: last_error = json.dumps({ "message": str(e), @@ -58,7 +58,7 @@ def send_email(scheduler: Scheduler): scheduler.last_error = last_error scheduler.success = False scheduler.save() - return None + return str(e) scheduler.last_error = None scheduler.success = True scheduler.save() From 838f6cb947f4a50e401f1f11a9b6d60383bab9ff Mon Sep 17 00:00:00 2001 From: root Date: Thu, 25 Apr 2019 02:23:54 -0700 Subject: [PATCH 0099/1137] require accept code of conduct --- gsoc/static/js/gsoc.js | 9 +++++++++ gsoc/templates/base.html | 2 ++ gsoc/templates/registration/register.html | 9 +++++---- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 gsoc/static/js/gsoc.js diff --git a/gsoc/static/js/gsoc.js b/gsoc/static/js/gsoc.js new file mode 100644 index 00000000..200e408b --- /dev/null +++ b/gsoc/static/js/gsoc.js @@ -0,0 +1,9 @@ +//js for register button +$(function() { + var chk = $('#check'); + var btn = $('#btncheck'); + + chk.on('change', function() { + btn.prop("disabled", !this.checked);//true: disabled, false: enabled + }).trigger('change'); //page load trigger event +}); diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index eccabf47..af9b2905 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -16,6 +16,7 @@ + {% load static %} {% block head %}{% endblock %} @@ -105,5 +106,6 @@ {% render_block 'js' %} + diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 595773bf..1c6dd3f0 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -6,8 +6,8 @@

    {{ warning| safe }}

    {% endif %} {% if can_register and not done_registeration %} -

    Welcome to registeration page!


    - +

    Sign up for a PSF GSoC account!


    + {% csrf_token %} @@ -17,8 +17,9 @@

    Welcome to registeration page!



    -
    - +
    + I accept the Python Community Code of Conduct for the duration of the program.
    + {% endif %} {% if not can_register %} From aa90ce12b2f62e089dab6a68fa28da1571ca2004 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 27 Apr 2019 19:19:46 +0800 Subject: [PATCH 0100/1137] set new blogs as blog list's child pages --- gsoc/models.py | 6 +++++- project.db | Bin 1335296 -> 1335296 bytes ....py.template => settings_local.py.template | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename gsoc/settings_local.py.template => settings_local.py.template (100%) diff --git a/gsoc/models.py b/gsoc/models.py index e949f6cd..17c894e6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -343,12 +343,16 @@ def create_user(self, *args, is_staff=True, **kwargs): app_config.save() profile.app_config = app_config profile.save() + blog_list_page = Page.objects.\ + filter(application_namespace='blogs_list').\ + filter(publisher_is_draft=True).first() page = api.create_page(blogname, get_cms_setting('TEMPLATES')[0][0], 'en', published=True, publication_date=timezone.now(), apphook=app_config.cmsapp, - apphook_namespace=namespace) + apphook_namespace=namespace, + parent=blog_list_page) su = User.objects.filter(is_superuser=True).first() api.publish_page(page, su, 'en') diff --git a/project.db b/project.db index ba7e3799a7513b0425f58c8a5713893cc5600b6b..bfeb4c5e9b898efd054c279e1d06d122d224211e 100644 GIT binary patch delta 5458 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 delta 2465 zcma)83v5%@8NQF>d;Pfg{@3K8ls@c1*Cqicj-ABVMKc>zjOP-9Iv!F&oH#E+>?DNp zC?sfwY+6;P+|tvjf@IxTvTJ zZI7^THPG0C~K~$iB`7MN1K#Ain51G=f3wbPRzPLHACNN z6@SQAY-ldq*c6R6#44kTab;GPT#~+%h<+PA{gx%A$1{5_&%2B|Pe>Be=Zk(TJ^PxP zV*J@t#|uVg&L&!{m=`?;a@Ngm*9PL{#&5O zTlpm@KFh&J@IJf)XWrW61n)=j84hm32N?ey7=uBicm~BsfS&2(9}`P7 zpmG=AjZnGD;1EB_BNn0Z;UxB&+8%j>XOTCAmw8O%I)@RiBhM?qt`7*BF6JqvR2~)b zd`MwG?1V~KsQyXWq%BYzv{Ln#${!U&y`U=UUzN|*uhd7iG&QW{sH9B&z6M_c?4*^|qM|c&X9eZWbL>2?x0pe~%wo_S8LW_WHcZu!uraPdI zgWK4EtMDm&j7fEh8;s1q|VRdkXiVm*=qY9x{_ zN~qj{`VlBW*afttlN_0zIk5;mF5|B#$E1=U!^q`(iX`VQIW{qFRHEt(tkimANLV%GLSV8t;@ zkJ-#db6GASk-x!7coklR1GwZnpb@o7Kz1lHmu9oF%_l9-xKVpQ_w7<7m*hCE$zdEt zbE4aT?H{*Pc^^pE`YhV8)`z391(S^?Esnxc?NjXo_OM%O(+qb!oAu>Lp&uye+S?X$ znzVD;+bp$6dd3tMQWTcayfSHMs`mO_J;&z#U$taLtF*{HtI!__1`50do~mFZ-;*B* zMm&+qsw$5+P*~*;Rt6)%!k}sy=F-JZ+)l}?&w7z{{!WT2-;d6WlTxD>@&Bv}+i^$Q zjpeltQ`s$>=3Q1coGE){RBNL6^t?Vt4Bo`cWfJmMq3&L^xh^={&}=sjfOcJA&`>+Vt8(NQ}w+dIL*b(mnzHtA!a*IW)A zv2pWY8%N`hfR5)n(h$1L!311J=)G@7=CiOP-TmLXzXG~9=2$63tIAvIH?r}VBhBUW5-@jFui?jq|_OtN=Dxk;#(!-KRJX_z#_O6q;Qm75A&))0NNIZ_|1&M_~<@;K`a zD?icgf|lT58vbxhD^hN$w7*?>TKI*lJyEK#M5)?Re$S>ns~;UzNfG8T!odhk@VA-f z)%F8D7;|K`kAyY+0vieIxowQ-$?IZ3cj$~9wh20u+?KE50CCP^WS&fP0dBMDZTcm% zsPNV-nwRGP2Doznwm8~UzERkf>Q`(%R+M^uzu(*tGic~iRqyED3x;k&&HAhG#6apel+zb!0+)DxV;6Tg2GUN zFDF0X^#uddSpOksu(6vAih4WoPU|M#ln%n_*ha;hI)Rn69vw^}ZqZE|@vuwzF*?m6 htnpfkF$=dt(wW_xdetoWALfbr3tb>FsVMa({2K*|2;2Yw diff --git a/gsoc/settings_local.py.template b/settings_local.py.template similarity index 100% rename from gsoc/settings_local.py.template rename to settings_local.py.template From 481a6b4b9eb0f4a0c0b2bda318a45de65073129b Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 27 Apr 2019 19:21:23 +0800 Subject: [PATCH 0101/1137] restore files --- .../settings_local.py.template | 0 project.db | Bin 1335296 -> 1335296 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename settings_local.py.template => gsoc/settings_local.py.template (100%) diff --git a/settings_local.py.template b/gsoc/settings_local.py.template similarity index 100% rename from settings_local.py.template rename to gsoc/settings_local.py.template diff --git a/project.db b/project.db index bfeb4c5e9b898efd054c279e1d06d122d224211e..ba7e3799a7513b0425f58c8a5713893cc5600b6b 100644 GIT binary patch delta 2465 zcma)83v5%@8NQF>d;Pfg{@3K8ls@c1*Cqicj-ABVMKc>zjOP-9Iv!F&oH#E+>?DNp zC?sfwY+6;P+|tvjf@IxTvTJ zZI7^THPG0C~K~$iB`7MN1K#Ain51G=f3wbPRzPLHACNN z6@SQAY-ldq*c6R6#44kTab;GPT#~+%h<+PA{gx%A$1{5_&%2B|Pe>Be=Zk(TJ^PxP zV*J@t#|uVg&L&!{m=`?;a@Ngm*9PL{#&5O zTlpm@KFh&J@IJf)XWrW61n)=j84hm32N?ey7=uBicm~BsfS&2(9}`P7 zpmG=AjZnGD;1EB_BNn0Z;UxB&+8%j>XOTCAmw8O%I)@RiBhM?qt`7*BF6JqvR2~)b zd`MwG?1V~KsQyXWq%BYzv{Ln#${!U&y`U=UUzN|*uhd7iG&QW{sH9B&z6M_c?4*^|qM|c&X9eZWbL>2?x0pe~%wo_S8LW_WHcZu!uraPdI zgWK4EtMDm&j7fEh8;s1q|VRdkXiVm*=qY9x{_ zN~qj{`VlBW*afttlN_0zIk5;mF5|B#$E1=U!^q`(iX`VQIW{qFRHEt(tkimANLV%GLSV8t;@ zkJ-#db6GASk-x!7coklR1GwZnpb@o7Kz1lHmu9oF%_l9-xKVpQ_w7<7m*hCE$zdEt zbE4aT?H{*Pc^^pE`YhV8)`z391(S^?Esnxc?NjXo_OM%O(+qb!oAu>Lp&uye+S?X$ znzVD;+bp$6dd3tMQWTcayfSHMs`mO_J;&z#U$taLtF*{HtI!__1`50do~mFZ-;*B* zMm&+qsw$5+P*~*;Rt6)%!k}sy=F-JZ+)l}?&w7z{{!WT2-;d6WlTxD>@&Bv}+i^$Q zjpeltQ`s$>=3Q1coGE){RBNL6^t?Vt4Bo`cWfJmMq3&L^xh^={&}=sjfOcJA&`>+Vt8(NQ}w+dIL*b(mnzHtA!a*IW)A zv2pWY8%N`hfR5)n(h$1L!311J=)G@7=CiOP-TmLXzXG~9=2$63tIAvIH?r}VBhBUW5-@jFui?jq|_OtN=Dxk;#(!-KRJX_z#_O6q;Qm75A&))0NNIZ_|1&M_~<@;K`a zD?icgf|lT58vbxhD^hN$w7*?>TKI*lJyEK#M5)?Re$S>ns~;UzNfG8T!odhk@VA-f z)%F8D7;|K`kAyY+0vieIxowQ-$?IZ3cj$~9wh20u+?KE50CCP^WS&fP0dBMDZTcm% zsPNV-nwRGP2Doznwm8~UzERkf>Q`(%R+M^uzu(*tGic~iRqyED3x;k&&HAhG#6apel+zb!0+)DxV;6Tg2GUN zFDF0X^#uddSpOksu(6vAih4WoPU|M#ln%n_*ha;hI)Rn69vw^}ZqZE|@vuwzF*?m6 htnpfkF$=dt(wW_xdetoWALfbr3tb>FsVMa({2K*|2;2Yw delta 5458 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 From 3169623e2b1a2cc8ef1bef4c35b7e4399f36203d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 28 Apr 2019 17:40:22 +0530 Subject: [PATCH 0102/1137] Add error message when there are no blogs present --- blogs_list/views.py | 5 ++++- gsoc/templates/base.html | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/blogs_list/views.py b/blogs_list/views.py index 50c7eb41..c8b0a4d8 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -46,6 +46,9 @@ def list_blogs(request): if flag: blogsets.append((year.gsoc_year, blogset)) + err = "No blogs currently! Please visit again later." if not blogsets else None + return render(request, 'list_view.html', { - 'blogsets': blogsets + 'blogsets': blogsets, + 'errors': [err] }) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index af9b2905..674819bd 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -85,6 +85,13 @@ No such user found! Please verify your credentials and enter again.
  • {% endif %} + {% if errors %} + {% for e in errors %} +
    + {{ e }} +
    + {% endfor %} + {% endif %} {% if request.current_page.publisher_is_draft %} {% for notification in request.current_page.notifications.all %}
    From e4fb2d0c159611d0f40fd84b061eea5415d59375 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 29 Apr 2019 10:58:14 +0530 Subject: [PATCH 0103/1137] Make prospector happy --- blogs_list/urls.py | 1 - blogs_list/views.py | 8 ++-- gsoc/__init__.py | 1 - gsoc/admin.py | 23 +++++----- gsoc/cms_toolbars.py | 24 +++++------ gsoc/cms_wizards.py | 12 ++++-- gsoc/common/utils/commands.py | 26 +++++++----- gsoc/common/utils/irc.py | 12 ++++-- gsoc/management/commands/runcron.py | 34 ++++++++------- gsoc/models.py | 26 ++++++------ gsoc/router.py | 11 ++--- gsoc/settings.py | 66 +++++++++++++++-------------- gsoc/templatetags/app_tag.py | 30 ++++++------- gsoc/urls.py | 11 +++-- gsoc/views.py | 24 +++++++---- manage.py | 2 +- 16 files changed, 163 insertions(+), 148 deletions(-) diff --git a/blogs_list/urls.py b/blogs_list/urls.py index 0d7e1baf..47daa90a 100644 --- a/blogs_list/urls.py +++ b/blogs_list/urls.py @@ -1,5 +1,4 @@ from django.conf.urls import url -from django.urls import path from .views import list_blogs diff --git a/blogs_list/views.py b/blogs_list/views.py index c8b0a4d8..fe4ed389 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -2,7 +2,6 @@ import random from django.shortcuts import render -from django.http import Http404 from gsoc.models import ( GsocYear, @@ -28,7 +27,6 @@ def list_blogs(request): ns = profile.app_config.namespace page = Page.objects.filter(application_namespace=ns) page = page.filter(publisher_is_draft=False).first() - url = page.get_absolute_url() student_name = profile.user.get_full_name() student_username = profile.user.username proposal_name = profile.accepted_proposal_pdf.name @@ -36,12 +34,12 @@ def list_blogs(request): blogset.append({ 'title': profile.app_config.app_title, - 'url': url, + 'url': page.get_absolute_url(), 'student': student_name if student_name else student_username, 'suborg': profile.suborg_full_name.suborg_name, 'color': random.choice(['umber', 'khaki', 'wine', 'straw']), 'proposal': proposal_path if proposal_name else None, - }) + }) if flag: blogsets.append((year.gsoc_year, blogset)) @@ -51,4 +49,4 @@ def list_blogs(request): return render(request, 'list_view.html', { 'blogsets': blogsets, 'errors': [err] - }) + }) diff --git a/gsoc/__init__.py b/gsoc/__init__.py index 8b137891..e69de29b 100644 --- a/gsoc/__init__.py +++ b/gsoc/__init__.py @@ -1 +0,0 @@ - diff --git a/gsoc/admin.py b/gsoc/admin.py index 0e09f409..9d85911d 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -181,7 +181,7 @@ def Article_delete_model(self, request, obj): break if has_delete_perm: - super(ArticleAdmin, self).delete_model(request, obj, form, change) + super(ArticleAdmin, self).delete_model(request, obj) else: raise PermissionDenied() @@ -190,16 +190,14 @@ def Article_get_queryset(self, request): user = request.user qs = Article.objects.all() - if user.is_superuser: - return qs - else: + if not user.is_superuser: userprofiles = user.userprofile_set.all() app_configs = [] for profile in userprofiles: app_configs.append(profile.app_config) qs = qs.filter(app_config__in=app_configs) - print(qs) - return qs + + return qs ArticleAdmin.save_model = Article_save_model @@ -239,15 +237,16 @@ class RegLinkAdmin(admin.ModelAdmin): ] def get_readonly_fields(self, request, obj=None): + readonly_fields = self.readonly_fields if obj and obj.is_used: - return self.readonly_fields + ( + readonly_fields += ( "user_role", "user_suborg", "user_gsoc_year", 'email', ) - else: - return self.readonly_fields + + return readonly_fields admin.site.register(RegLink, RegLinkAdmin) @@ -288,12 +287,14 @@ class PageNotificationAdmin(admin.ModelAdmin): def get_fieldsets(self, request, obj=None): if request.user.is_superuser: - return ( + fieldsets = ( (None, { 'fields': ('user', 'page', 'message') }), ) else: - return ((None, {'fields': ('page', 'message')}), ) + fieldsets = ((None, {'fields': ('page', 'message')}), ) + + return fieldsets def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "page": diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 522dc9e8..fe5ed335 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -10,7 +10,6 @@ from cms.utils.conf import get_cms_setting from cms.utils.urlutils import admin_reverse -from django.shortcuts import reverse from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language_from_request @@ -21,7 +20,6 @@ from aldryn_newsblog.models import Article from aldryn_newsblog.cms_toolbars import NewsBlogToolbar -from cms.constants import FOLLOW_REDIRECT from cms.models import Page @@ -40,7 +38,7 @@ def add_admin_menu(self): sites_menu.add_break(ADMIN_SITES_BREAK) for site in sites_queryset: sites_menu.add_link_item(site.name, url='http://%s' % site.domain, - active=site.pk == self.current_site.pk) + active=site.pk == self.current_site.pk) # admin self._admin_menu.add_sideframe_item(_('Administration'), url=admin_reverse('index')) @@ -56,25 +54,27 @@ def add_admin_menu(self): name='Add Users', url=admin_reverse('gsoc_adduserlog_add'), on_close=None, - ) + ) # clipboard if self.toolbar.edit_mode_active: # True if the clipboard exists and there's plugins in it. clipboard_is_bound = self.toolbar.clipboard_plugin self._admin_menu.add_link_item(_('Clipboard...'), url='#', - extra_classes=['cms-clipboard-trigger'], - disabled=not clipboard_is_bound) + extra_classes=['cms-clipboard-trigger'], + disabled=not clipboard_is_bound) self._admin_menu.add_link_item(_('Clear clipboard'), url='#', - extra_classes=['cms-clipboard-empty'], - disabled=not clipboard_is_bound) + extra_classes=['cms-clipboard-empty'], + disabled=not clipboard_is_bound) self._admin_menu.add_break(CLIPBOARD_BREAK) # Disable toolbar - self._admin_menu.add_link_item(_('Disable toolbar'), url='?%s' % get_cms_setting('CMS_TOOLBAR_URL__DISABLE')) + self._admin_menu.add_link_item( + _('Disable toolbar'), url='?%s' % + get_cms_setting('CMS_TOOLBAR_URL__DISABLE')) self._admin_menu.add_break(TOOLBAR_DISABLE_BREAK) self._admin_menu.add_link_item(_('Shortcuts...'), url='#', - extra_classes=('cms-show-shortcuts',)) + extra_classes=('cms-show-shortcuts',)) self._admin_menu.add_break(SHORTCUTS_BREAK) # logout @@ -127,7 +127,7 @@ def populate(self): article = None menu = self.toolbar.get_or_create_menu('newsblog-app', - config.get_app_title()) + config.get_app_title()) change_config_perm = user.has_perm( 'aldryn_newsblog.change_newsblogconfig') @@ -147,7 +147,7 @@ def populate(self): delete_article_perm = user.is_superuser if article else False article_perms = [change_article_perm, add_article_perm, - delete_article_perm, ] + delete_article_perm, ] if change_config_perm: url_args = {} diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py index 908e0885..ac445489 100644 --- a/gsoc/cms_wizards.py +++ b/gsoc/cms_wizards.py @@ -33,28 +33,31 @@ def user_has_add_permission(self, user, **kwargs): # By default, no permission. return False + NewsBlogArticleWizard.user_has_add_permission = user_has_add_permission CreateNewsBlogArticleForm.Meta.fields = ['title'] + def __init__(self, **kwargs): super(CreateNewsBlogArticleForm, self).__init__(**kwargs) # If there's only 1 (or zero) app_configs, don't bother show the # app_config choice field, we'll choose the option for the user. - app_configs = get_published_app_configs() + get_published_app_configs() userprofiles = self.user.userprofile_set.all() app_config_choices = [] for profile in userprofiles: app_config_choices.append((profile.app_config.pk, profile.app_config.get_app_title())) - + self.fields['app_config'] = forms.ChoiceField( label=_('Section'), required=True, choices=app_config_choices - ) + ) + def save(self, commit=True): article = super(CreateNewsBlogArticleForm, self).save(commit=False) @@ -70,9 +73,10 @@ def save(self, commit=True): plugin_type='TextPlugin', language=self.language_code, body=content, - ) + ) return article + CreateNewsBlogArticleForm.__init__ = __init__ CreateNewsBlogArticleForm.save = save diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 976e2cf4..c052c68f 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -28,16 +28,16 @@ def send_email(scheduler: Scheduler): body=content, subject=settings.EMAIL_SUBJECT_PREFIX + data['subject'], from_email=settings.SERVER_EMAIL, - reply_to = settings.REPLY_EMAIL, + reply_to=settings.REPLY_EMAIL, to=data['send_to'], - ) + ) send_email.content_subtype = "html" send_email.send() except SMTPSenderRefused as e: last_error = json.dumps({ "message": str(e), "smtp_code": e.smtp_code, - }) + }) scheduler.last_error = last_error scheduler.success = False scheduler.save() @@ -46,7 +46,7 @@ def send_email(scheduler: Scheduler): last_error = json.dumps({ "message": str(e), "smtp_code": e.smtp_code, - }) + }) scheduler.last_error = last_error scheduler.success = False scheduler.save() @@ -54,7 +54,7 @@ def send_email(scheduler: Scheduler): except Exception as e: last_error = json.dumps({ "message": str(e), - }) + }) scheduler.last_error = last_error scheduler.success = False scheduler.save() @@ -69,12 +69,16 @@ def deactivate_user(scheduler: Scheduler): """ makes a user inactive when scheduled """ - u = User.objects.filter(pk=scheduler.data).first() - u.is_active = False - u.save() - scheduler.success = True - scheduler.save() - return None + try: + u = User.objects.filter(pk=scheduler.data).first() + u.is_active = False + u.save() + scheduler.success = True + scheduler.save() + return None + except Exception as e: + return str(e) + def send_irc_msgs(schedulers): """ diff --git a/gsoc/common/utils/irc.py b/gsoc/common/utils/irc.py index 7d1fe101..3cabd968 100644 --- a/gsoc/common/utils/irc.py +++ b/gsoc/common/utils/irc.py @@ -7,12 +7,14 @@ import gsoc.settings as config + class ModIRCClient(IRCClient): def __init__(self, handler, nick, server, messages): IRCClient.__init__(self, handler, nick, server) self.messages = messages + class CommandBot(BaseIRCHandler): def handle_register(self): @@ -25,10 +27,11 @@ def handle_register(self): def handle_disconnect(self): self.client.terminate() - def handle_error(self, num, **params): - if num == Err.NICKNAMEINUSE: + def handle_error(self, error, **params): + if error == Err.NICKNAMEINUSE: new_nick = params['nick'] + str(randint(1, 9)) - self.client.register(nick = new_nick) + self.client.register(nick=new_nick) + def parse_data(data): """ @@ -38,7 +41,7 @@ def parse_data(data): """ data = json.loads(data) chunk_size = 150 - chunks = [data['message'][i:i+chunk_size] for i in range(0, len(data['message']), chunk_size)] + chunks = [data['message'][i:i + chunk_size] for i in range(0, len(data['message']), chunk_size)] num_chunks = len(chunks) commands = [] for i in range(num_chunks): @@ -53,6 +56,7 @@ def parse_data(data): return commands + def send_message(messages): """ sends a set of messages to the receiver on irc after parsing them diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index 33e4ca4f..d16d0cd6 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -1,21 +1,20 @@ from multiprocessing.dummy import Pool as ThreadPool -from multiprocessing import TimeoutError from django.utils import timezone -from django.core.management.base import BaseCommand, CommandError -from django.contrib.sessions.models import Session +from django.core.management.base import BaseCommand import gsoc.settings as config from gsoc.models import Scheduler from gsoc.common.utils import commands + class Command(BaseCommand): help = 'Run the cron command to process items such as sending scheduled emails etc.' tasks = ['build_items', 'process_items'] requires_system_checks = False # for debugging - - #cleanup sessions - #Session.objects.all().delete() + + # cleanup sessions + # Session.objects.all().delete() def add_arguments(self, parser): parser.add_argument( @@ -24,7 +23,7 @@ def add_arguments(self, parser): choices=self.tasks, type=str, help='The task which will be started' - ) + ) parser.add_argument( '-t', '--timeout', @@ -32,7 +31,7 @@ def add_arguments(self, parser): default=config.RUNCRON_TIMEOUT, type=int, help='Set timeout' - ) + ) parser.add_argument( '-n', '--num_workers', @@ -40,7 +39,7 @@ def add_arguments(self, parser): default=config.RUNCRON_NUM_WORKERS, type=int, help='Set number of workers' - ) + ) def build_items(self, options): # build tasks @@ -49,17 +48,22 @@ def build_items(self, options): def handle_process(self, scheduler): if scheduler.activation_date and timezone.now() > scheduler.activation_date: self.stdout.write('Running command {}:{}' - .format(scheduler.command, scheduler.id), ending='\n') + .format(scheduler.command, scheduler.id), ending='\n') err = getattr(commands, scheduler.command)(scheduler) if not err: self.stdout.write(self.style.SUCCESS('Finished command {}:{}' - .format(scheduler.command, scheduler.id)), ending='\n') + .format(scheduler.command, scheduler.id)), ending='\n') scheduler.success = True scheduler.save() else: - self.stdout.write(self.style.ERROR('Command {}:{} failed with error: {}' - .format(scheduler.command, scheduler.id, err)), ending='\n') + self.stdout.write( + self.style.ERROR( + 'Command {}:{} failed with error: {}' .format( + scheduler.command, + scheduler.id, + err)), + ending='\n') scheduler.success = False scheduler.last_error = err scheduler.save() @@ -71,10 +75,10 @@ def process_items(self, options): self.stdout.write(self.style.SUCCESS('No scheduled send_irc_msg tasks'), ending='\n') else: self.stdout.write(self.style.SUCCESS('Sending {} scheduled irc message(s)' - .format(len(irc_schedulers))), ending='\n') + .format(len(irc_schedulers))), ending='\n') commands.send_irc_msgs(irc_schedulers) self.stdout.write(self.style.SUCCESS('Sent {} irc message(s)' - .format(len(irc_schedulers))), ending='\n') + .format(len(irc_schedulers))), ending='\n') # generic handlers schedulers = Scheduler.objects.filter(success=None) diff --git a/gsoc/models.py b/gsoc/models.py index 6c1d8ed8..04ef4c6d 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -3,7 +3,7 @@ import datetime import uuid -from django.contrib.auth.models import Group, Permission +from django.contrib.auth.models import Permission from django.contrib import auth from django.db import models from django.contrib.auth.models import User @@ -60,7 +60,7 @@ class UserProfile(models.Model): (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student') - ) + ) user = models.ForeignKey(User, on_delete=models.CASCADE) role = models.IntegerField(name='role', choices=ROLES, default=0) @@ -78,10 +78,10 @@ class UserProfile(models.Model): def has_proposal(self): try: - self.userprofile_set.get(role=3).accepted_proposal_pdf.path - return True + if self.userprofile_set.get(role=3).accepted_proposal_pdf.path: + return True - except: + except BaseException: return False @@ -109,6 +109,8 @@ def student_profile(self): auth.models.User.add_to_class('student_profile', student_profile) # Auto Delete Redundant Proposal + + @receiver(models.signals.post_delete, sender=UserProfile) def auto_delete_proposal_on_delete(sender, instance, **kwargs): """ @@ -219,7 +221,7 @@ def find_all_emails(self, text): try: validate_email(email) real_emails.append(email) - except: + except BaseException: pass return real_emails @@ -230,7 +232,7 @@ def find_all_possible_phone_numbers(self, text): matcher = PhoneNumberMatcher(text, 'US') all_numbers = list(iter(matcher)) all_number_strings = [x.raw_string for x in all_numbers] - ptn = re.compile(r'\+?[0-9][0-9\(\)\-\ ]{3,}[0-9]', re.A| re.M) + ptn = re.compile(r'\+?[0-9][0-9\(\)\-\ ]{3,}[0-9]', re.A | re.M) maybe_numbers = re.findall(ptn, text) for maybe_number in maybe_numbers: if maybe_number in all_number_strings: @@ -239,7 +241,7 @@ def find_all_possible_phone_numbers(self, text): maybe_number_parsed = phonenumbers.parse(maybe_number) if phonenumbers.is_possible_number(maybe_number_parsed): all_number_strings.append(maybe_number) - except: + except BaseException: pass return all_number_strings @@ -252,9 +254,9 @@ def validate(self, text): locations = self.find_all_locations(text) if any((emails, possible_phone_numbers, locations)): message = { - "emails": emails, - "possible_phone_numbers": possible_phone_numbers, - "locations": locations, + "emails": emails, + "possible_phone_numbers": possible_phone_numbers, + "locations": locations, } raise ValidationError(message=message) @@ -295,7 +297,7 @@ class RegLink(models.Model): user_suborg = models.ForeignKey(SubOrg, name="user_suborg", on_delete=models.CASCADE, null=True, blank=False) user_gsoc_year = models.ForeignKey(GsocYear, name="user_gsoc_year", - on_delete=models.CASCADE, null=True, blank=False) + on_delete=models.CASCADE, null=True, blank=False) adduserlog = models.ForeignKey(AddUserLog, on_delete=models.CASCADE, null=True, blank=True, related_name='reglinks') email = models.CharField(null=False, blank=False, diff --git a/gsoc/router.py b/gsoc/router.py index f8ebaa98..5d4468da 100644 --- a/gsoc/router.py +++ b/gsoc/router.py @@ -1,16 +1,11 @@ -from django.conf import settings -from cms.models import PageUser -from cms.models import PageUserGroup +class DatabaseAppsRouter(): - -class DatabaseAppsRouter(object): - """ def db_for_read(self, model, **hints): if model == PageUser: return 'auth_db' if model == PageUserGroup: - return 'default' + return 'default' elif model._meta.app_label in settings.DATABASE_APPS_MAPPING: return settings.DATABASE_APPS_MAPPING[model._meta.app_label] return None @@ -55,4 +50,4 @@ def allow_migrate(self, db, app_label, model_name=None, **hints): elif app_label in settings.DATABASE_APPS_MAPPING: return False return None -""" \ No newline at end of file +""" diff --git a/gsoc/settings.py b/gsoc/settings.py index 650a9da0..0ccd6dbf 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -11,9 +11,7 @@ """ import logging.config -import django.utils.timezone as tz import os -import datetime try: from settings_local import * except ImportError: @@ -21,6 +19,8 @@ gettext = lambda s: s + + DATA_DIR = os.path.dirname(os.path.dirname(__file__)) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -73,7 +73,7 @@ 'DIRS': [ os.path.join(BASE_DIR, 'gsoc', 'templates'), os.path.join(BASE_DIR, 'blogs_list', 'templates'), - ], + ], 'OPTIONS': { 'context_processors': [ 'django.contrib.auth.context_processors.auth', @@ -87,13 +87,13 @@ 'sekizai.context_processors.sekizai', 'django.template.context_processors.static', 'cms.context_processors.cms_settings' - ], + ], 'loaders': [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', - ], + ], + }, }, - }, ] MIDDLEWARE = ( @@ -162,12 +162,12 @@ ) LANGUAGES = ( - ## Customize this + # Customize this ('en', gettext('en')), ) CMS_LANGUAGES = { - ## Customize this + # Customize this 1: [ { 'code': 'en', @@ -175,17 +175,17 @@ 'redirect_on_fallback': True, 'public': True, 'hide_untranslated': False, - }, - ], + }, + ], 'default': { 'redirect_on_fallback': True, 'public': True, 'hide_untranslated': False, - }, + }, } CMS_TEMPLATES = ( - ## Customize this + # Customize this ('fullwidth.html', 'Fullwidth'), ('sidebar_left.html', 'Sidebar Left'), ('sidebar_right.html', 'Sidebar Right'), @@ -221,16 +221,16 @@ AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, + }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, + }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, + }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + }, ] LOGIN_REDIRECT_URL = '/after-login/' @@ -259,22 +259,22 @@ 'format': ('%(levelname)s %(asctime)s %(process)d ' '%(thread)d %(filename)s %(module)s %(funcName)s ' '%(lineno)d %(message)s') - }, + }, 'simple': { 'format': '%(levelname)s: %(message)s' + }, }, - }, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' - } - }, + } + }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'verbose' - }, + }, 'file': { 'level': 'DEBUG', 'class': 'logging.handlers.TimedRotatingFileHandler', @@ -283,7 +283,7 @@ 'when': 'midnight', 'backupCount': 60, 'encoding': 'utf-8', - }, + }, 'access_logs': { 'level': 'INFO', 'class': 'logging.handlers.TimedRotatingFileHandler', @@ -292,38 +292,38 @@ 'when': 'midnight', 'backupCount': 7, 'encoding': 'utf-8', - }, + }, 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler' + }, }, - }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'INFO', 'propagate': True, - }, + }, 'django.server': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, - }, + }, 'django.db': { 'handlers': ['file'], 'level': 'WARNING', 'propagate': False, - }, + }, 'django.security.DisallowedHost': { 'handlers': ['file'], 'propagate': False, - }, + }, # Catch All Logger -- Captures any other logging '': { 'handlers': ERROR_HANDLERS, 'level': ERROR_LEVEL, + } } - } } logging.config.dictConfig(LOGGING) @@ -342,7 +342,8 @@ 'language': '{{ language }}', 'extraPlugins': 'button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube', 'toolbar': [ - {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']}, + {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', + 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']}, {'name': 'editing', 'items': ['Find', 'Replace', '-', 'SelectAll', '-', 'Scayt']}, # ['cmsplugins', 'cmswidget'], {'name': 'settings', 'items': ['Source', 'ShowBlocks', 'Maximize']}, @@ -354,11 +355,12 @@ 'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']}, {'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']}, - {'name': 'insert', 'items': ['Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', 'Image', 'Youtube']}, + {'name': 'insert', 'items': ['Table', 'HorizontalRule', + 'Smiley', 'SpecialChar', 'PageBreak', 'Image', 'Youtube']}, '/', {'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']}, {'name': 'colors', 'items': ['TextColor', 'BGColor']}, - ], + ], 'toolbarCanCollapse': False, } diff --git a/gsoc/templatetags/app_tag.py b/gsoc/templatetags/app_tag.py index bad10f17..93fd38f1 100644 --- a/gsoc/templatetags/app_tag.py +++ b/gsoc/templatetags/app_tag.py @@ -2,42 +2,40 @@ import datetime import django.utils.timezone as tz import pytz -from dateutil.relativedelta import * -from django.shortcuts import render register = template.Library() """ This function makes use of the django timezone library to get the user's local time and pytz for getting utc time. - Now, we use the common_timezones library in pytz to get - timezone of various places across the globe. - + Now, we use the common_timezones library in pytz to get + timezone of various places across the globe. + We calculate the difference between Utctime and localtime and compare - the difference of utctime with those global timezones with - previously computed difference and accordingly, set the + the difference of utctime with those global timezones with + previously computed difference and accordingly, set the timezone of the user. """ + + @register.simple_tag(takes_context=True) -def time_zone(context,flag=0): +def time_zone(context, flag=0): gmtTime = "+00:00" localTime = tz.now() utcTimeZone = pytz.timezone('UTC') utcTime = datetime.datetime.now(tz=utcTimeZone) all_timezones = pytz.common_timezones - localDate = datetime.datetime.strftime(localTime.date(), '%d') - utcDate = datetime.datetime.strftime(utcTime.date(), '%d') TIME_ZONE = "UTC" - for i in all_timezones : + for i in all_timezones: timeZone = pytz.timezone(i) timeFromUTC = str(datetime.datetime.now(tz=timeZone))[-6:] time = datetime.datetime.now(tz=timeZone) - if(time.hour == localTime.hour) : - if(abs(time.minute-localTime.minute) <= 1 ) : + if(time.hour == localTime.hour): + if(abs(time.minute - localTime.minute) <= 1): TIME_ZONE = i gmtTime = timeFromUTC break - if flag : + if flag: return gmtTime - else : - return TIME_ZONE \ No newline at end of file + else: + return TIME_ZONE diff --git a/gsoc/urls.py b/gsoc/urls.py index e4afe7c1..b0aaade3 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -6,11 +6,10 @@ from django.conf.urls import include, url from django.conf.urls.i18n import i18n_patterns from django.contrib import admin -from django.contrib.auth import views as auth_views from django.contrib.sitemaps.views import sitemap from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views.static import serve -from django.urls import path,include +from django.urls import path from django.views.generic import TemplateView from django.views.generic.base import RedirectView @@ -22,13 +21,13 @@ url(r'^sitemap\.xml$', sitemap, {'sitemaps': { 'cmspages': CMSSitemap, - } - }), + } + }), url(r'^robots.txt$', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots_file"), url(r'^favicon.ico$', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), ] -#Add Django site authentication urls (for login, logout, password management) +# Add Django site authentication urls (for login, logout, password management) urlpatterns += [ url('accounts/', include('django.contrib.auth.urls')), url('accounts/register', gsoc.views.register_view, name='register') @@ -45,7 +44,7 @@ # For django versions before 2.0: url(r'^__debug__/', include(debug_toolbar.urls)), - ] + urlpatterns + ] + urlpatterns urlpatterns = [ url(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), diff --git a/gsoc/views.py b/gsoc/views.py index 5fac6cee..c17270c4 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -2,10 +2,9 @@ from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User from .forms import ProposalUploadForm -from .models import validate_proposal_text, RegLink, SubOrg, UserProfile, GsocYear, Scheduler +from .models import RegLink, validate_proposal_text from django import shortcuts -from django.http import JsonResponse, HttpResponseForbidden -from django.core.validators import validate_email +from django.http import JsonResponse from django.core.exceptions import ValidationError from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter @@ -33,21 +32,26 @@ def convert_pdf_to_txt(f): retstr.close() return text + def is_user_accepted_student(user): return user.is_current_year_student() + + def scan_proposal(file): """ NOTE: returns True if not found private data. """ try: text = convert_pdf_to_txt(file) - except: + except BaseException: text = '' try: validate_proposal_text(text) return None except ValidationError as err: return err + + @decorators.login_required def after_login_view(request): user = request.user @@ -55,6 +59,7 @@ def after_login_view(request): return shortcuts.redirect('/myprofile') return shortcuts.redirect('/') + @decorators.login_required @decorators.user_passes_test(is_user_accepted_student) def upload_proposal_view(request): @@ -63,10 +68,10 @@ def upload_proposal_view(request): "emails": [], "possible_phone_numbers": [], "locations": [], - }, + }, 'file_type_valid': False, 'file_not_too_large': False, - } + } if request.method == 'POST': file = request.FILES.get('accepted_proposal_pdf') resp['file_type_valid'] = file and file.name.endswith('.pdf') @@ -81,6 +86,7 @@ def upload_proposal_view(request): resp['private_data'] = scan_result.message_dict return JsonResponse(resp) + @decorators.login_required @decorators.user_passes_test(is_user_accepted_student) def cancel_proposal_upload_view(request): @@ -102,7 +108,7 @@ def register_view(request): 'done_registeration': False, 'warning': '', 'reglink_id': reglink_id, - } + } if reglink_usable is False or request.method == 'GET': if reglink_usable is False: context['can_register'] = False @@ -152,7 +158,7 @@ def register_view(request): reglink.save() context['done_registeration'] = True context['warning'] = '' - return shortcuts.render(request, 'registration/register.html', context) else: context['done_registeration'] = False - return shortcuts.render(request, 'registration/register.html', context) + + return shortcuts.render(request, 'registration/register.html', context) diff --git a/manage.py b/manage.py index 5e2665f9..db0d00f1 100644 --- a/manage.py +++ b/manage.py @@ -11,5 +11,5 @@ "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" - ) from exc + ) from exc execute_from_command_line(sys.argv) From 03ff3225a502f35739a40fe0fa762815b4026bd8 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 29 Apr 2019 12:39:48 +0530 Subject: [PATCH 0104/1137] Migrate db --- project.db | Bin 1335296 -> 1339392 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index ba7e3799a7513b0425f58c8a5713893cc5600b6b..9508bc2edc40a3b48ed82a14edf0246bfc98bbad 100644 GIT binary patch delta 664 zcmZXRO=uHA7=~x|C%ct^HlWmEqRBE*bglcmuf}}y)B1%)5^wLv; zWlJ%XN|BMCiU;xFp|X(0gAyA&iTDErK|=Oa)PwZm!Ah-DuqQuwzTusLk9ps(T+f!> z?DFA2Hvlk0Q2}iLh%*nOCf^P86XoZ(9(Tg66(Mv(k(Yd{C@t-Je@a`@mK_)?Nx`;? z!N%ZZv2ZgtTO6O6o+(V+zB|s?yO9h7ZF?yC8p4w+fw8W8VSOR2LkdG-DIA5TcqoDs z*2T}++M39P9ImeJA{%b+#VuE~T*-31mMdGX&vN~itJGZNaUIXPw1a9NRFCR-Li`Tu zpg;dt5K$9;D(+Jj?ZEa|^kjflEj3V6HE#W=kNE=RJvrr_l^%-A;;@kAySO6zgPouc zljK-k8{z<9O4Q*r!&QW8FUh|^XBUDE%?UQ}ci(CLGEl)N@K4FM?DbxhR>j}UOL0_q zC3JgMz$pKRzm77>8ue{(^ZDv9nX7A224ArZS$1c37Y$Pbu|yjdL3`-U+RV)*4mY+Y2nCMf ziXfthp7Kd5=q1P+c<~`J_E7XB!Zsnu3*kdOgfBkyN9rOX9rzqL|GscIXTk_itcPc7 zT?$2!E7XzkBXcc5t3s(YA796w)|35t&U;IeT=p5qlH4Ke+xBdG1y}T*;IYk7o&cZ7 zrUoOKcp^DGIuePblCi?RKSYx^h2EC;#3u;@$yjE5G?I>2S`8PSQMc zu3*{yQ?D7kW(b;L(+s<2I5b1l3~AMnSwm-+`QNfmHk?-H3wA|t>g)vnt6p=uR-JCk zN}WiJQTD&~Hd{_u;Ai>kT-e%R$ud8fA^I~lhQ4)oFSP%^G3k6XFnt00H=qT(${^@- z8}NYO!X})?V|`-nVhNnMd|WWy4N<~NLCKHzH=)|<&vVnQIIt$SPkM@O-BX0`j#JDl zN+wN|JR*INBr)OWv&{%QLLHyu%A|=4vQJp6b@Z7lO7ypzFh$$V%e~_~;Q*cQ!qy4| zxq}0c9=u!zz?CiFF;szST&jTEcTn?r6<>DnZakJt#xs%On9om9%oOLjX=;RUw;4QW zRG{uqsPRZhMDG@y#IZ9V$=TeUR3>paOz+^gW*FD!%kY!47@H z(t+2u;VMc2GZ9eX1U^>bJZZ<a523XQ8 Rk&BdEG@ouw#$u$Z^B Date: Mon, 29 Apr 2019 12:47:34 +0530 Subject: [PATCH 0105/1137] Fix indentation, trailing whitespace --- gsoc/admin.py | 4 ++-- gsoc/management/commands/runcron.py | 6 ++++-- gsoc/settings.py | 2 +- gsoc/templatetags/app_tag.py | 16 ++++++++-------- gsoc/views.py | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 9d85911d..a2780f00 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -196,7 +196,7 @@ def Article_get_queryset(self, request): for profile in userprofiles: app_configs.append(profile.app_config) qs = qs.filter(app_config__in=app_configs) - + return qs @@ -245,7 +245,7 @@ def get_readonly_fields(self, request, obj=None): "user_gsoc_year", 'email', ) - + return readonly_fields diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index d16d0cd6..d13c5135 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -51,8 +51,10 @@ def handle_process(self, scheduler): .format(scheduler.command, scheduler.id), ending='\n') err = getattr(commands, scheduler.command)(scheduler) if not err: - self.stdout.write(self.style.SUCCESS('Finished command {}:{}' - .format(scheduler.command, scheduler.id)), ending='\n') + self.stdout.write(self.style + .SUCCESS('Finished command {}:{}' + .format(scheduler.command, scheduler.id)), + ending='\n') scheduler.success = True scheduler.save() diff --git a/gsoc/settings.py b/gsoc/settings.py index 0ccd6dbf..3a0cb10e 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -18,7 +18,7 @@ raise Exception('Missing settings_local.py. Did you create it from the template?') -gettext = lambda s: s +def gettext(s): return s DATA_DIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/gsoc/templatetags/app_tag.py b/gsoc/templatetags/app_tag.py index 93fd38f1..8a57371a 100644 --- a/gsoc/templatetags/app_tag.py +++ b/gsoc/templatetags/app_tag.py @@ -6,15 +6,15 @@ register = template.Library() """ - This function makes use of the django timezone library to - get the user's local time and pytz for getting utc time. - Now, we use the common_timezones library in pytz to get - timezone of various places across the globe. + This function makes use of the django timezone library to + get the user's local time and pytz for getting utc time. + Now, we use the common_timezones library in pytz to get + timezone of various places across the globe. - We calculate the difference between Utctime and localtime and compare - the difference of utctime with those global timezones with - previously computed difference and accordingly, set the - timezone of the user. + We calculate the difference between Utctime and localtime and compare + the difference of utctime with those global timezones with + previously computed difference and accordingly, set the + timezone of the user. """ diff --git a/gsoc/views.py b/gsoc/views.py index c17270c4..99d6a390 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -160,5 +160,5 @@ def register_view(request): context['warning'] = '' else: context['done_registeration'] = False - + return shortcuts.render(request, 'registration/register.html', context) From 338975895558c0c4952afcedd44fccf0cb6ba2b5 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Mon, 29 Apr 2019 18:46:15 +0800 Subject: [PATCH 0106/1137] fix registration post link and show email address on register info errors --- gsoc/templates/registration/register.html | 4 +++- gsoc/views.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 1c6dd3f0..4c4856d4 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -7,7 +7,9 @@

    {{ warning| safe }}

    {% endif %} {% if can_register and not done_registeration %}

    Sign up for a PSF GSoC account!


    -
    + {% csrf_token %} diff --git a/gsoc/views.py b/gsoc/views.py index 99d6a390..d5a655c8 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -108,13 +108,12 @@ def register_view(request): 'done_registeration': False, 'warning': '', 'reglink_id': reglink_id, + 'email': getattr(reglink, 'email', 'EMPTY') } if reglink_usable is False or request.method == 'GET': if reglink_usable is False: context['can_register'] = False context['warning'] = 'Your registeration link is invalid! Please check again!' - else: - context['email'] = reglink.email return shortcuts.render(request, 'registration/register.html', context) if request.method == 'POST': username = request.POST.get('username', '') From ac3b889823af594a59e22e5048c1643c72ebd0c8 Mon Sep 17 00:00:00 2001 From: ntkomata Date: Mon, 29 Apr 2019 18:46:42 +0800 Subject: [PATCH 0107/1137] fix typo:registeration->registration --- gsoc/templates/registration/register.html | 4 ++-- gsoc/views.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 4c4856d4..34ad47b3 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -5,7 +5,7 @@ {% if warning %}

    {{ warning| safe }}

    {% endif %} - {% if can_register and not done_registeration %} + {% if can_register and not done_registration %}

    Sign up for a PSF GSoC account!


    Sign up for a PSF GSoC account!


    {% if not can_register %}

    Sorry, but you can't register using this link!

    {% endif %} - {% if done_registeration %} + {% if done_registration %}

    Thank you for registering! You can login now!

    {% endif %} diff --git a/gsoc/views.py b/gsoc/views.py index d5a655c8..31e3649f 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -105,7 +105,7 @@ def register_view(request): reglink = None context = { 'can_register': True, - 'done_registeration': False, + 'done_registration': False, 'warning': '', 'reglink_id': reglink_id, 'email': getattr(reglink, 'email', 'EMPTY') @@ -113,14 +113,14 @@ def register_view(request): if reglink_usable is False or request.method == 'GET': if reglink_usable is False: context['can_register'] = False - context['warning'] = 'Your registeration link is invalid! Please check again!' + context['warning'] = 'Your registration link is invalid! Please check again!' return shortcuts.render(request, 'registration/register.html', context) if request.method == 'POST': username = request.POST.get('username', '') password = request.POST.get('password', '') password2 = request.POST.get('password2', '') info_valid = True - registeration_success = True + registration_success = True if password != password2: context['warning'] += 'Your password didn\'t match!
    ' info_valid = False @@ -151,13 +151,13 @@ def register_view(request): user = None if user is None: - registeration_success = False - if registeration_success: + registration_success = False + if registration_success: reglink.is_used = True reglink.save() - context['done_registeration'] = True + context['done_registration'] = True context['warning'] = '' else: - context['done_registeration'] = False + context['done_registration'] = False return shortcuts.render(request, 'registration/register.html', context) From 1b56f3efd420b45f010c4742efc82e922b09562d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 2 May 2019 09:51:08 +0530 Subject: [PATCH 0108/1137] Move js to separate files --- gsoc/static/js/proposal.js | 121 ++++++++++++++++++++++++++++++++ gsoc/templates/myprofile.html | 126 +--------------------------------- 2 files changed, 122 insertions(+), 125 deletions(-) create mode 100644 gsoc/static/js/proposal.js diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js new file mode 100644 index 00000000..f0637dc3 --- /dev/null +++ b/gsoc/static/js/proposal.js @@ -0,0 +1,121 @@ +function cancelProposalUpload() { + axios.get('/cancel_proposal_upload/').then( + function(resp) { + inPageInfo("Proposal upload canceled."); + setProposalUploadingStatus(false); + } + ) +} +function setProposalUploadingStatus(status) { + const button = document.querySelector("#upload-proposal-button"); + if(status) { + button.innerHTML = 'Uploading...Please wait.'; + button.onclick = null; + } else { + button.innerHTML = 'Confirm'; + button.onclick = beforeUpload; + } +} +function inPageInfo(text, isAlert=true) { + const boxTag = document.querySelector('#infoBox'); + const boxText = document.querySelector('#infoBoxText'); + boxText.innerHTML = text; + if(isAlert)boxTag.className = 'alert'; + else boxTag.className = 'info'; + + boxTag.style.display = 'inline-block'; +} +document.getElementById('proposalFileInput').onchange = function(){ + hideInfoBox(); + const fileInput = document.getElementById('proposalFileInput'); + const file = fileInput.files[0]; + const fileSizeTooLarge = file && file.size > 20 * 1024 * 1024; + const fileWrongFormat = file && !file.name.endsWith(".pdf"); + if (fileSizeTooLarge){ + inPageInfo(`Sorry, your proposal file is too large
    +Please keep the file smaller than 20MB.`); + fileInput.value = ''; + } + if (fileWrongFormat){ + inPageInfo(`Sorry, the file you just chose doesn't seem to be a pdf file.`); + fileInput.value = ''; + } + +}; +function hideInfoBox() { + const boxTag = document.querySelector('#infoBox'); + boxTag.style.display = 'none'; +} +function beforeUpload() { + const offlineCancel = function(){ + inPageInfo("Proposal upload canceled.") + }; + const infoText = 'Please make sure there is no private data in your pdf file. Confirm?' + inPageInfo(infoText, false); + showInfoBoxBtns(uploadProposal, offlineCancel) +} +function hideInfoBoxBtns() { + const btn1 = document.querySelector('#infoBtn1'); + const btn2 = document.querySelector('#infoBtn2'); + btn1.onclick = null; + btn2.onclick = null; + btn1.style.display = 'none'; + btn2.style.display = 'none'; +} +function showInfoBoxBtns(callback1, callback2) { + const btn1 = document.querySelector('#infoBtn1'); + const btn2 = document.querySelector('#infoBtn2'); + btn1.onclick = function(){hideInfoBoxBtns(); hideInfoBox(); callback1()}; + btn2.onclick = function(){hideInfoBoxBtns(); hideInfoBox(); callback2()}; + btn1.style.display = 'inline'; + btn2.style.display = 'inline'; +} +function onFindPrivateData(text) { + const successCallback = function() { + inPageInfo("Upload succeeded! Please refresh to get rid of the GIANT banner!",false); + setProposalUploadingStatus(false); + }; + showInfoBoxBtns(successCallback, cancelProposalUpload); + inPageInfo(text, true); +} +function uploadProposal() { + hideInfoBoxBtns(); + setProposalUploadingStatus(true); + const uploadForm = document.querySelector('#upload-proposal-form'); + axios.post( + '/upload-proposal/', + new FormData(uploadForm), + ) + .then(function(resp) { + if(!resp.data['file_type_valid']) { + inPageInfo("Your file doesn't seem to be a pdf file. Please check again!"); + setProposalUploadingStatus(false); + return + } + if(!resp.data['file_not_too_large']) { + inPageInfo("Your file is larger than 20MB. Please make it smaller!"); + setProposalUploadingStatus(false); + return + } + const privateData = resp.data['private_data']; + if(privateData["emails"].length > 0 || + privateData["possible_phone_numbers"].length > 0 || + privateData["locations"].length > 0) { + let confirmText = "We seemed to have found these private data in your pdf file. Are you sure to proceed?"; + if (privateData['emails'].length > 0) + confirmText += `
    Email addresses: ${privateData['emails'].toString()}` + if(privateData['possible_phone_numbers'].length > 0) + confirmText += `
    Possible phone numbers: ${privateData['possible_phone_numbers'].toString()}` + if(privateData['locations'].length > 0) + confirmText += `
    Locations: ${privateData['locations'].toString()}` + onFindPrivateData(confirmText); + return + } + setProposalUploadingStatus(false); + inPageInfo('Proposal upload succeeded! Please refresh to get rid of the GIANT banner!', false); + }) + .catch(function(err) { + setProposalUploadingStatus(false); + console.log(err); + }) +} \ No newline at end of file diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index cd7a55ae..39ff673b 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -72,131 +72,7 @@

    Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!

    - - + {% endif %} {% endif %}
    From 5a994c18667dd892ed92efbc95cd81d8420dea6b Mon Sep 17 00:00:00 2001 From: karna98 Date: Fri, 3 May 2019 03:23:26 +0530 Subject: [PATCH 0109/1137] Improved UI --- gsoc/static/css/python-gsoc.css | 8 ++- gsoc/templates/registration/login.html | 62 ++++++++++--------- .../registration/password_reset_complete.html | 6 +- .../registration/password_reset_done.html | 10 +-- .../registration/password_reset_form.html | 30 +++++---- 5 files changed, 69 insertions(+), 47 deletions(-) diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index 1d8dcce2..24c49fde 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -251,4 +251,10 @@ div.problem { .card .no-proposal { font-size: 12px; color: red; -} \ No newline at end of file +} + +.center { + display: flex; + justify-content: center; + align-items: center; + } \ No newline at end of file diff --git a/gsoc/templates/registration/login.html b/gsoc/templates/registration/login.html index 9224c396..380c8faf 100644 --- a/gsoc/templates/registration/login.html +++ b/gsoc/templates/registration/login.html @@ -3,43 +3,47 @@ {% block content %} {% if form.errors %} -

    Your username and password didn't match. Please try again.

    +

    Your username and password didn't match. Please try again.

    {% endif %} {% if next %} - {% if user.is_authenticated %} -

    Your account doesn't have access to this page. To proceed, - please login with an account that has access.

    - {% else %} -

    Please login to see this page.

    - {% endif %} + {% if user.is_authenticated %} +

    Your account doesn't have access to this page. To proceed, + please login with an account that has access.

    + {% else %} +

    Please login to see this page.

    + {% endif %} {% endif %} {% if user.is_authenticated %} -

    You are already logged in!

    -Logout +

    You are already logged in!

    + Logout {% else %} - -{% csrf_token %} -

    Login

    -
    - {{ form.username.label_tag }} - {{ form.username }} -
    -
    - {{ form.password.label_tag }} - {{ form.password }} -
    - -
    - - -
    - - -{# Assumes you setup the password_reset view in your URLconf #} -

    Lost password?

    +
    +
    + {% csrf_token %} +
    +

    Login

    +
    + {{ form.username.label_tag }} + {{ form.username }} +
    + +
    + {{ form.password.label_tag }} + {{ form.password }} +
    + +
    + + + {# Assumes you setup the password_reset view in your URLconf #} + Lost password? +
    +
    +
    +
    {% endif %} diff --git a/gsoc/templates/registration/password_reset_complete.html b/gsoc/templates/registration/password_reset_complete.html index a4737741..85c495df 100644 --- a/gsoc/templates/registration/password_reset_complete.html +++ b/gsoc/templates/registration/password_reset_complete.html @@ -1,6 +1,8 @@ {% extends "base.html" %} {% block content %} -

    The password has been changed!

    -

    log in again?

    +
    +

    The password has been changed!

    +

    Log in again?

    +
    {% endblock %} diff --git a/gsoc/templates/registration/password_reset_done.html b/gsoc/templates/registration/password_reset_done.html index 742aa170..84b17fb1 100644 --- a/gsoc/templates/registration/password_reset_done.html +++ b/gsoc/templates/registration/password_reset_done.html @@ -3,8 +3,10 @@ {% block title %} Email Sent {% endblock %} {% block content %} -
    -

    Check your inbox

    -

    We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.

    -
    +
    +
    +

    Check your inbox!!

    +

    We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.

    +
    +
    {% endblock %} diff --git a/gsoc/templates/registration/password_reset_form.html b/gsoc/templates/registration/password_reset_form.html index d61a4010..7f2b1c57 100644 --- a/gsoc/templates/registration/password_reset_form.html +++ b/gsoc/templates/registration/password_reset_form.html @@ -3,16 +3,24 @@ {% block title %} Forgot your Password? {% endblock %} {% block content %} -

    Forgot your password?

    -

    Enter your email address below, and we'll email instructions for setting a new one.

    +
    +
    + {% csrf_token %} +
    +

    Forgot your password?

    +

    Enter your email address below, and we'll email instructions for setting a new one.

    + +
    + {% if form.email.errors %} + {{ form.email.errors }} + {% endif %} + {{ form.as_p }} +
    +
    + +
    +
    +
    +
    -
    - {% csrf_token %} - {% if form.email.errors %} - {{ form.email.errors }} - {% endif %} - {{ form.as_p }} - - -
    {% endblock %} From 63fb850323943de7030e6d1c41908e76a1598b4f Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 4 May 2019 14:07:26 +0800 Subject: [PATCH 0110/1137] fix proposal MultipleObjectsReturned errors and use django timezone in year validation --- gsoc/models.py | 18 +++++++----------- project.db | Bin 1339392 -> 1339392 bytes 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 04ef4c6d..6ff049ee 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -78,30 +78,26 @@ class UserProfile(models.Model): def has_proposal(self): try: - if self.userprofile_set.get(role=3).accepted_proposal_pdf.path: - return True - + proposal = self.student_profile().accepted_proposal_pdf + return proposal is not None and proposal.path except BaseException: return False def is_current_year_student(self): try: - profile = self.userprofile_set.get(role=3) + profile = self.student_profile() + if not profile: + return False year = profile.gsoc_year.gsoc_year - current_year = datetime.datetime.now().year + current_year = timezone.now().year return current_year == year except UserProfile.DoesNotExist: return False def student_profile(self): - try: - # TODO: will raise MultipleObjectsReturned when there are more than - # one student profiles, fix this - return self.userprofile_set.get(role=3) - except UserProfile.DoesNotExist: - return None + return self.userprofile_set.filter(role=3).first() auth.models.User.add_to_class('has_proposal', has_proposal) diff --git a/project.db b/project.db index 9508bc2edc40a3b48ed82a14edf0246bfc98bbad..4a843e9149da7817319dfb20415c6199ab39adea 100644 GIT binary patch delta 1341 zcmai!Urbw79LMkJxuq>|&*54!&0M5(7{#phw)ft4E!#ttjLq0f#!71;SW5q}-qJr% zI!h;U)!98PZj2`0B_=|Q*b{h~=5rf2NVrpVdm9g8JPwq5BT%&LeKfxOW zaUZkw99Y+aDjEWviYbE{Y$IXo6kgv6Tg8n65o`qFMj);S{`GRPsx4M^#cFf0+ET2x z7OPCLioU91x=IiaDZGgv;zwn+_8V3=lq!#YXhS>rCq$rL#SUJjf|ZT+Hk>AzISQ}f zn>a_l%2;FSR8y?Zul$r^51YeN!RYi0hWd&-k_;s>0p4sgbAd!8nGGaU;YcPBNd_#= zbarea7E)GHtZ=Y}xr3rkCgy?Kf~9tWh^g>?o&5Y4#ZTQ()+t>zOy- z?P1jJi)xn~i>2jhS(u)VMQvjzEh%l z@%y+Bzle1>iXNb4bc?XAppNID3;cIM%%$Zeme^C3zxPe;Om z^i(RH$^<6@>2P#chaKG3@$IK$;mm@Sb6D;B=m1`Mu%+)bWj)(~j=ccEDLM$iD)74i zgmQErK zoex1X54<4KJp{gqG4b96>m}IB9_pd!;S`^Gn$)BH`AJ{+!1oW|{|jbp!Hlh*N6oJ; zbXuF4mZpXGBdgdf%jYGRVLSAY`y44D}Fh znV~YzYR=7z+Ov%WrdF#a3`rWqUgwegZmIL^H-x5luTZaY)pw?pkcPw$TZq(Ui{Zpe@>S*P&MnHoTJFM-&T% zrJUJTN{x};=Qa+uuxND}IKx|m?3K2=h}5U z!!6Y`j-W>~LVI|8RLMThM7yGoBI>}{?fA`HLQMptx!&1icWgG&buSQA z`^FOSSS}eKRU=YzGC lrGpjml+IOM9W>X2jj3NQZOmqYLk3RZkovxWemHr~{SOOr$jSf! From b4c76452a760841cd3d0d46599601feab77d37df Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sat, 4 May 2019 14:08:35 +0800 Subject: [PATCH 0111/1137] restore db file --- project.db | Bin 1339392 -> 1339392 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 4a843e9149da7817319dfb20415c6199ab39adea..9508bc2edc40a3b48ed82a14edf0246bfc98bbad 100644 GIT binary patch delta 630 zcmXAmUq};i9Ki3l`_0=;_xtW#h)}Sj44O3CZky)~iA2Lcw8d~(n=iU-*_k>gmQErK zoex1X54<4KJp{gqG4b96>m}IB9_pd!;S`^Gn$)BH`AJ{+!1oW|{|jbp!Hlh*N6oJ; zbXuF4mZpXGBdgdf%jYGRVLSAY`y44D}Fh znV~YzYR=7z+Ov%WrdF#a3`rWqUgwegZmIL^H-x5luTZaY)pw?pkcPw$TZq(Ui{Zpe@>S*P&MnHoTJFM-&T% zrJUJTN{x};=Qa+uuxND}IKx|m?3K2=h}5U z!!6Y`j-W>~LVI|8RLMThM7yGoBI>}{?fA`HLQMptx!&1icWgG&buSQA z`^FOSSS}eKRU=YzGC lrGpjml+IOM9W>X2jj3NQZOmqYLk3RZkovxWemHr~{SOOr$jSf! delta 1341 zcmai!Urbw79LMkJxuq>|&*54!&0M5(7{#phw)ft4E!#ttjLq0f#!71;SW5q}-qJr% zI!h;U)!98PZj2`0B_=|Q*b{h~=5rf2NVrpVdm9g8JPwq5BT%&LeKfxOW zaUZkw99Y+aDjEWviYbE{Y$IXo6kgv6Tg8n65o`qFMj);S{`GRPsx4M^#cFf0+ET2x z7OPCLioU91x=IiaDZGgv;zwn+_8V3=lq!#YXhS>rCq$rL#SUJjf|ZT+Hk>AzISQ}f zn>a_l%2;FSR8y?Zul$r^51YeN!RYi0hWd&-k_;s>0p4sgbAd!8nGGaU;YcPBNd_#= zbarea7E)GHtZ=Y}xr3rkCgy?Kf~9tWh^g>?o&5Y4#ZTQ()+t>zOy- z?P1jJi)xn~i>2jhS(u)VMQvjzEh%l z@%y+Bzle1>iXNb4bc?XAppNID3;cIM%%$Zeme^C3zxPe;Om z^i(RH$^<6@>2P#chaKG3@$IK$;mm@Sb6D;B=m1`Mu%+)bWj)(~j=ccEDLM$iD)74i z Date: Sun, 5 May 2019 11:43:14 +0800 Subject: [PATCH 0112/1137] add year parameter to student_profile() --- gsoc/models.py | 8 ++++++-- project.db | Bin 1339392 -> 0 bytes 2 files changed, 6 insertions(+), 2 deletions(-) delete mode 100644 project.db diff --git a/gsoc/models.py b/gsoc/models.py index 6ff049ee..5b208365 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -96,8 +96,12 @@ def is_current_year_student(self): return False -def student_profile(self): - return self.userprofile_set.filter(role=3).first() +def student_profile(self, year=timezone.now().year): + gsoc_year = GsocYear.objects.filter(gsoc_year=year).first() + if gsoc_year is None: + return None + return self.userprofile_set.filter(role=3, + gsoc_year=gsoc_year).first() auth.models.User.add_to_class('has_proposal', has_proposal) diff --git a/project.db b/project.db deleted file mode 100644 index 9508bc2edc40a3b48ed82a14edf0246bfc98bbad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1339392 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 J_^rV2{|C&wcJ=@O From 20889d7f000f8371a31a710b43a7ac435f380c9b Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sun, 5 May 2019 11:46:01 +0800 Subject: [PATCH 0113/1137] remove redundant try block --- gsoc/models.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 5b208365..71c5f8a3 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -85,15 +85,12 @@ def has_proposal(self): def is_current_year_student(self): - try: - profile = self.student_profile() - if not profile: - return False - year = profile.gsoc_year.gsoc_year - current_year = timezone.now().year - return current_year == year - except UserProfile.DoesNotExist: + profile = self.student_profile() + if not profile: return False + year = profile.gsoc_year.gsoc_year + current_year = timezone.now().year + return current_year == year def student_profile(self, year=timezone.now().year): From 7f6bc91914b99ea67d7f337839180d75c511a7cd Mon Sep 17 00:00:00 2001 From: ntkomata Date: Sun, 5 May 2019 11:48:54 +0800 Subject: [PATCH 0114/1137] restore db --- project.db | Bin 0 -> 1339392 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 project.db diff --git a/project.db b/project.db new file mode 100644 index 0000000000000000000000000000000000000000..9508bc2edc40a3b48ed82a14edf0246bfc98bbad GIT binary patch literal 1339392 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 J_^rV2{|C&wcJ=@O literal 0 HcmV?d00001 From 7331592f860bbb71468d35d61454e9df7cee453f Mon Sep 17 00:00:00 2001 From: "mihir(faux__)" Date: Tue, 7 May 2019 22:17:12 +0530 Subject: [PATCH 0115/1137] Have added pure css wherever not used --- .../aldryn_newsblog/plugins/article_search.html | 4 ++-- gsoc/templates/myprofile.html | 15 +++++++++------ gsoc/templates/registration/login.html | 2 +- .../registration/password_reset_confirm.html | 2 +- .../registration/password_reset_form.html | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/gsoc/templates/aldryn_newsblog/plugins/article_search.html b/gsoc/templates/aldryn_newsblog/plugins/article_search.html index c1d7f3a4..f7638d66 100644 --- a/gsoc/templates/aldryn_newsblog/plugins/article_search.html +++ b/gsoc/templates/aldryn_newsblog/plugins/article_search.html @@ -2,10 +2,10 @@ {# submission of this form will forward to "includes/search_results.html" #} {# this form can also be submitted using ajax and "includes/search_results.html" is returned #} -
    + - +
    diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index 39ff673b..3d403bfc 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -35,14 +35,14 @@

    Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!


    diff --git a/gsoc/templates/registration/login.html b/gsoc/templates/registration/login.html index 9224c396..7a786995 100644 --- a/gsoc/templates/registration/login.html +++ b/gsoc/templates/registration/login.html @@ -20,7 +20,7 @@ Logout {% else %} -
    + {% csrf_token %}

    Login

    diff --git a/gsoc/templates/registration/password_reset_confirm.html b/gsoc/templates/registration/password_reset_confirm.html index ea31ddce..eb0a7dc5 100644 --- a/gsoc/templates/registration/password_reset_confirm.html +++ b/gsoc/templates/registration/password_reset_confirm.html @@ -3,7 +3,7 @@ {% block content %} {% if validlink %}

    Please enter (and confirm) your new password.

    - + {% csrf_token %} diff --git a/gsoc/templates/registration/password_reset_form.html b/gsoc/templates/registration/password_reset_form.html index d61a4010..926cfe25 100644 --- a/gsoc/templates/registration/password_reset_form.html +++ b/gsoc/templates/registration/password_reset_form.html @@ -6,7 +6,7 @@

    Forgot your password?

    Enter your email address below, and we'll email instructions for setting a new one.

    - + {% csrf_token %} {% if form.email.errors %} {{ form.email.errors }} From a54a5ba194ef4fe8b461932f4480032053a44344 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 3 May 2019 17:54:53 +0530 Subject: [PATCH 0116/1137] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 058ed691..f73d2802 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ logs/* # Pyenv .python-version +# Vscode +.vscode From 4a4c2ddcba8c99effb00b44a8a4bd40b2dfb3c8c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 3 May 2019 21:37:19 +0530 Subject: [PATCH 0117/1137] Add Comment model and migration --- gsoc/migrations/0016_comment.py | 28 ++++++++++++++++++++++++++++ gsoc/models.py | 16 ++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 gsoc/migrations/0016_comment.py diff --git a/gsoc/migrations/0016_comment.py b/gsoc/migrations/0016_comment.py new file mode 100644 index 00000000..417bda65 --- /dev/null +++ b/gsoc/migrations/0016_comment.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.8 on 2019-05-03 15:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('aldryn_newsblog', '0016_auto_20180329_1417'), + ('gsoc', '0015_auto_20190423_1031'), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('username', models.CharField(max_length=50)), + ('content', models.TextField()), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), + ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Comment')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 04ef4c6d..ac978288 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -18,6 +18,7 @@ from aldryn_apphooks_config.fields import AppHookConfigField from aldryn_newsblog.cms_appconfig import NewsBlogConfig +from aldryn_newsblog.models import Article from cms.models import Page, PagePermission from cms import api @@ -390,3 +391,18 @@ def create_scheduler(self, trigger_time=timezone.now()): def create_send_reglink_schedulers(sender, instance, **kwargs): if instance.adduserlog is not None and instance.scheduler is None: instance.create_scheduler() + + +class Comment(models.Model): + username = models.CharField(max_length=50) + user = models.ForeignKey(User, null=True, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE) + content = models.TextField() + parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE) + + +def get_root_comments(self): + return self.comment_set.filter(parent=None).all() + + +Article.add_to_class('get_root_comments', get_root_comments) From 5b6e90edf241fab3b9b8172e0d516bd86283f7fc Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 4 May 2019 13:21:35 +0530 Subject: [PATCH 0118/1137] Add views and templates for comments --- gsoc/models.py | 4 +- gsoc/static/css/comments.css | 41 +++++++++++++++ gsoc/static/js/comments.js | 5 ++ .../aldryn_newsblog/article_detail.html | 5 +- gsoc/templates/aldryn_newsblog/base.html | 5 ++ .../aldryn_newsblog/includes/comments.html | 51 +++++++++++++++++++ gsoc/templates/base.html | 11 ++-- gsoc/urls.py | 5 ++ gsoc/views.py | 41 ++++++++++++++- 9 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 gsoc/static/css/comments.css create mode 100644 gsoc/static/js/comments.js create mode 100644 gsoc/templates/aldryn_newsblog/includes/comments.html diff --git a/gsoc/models.py b/gsoc/models.py index ac978288..0244a232 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -398,7 +398,9 @@ class Comment(models.Model): user = models.ForeignKey(User, null=True, on_delete=models.CASCADE) article = models.ForeignKey(Article, on_delete=models.CASCADE) content = models.TextField() - parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE) + parent = models.ForeignKey('self', null=True, + on_delete=models.CASCADE, + related_name='replies') def get_root_comments(self): diff --git a/gsoc/static/css/comments.css b/gsoc/static/css/comments.css new file mode 100644 index 00000000..f12160a4 --- /dev/null +++ b/gsoc/static/css/comments.css @@ -0,0 +1,41 @@ +.aldryn-newsblog-comments { + margin: 16px 0; + padding: 16px 24px; +} + +.aldryn-newsblog-comments form { + display: none; +} + +.aldryn-newsblog-comments #form-root { + display: block; +} + +.aldryn-newsblog-subcomments { + padding-left: 24px; + border-left: 0.1px solid #eee; +} + +.comment { + padding: 5px 10px; + margin: 5px 0px; + background: #eee; +} + +.comment-container .c-username { + color: #489EBA; + font-size: 12px; + font-weight: bold; +} + +.comment-container .c-actions { + font-size: 12px; +} + +.comment-container .c-actions .reply { + cursor: pointer; +} + +.comment-container .c-content { + font-size: 14px; +} \ No newline at end of file diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js new file mode 100644 index 00000000..bed5395f --- /dev/null +++ b/gsoc/static/js/comments.js @@ -0,0 +1,5 @@ +function showCommentForm(commentPk) { + var form = document.getElementById('form-' + commentPk); + form.style.display = 'block'; + form.scrollIntoView(); +} \ No newline at end of file diff --git a/gsoc/templates/aldryn_newsblog/article_detail.html b/gsoc/templates/aldryn_newsblog/article_detail.html index ea7d1ec1..eb7454eb 100644 --- a/gsoc/templates/aldryn_newsblog/article_detail.html +++ b/gsoc/templates/aldryn_newsblog/article_detail.html @@ -25,5 +25,8 @@ {% endif %} - {% static_placeholder "newsblog_comments" %} + +
    + {% include "aldryn_newsblog/includes/comments.html" with comments=article.get_root_comments article=article user=user csrf_token=csrf_token only %} +
    {% endblock %} \ No newline at end of file diff --git a/gsoc/templates/aldryn_newsblog/base.html b/gsoc/templates/aldryn_newsblog/base.html index 20dfaa98..5339f851 100644 --- a/gsoc/templates/aldryn_newsblog/base.html +++ b/gsoc/templates/aldryn_newsblog/base.html @@ -2,6 +2,7 @@ {% block head %} + {% endblock head %} @@ -10,3 +11,7 @@ {% block newsblog_content %}{% endblock %} {% endblock content %} + +{% block js %} + +{% endblock %} \ No newline at end of file diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html new file mode 100644 index 00000000..ca0fae9e --- /dev/null +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -0,0 +1,51 @@ +{% for comment in comments %} +
    +
    +
    + {{ comment.username }} +
    +
    + {{ comment.content }} +
    +
    +
    + + + Reply + +
    +
    +
    + {% include "aldryn_newsblog/includes/comments.html" with comments=comment.replies.all parent=comment article=article user=user csrf_token=csrf_token only %} +
    +{% endfor %} + + + {% csrf_token %} + + + + + + + {% if parent %} + + {% endif %} + + {% if user.is_authenticated %} + + {% endif %} + +
    + {% if user.is_authenticated %} + + {% else %} + + {% endif %} + +
    + + + \ No newline at end of file diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 674819bd..d88df973 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -11,7 +11,7 @@ - @@ -87,9 +87,11 @@ {% endif %} {% if errors %} {% for e in errors %} -
    - {{ e }} -
    + {% if e %} +
    + {{ e }} +
    + {% endif %} {% endfor %} {% endif %} {% if request.current_page.publisher_is_draft %} @@ -114,5 +116,6 @@ {% render_block 'js' %} + {% block js %}{% endblock %} diff --git a/gsoc/urls.py b/gsoc/urls.py index b0aaade3..28e93dfa 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -55,3 +55,8 @@ url('upload-proposal/', gsoc.views.upload_proposal_view, name='upload-proposal'), url('cancel_proposal_upload/', gsoc.views.cancel_proposal_upload_view, name='cancel-proposal-upload'), ] + +# Add comment routes +urlpatterns += [ + url('comment/new/', gsoc.views.new_comment, name='new_comment'), +] diff --git a/gsoc/views.py b/gsoc/views.py index 31e3649f..fe0e55ea 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,11 +1,16 @@ +from .forms import ProposalUploadForm +from .models import RegLink, validate_proposal_text, Comment + import io + from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User -from .forms import ProposalUploadForm -from .models import RegLink, validate_proposal_text from django import shortcuts from django.http import JsonResponse from django.core.exceptions import ValidationError +from django.shortcuts import redirect + +from aldryn_newsblog.models import Article from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter from pdfminer.converter import TextConverter @@ -161,3 +166,35 @@ def register_view(request): context['done_registration'] = False return shortcuts.render(request, 'registration/register.html', context) + + +def new_comment(request): + if request.method == 'POST': + comment = request.POST.get('comment') + article_pk = request.POST.get('article') + article = Article.objects.get(pk=article_pk) + user_pk = request.POST.get('user', None) + parent_pk = request.POST.get('parent', None) + + if parent_pk: + parent = Comment.objects.get(pk=parent_pk) + else: + parent = None + + if user_pk: + user = User.objects.get(pk=user_pk) + username = user.username + else: + user = None + username = request.POST.get('username') + + c = Comment(username=username, content=comment, + user=user, article=article, + parent=parent) + c.save() + + redirect_path = request.POST.get('redirect') + if redirect_path: + return redirect(redirect_path) + else: + return redirect('/') \ No newline at end of file From 368d1d9b3ef0e193d2bba105985ad5e698a0e64c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 6 May 2019 11:35:41 +0530 Subject: [PATCH 0119/1137] Add share button and datetime on comments --- gsoc/migrations/0017_auto_20190506_0516.py | 26 ++++++++++++++++++ gsoc/models.py | 1 + gsoc/static/css/comments.css | 12 +++++++++ gsoc/static/js/comments.js | 27 ++++++++++++++++++- .../aldryn_newsblog/includes/comments.html | 10 ++++++- 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 gsoc/migrations/0017_auto_20190506_0516.py diff --git a/gsoc/migrations/0017_auto_20190506_0516.py b/gsoc/migrations/0017_auto_20190506_0516.py new file mode 100644 index 00000000..70eb598d --- /dev/null +++ b/gsoc/migrations/0017_auto_20190506_0516.py @@ -0,0 +1,26 @@ +# Generated by Django 2.1.8 on 2019-05-06 05:16 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0016_comment'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AlterField( + model_name='comment', + name='parent', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='gsoc.Comment'), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 0244a232..4b9f7f40 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -401,6 +401,7 @@ class Comment(models.Model): parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='replies') + created_at = models.DateTimeField(auto_now_add=True) def get_root_comments(self): diff --git a/gsoc/static/css/comments.css b/gsoc/static/css/comments.css index f12160a4..f1354e66 100644 --- a/gsoc/static/css/comments.css +++ b/gsoc/static/css/comments.css @@ -22,6 +22,10 @@ background: #eee; } +.comment.selected { + background: #ccc; +} + .comment-container .c-username { color: #489EBA; font-size: 12px; @@ -32,10 +36,18 @@ font-size: 12px; } +.comment-container .c-actions span { + margin-right: 10px; +} + .comment-container .c-actions .reply { cursor: pointer; } +.comment-container .c-actions .share { + cursor: pointer; +} + .comment-container .c-content { font-size: 14px; } \ No newline at end of file diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index bed5395f..e08b8494 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -2,4 +2,29 @@ function showCommentForm(commentPk) { var form = document.getElementById('form-' + commentPk); form.style.display = 'block'; form.scrollIntoView(); -} \ No newline at end of file +} + +function copyCommentUrl(commentPk) { + var href = window.location.href.split('#')[0] + var str = href + '#comment-' + commentPk; + console.log(str); + var el = document.createElement('textarea'); + el.value = str; + el.setAttribute('readonly', ''); + el.style = {position: 'absolute', left: '-9999px'}; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); +} + +(function markSelected() { + var parts = window.location.href.split('#') + if (parts.length == 2 && parts[1].split('-')[0] == 'comment') { + var comment = document.getElementById(parts[1]); + console.log(comment); + if (comment) { + comment.classList.add('selected') + } + } +}()); \ No newline at end of file diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index ca0fae9e..2959829d 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -1,6 +1,7 @@ {% for comment in comments %}
    -
    +
    + copy
    {{ comment.username }}
    @@ -9,10 +10,17 @@
    + + {{ comment.created_at }} + Reply +
    From 5c69c70269db31c9c9e749f3d9d021ef53f4015c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 12:07:51 +0530 Subject: [PATCH 0120/1137] Add recaptcha verification for adding new comments --- gsoc/settings.py | 3 +- gsoc/static/css/python-gsoc.css | 3 + gsoc/static/js/recaptcha.js | 13 ++++ .../aldryn_newsblog/includes/comments.html | 3 +- gsoc/templates/base.html | 2 + gsoc/views.py | 77 ++++++++++++------- 6 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 gsoc/static/js/recaptcha.js diff --git a/gsoc/settings.py b/gsoc/settings.py index 3a0cb10e..0f701ad8 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -150,7 +150,8 @@ def gettext(s): return s 'taggit', 'gsoc', 'blogs_list', - 'debug_toolbar' + 'debug_toolbar', + 'snowpenguin.django.recaptcha3', ) THUMBNAIL_PROCESSORS = ( 'easy_thumbnails.processors.colorspace', diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index 24c49fde..601c41d2 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -253,6 +253,9 @@ div.problem { color: red; } +.grecaptcha-badge { + display: none; +} .center { display: flex; justify-content: center; diff --git a/gsoc/static/js/recaptcha.js b/gsoc/static/js/recaptcha.js new file mode 100644 index 00000000..303e9a93 --- /dev/null +++ b/gsoc/static/js/recaptcha.js @@ -0,0 +1,13 @@ +grecaptcha.ready(function() { + grecaptcha.execute('6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj', {action: 'comment'}).then(function(token) { + forms = document.getElementsByClassName('comment-form'); + for (var i=0; i
    - copy
    {{ comment.username }}
    @@ -28,7 +27,7 @@
    {% endfor %} -
    + {% csrf_token %} diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index d88df973..2a738561 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -17,6 +17,7 @@ + {% load static %} {% block head %}{% endblock %} @@ -116,6 +117,7 @@ {% render_block 'js' %} + {% block js %}{% endblock %} diff --git a/gsoc/views.py b/gsoc/views.py index fe0e55ea..99a2c91d 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -2,6 +2,8 @@ from .models import RegLink, validate_proposal_text, Comment import io +import urllib +import json from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User @@ -17,6 +19,7 @@ from pdfminer.layout import LAParams from pdfminer.pdfpage import PDFPage +from gsoc import settings # handle proposal upload @@ -170,31 +173,51 @@ def register_view(request): def new_comment(request): if request.method == 'POST': - comment = request.POST.get('comment') - article_pk = request.POST.get('article') - article = Article.objects.get(pk=article_pk) - user_pk = request.POST.get('user', None) - parent_pk = request.POST.get('parent', None) - - if parent_pk: - parent = Comment.objects.get(pk=parent_pk) - else: - parent = None + print('POST method for adding comment') - if user_pk: - user = User.objects.get(pk=user_pk) - username = user.username - else: - user = None - username = request.POST.get('username') - - c = Comment(username=username, content=comment, - user=user, article=article, - parent=parent) - c.save() - - redirect_path = request.POST.get('redirect') - if redirect_path: - return redirect(redirect_path) - else: - return redirect('/') \ No newline at end of file + recaptcha_response = request.POST.get('g-recaptcha-response') + url = 'https://www.google.com/recaptcha/api/siteverify' + payload = { + 'secret': settings.RECAPTCHA_PRIVATE_KEY, + 'response': recaptcha_response + } + data = urllib.parse.urlencode(payload).encode() + req = urllib.request.Request(url, data=data) + + print('Connecting to google') + response = urllib.request.urlopen(req) + result = json.loads(response.read().decode()) + + if (result['success'] and + result['action'] == 'comment' and + result['score'] >= settings.RECAPTCHA_THRESHOLD): + print('recaptcha_score', result['score']) + + comment = request.POST.get('comment') + article_pk = request.POST.get('article') + article = Article.objects.get(pk=article_pk) + user_pk = request.POST.get('user', None) + parent_pk = request.POST.get('parent', None) + + if parent_pk: + parent = Comment.objects.get(pk=parent_pk) + else: + parent = None + + if user_pk: + user = User.objects.get(pk=user_pk) + username = user.username + else: + user = None + username = request.POST.get('username') + + c = Comment(username=username, content=comment, + user=user, article=article, + parent=parent) + c.save() + + redirect_path = request.POST.get('redirect') + if redirect_path: + return redirect(redirect_path) + else: + return redirect('/') From 87105358323f9ec97f818ad0456d9e88adf0c8c8 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 12:15:54 +0530 Subject: [PATCH 0121/1137] Merged migrations --- gsoc/migrations/0016_comment.py | 5 +++-- gsoc/migrations/0017_auto_20190506_0516.py | 26 ---------------------- gsoc/views.py | 5 ----- 3 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 gsoc/migrations/0017_auto_20190506_0516.py diff --git a/gsoc/migrations/0016_comment.py b/gsoc/migrations/0016_comment.py index 417bda65..387324a8 100644 --- a/gsoc/migrations/0016_comment.py +++ b/gsoc/migrations/0016_comment.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.8 on 2019-05-03 15:47 +# Generated by Django 2.1.8 on 2019-05-08 06:44 from django.conf import settings from django.db import migrations, models @@ -20,8 +20,9 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('username', models.CharField(max_length=50)), ('content', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), - ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Comment')), + ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='gsoc.Comment')), ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), diff --git a/gsoc/migrations/0017_auto_20190506_0516.py b/gsoc/migrations/0017_auto_20190506_0516.py deleted file mode 100644 index 70eb598d..00000000 --- a/gsoc/migrations/0017_auto_20190506_0516.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-06 05:16 - -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('gsoc', '0016_comment'), - ] - - operations = [ - migrations.AddField( - model_name='comment', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), - preserve_default=False, - ), - migrations.AlterField( - model_name='comment', - name='parent', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='gsoc.Comment'), - ), - ] diff --git a/gsoc/views.py b/gsoc/views.py index 99a2c91d..7fad90a7 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -173,8 +173,6 @@ def register_view(request): def new_comment(request): if request.method == 'POST': - print('POST method for adding comment') - recaptcha_response = request.POST.get('g-recaptcha-response') url = 'https://www.google.com/recaptcha/api/siteverify' payload = { @@ -184,15 +182,12 @@ def new_comment(request): data = urllib.parse.urlencode(payload).encode() req = urllib.request.Request(url, data=data) - print('Connecting to google') response = urllib.request.urlopen(req) result = json.loads(response.read().decode()) if (result['success'] and result['action'] == 'comment' and result['score'] >= settings.RECAPTCHA_THRESHOLD): - print('recaptcha_score', result['score']) - comment = request.POST.get('comment') article_pk = request.POST.get('article') article = Article.objects.get(pk=article_pk) From 21c71f8d2f1b20bac8f52b1dce29ed52021ab463 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 12:22:29 +0530 Subject: [PATCH 0122/1137] Fix pep8 warnings --- gsoc/migrations/0016_comment.py | 13 +++++++++---- gsoc/views.py | 8 ++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/gsoc/migrations/0016_comment.py b/gsoc/migrations/0016_comment.py index 387324a8..adbfc319 100644 --- a/gsoc/migrations/0016_comment.py +++ b/gsoc/migrations/0016_comment.py @@ -17,13 +17,18 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Comment', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(auto_created=True, primary_key=True, + serialize=False, verbose_name='ID')), ('username', models.CharField(max_length=50)), ('content', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), - ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='gsoc.Comment')), - ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + to='aldryn_newsblog.Article')), + ('parent', models.ForeignKey(null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='replies', to='gsoc.Comment')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/gsoc/views.py b/gsoc/views.py index 7fad90a7..78bb20e6 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -185,9 +185,9 @@ def new_comment(request): response = urllib.request.urlopen(req) result = json.loads(response.read().decode()) - if (result['success'] and - result['action'] == 'comment' and - result['score'] >= settings.RECAPTCHA_THRESHOLD): + if (result['success'] and result['action'] == 'comment' + and result['score'] >= settings.RECAPTCHA_THRESHOLD): + # if score greater than threshold allow to add comment = request.POST.get('comment') article_pk = request.POST.get('article') article = Article.objects.get(pk=article_pk) @@ -205,7 +205,7 @@ def new_comment(request): else: user = None username = request.POST.get('username') - + c = Comment(username=username, content=comment, user=user, article=article, parent=parent) From 07df3acaaaa63b10572687ca4eb82f24e9ada493 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 13:41:52 +0530 Subject: [PATCH 0123/1137] Add message if reCAPTCHA verification fails while commenting --- gsoc/templates/base.html | 7 +++++++ gsoc/views.py | 15 ++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 2a738561..d540ecd1 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -95,6 +95,13 @@ {% endif %} {% endfor %} {% endif %} + {% if messages %} + {% for message in messages %} +
    + {{ message }} +
    + {% endfor %} + {% endif %} {% if request.current_page.publisher_is_draft %} {% for notification in request.current_page.notifications.all %}
    diff --git a/gsoc/views.py b/gsoc/views.py index 78bb20e6..71033e68 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -5,6 +5,7 @@ import urllib import json +from django.contrib import messages from django.contrib.auth import decorators, password_validation, validators from django.contrib.auth.models import User from django import shortcuts @@ -210,9 +211,13 @@ def new_comment(request): user=user, article=article, parent=parent) c.save() + else: + messages.add_message(request, messages.ERROR, + 'reCAPTCHA verification failed') - redirect_path = request.POST.get('redirect') - if redirect_path: - return redirect(redirect_path) - else: - return redirect('/') + redirect_path = request.POST.get('redirect') + + if redirect_path: + return redirect(redirect_path) + else: + return redirect('/') \ No newline at end of file From a4430427ffc99207c945982070a568a91088b6a2 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 14:16:28 +0530 Subject: [PATCH 0124/1137] Add recaptcha config variables in settings_local template --- gsoc/settings.py | 1 - settings_local.py.template | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 0f701ad8..4a65b18a 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -151,7 +151,6 @@ def gettext(s): return s 'gsoc', 'blogs_list', 'debug_toolbar', - 'snowpenguin.django.recaptcha3', ) THUMBNAIL_PROCESSORS = ( 'easy_thumbnails.processors.colorspace', diff --git a/settings_local.py.template b/settings_local.py.template index a799c4b8..5a80db98 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -46,4 +46,10 @@ EMAIL_HOST = 'localhost' EMAIL_PORT = 25 #EMAIL_HOST_USER = "" #EMAIL_HOST_PASSWORD = "" -REPLY_EMAIL = 'gsoc-admins@python.org' \ No newline at end of file +REPLY_EMAIL = 'gsoc-admins@python.org' + +# reCAPTCHA settings +# update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually +RECAPTCHA_PRIVATE_KEY = '6LdAVqIUAAAAACv_tNPudwx_pD9dbjtwvr3WwQ9Y' +RECAPTCHA_PUBLIC_KEY = '6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj' +RECAPTCHA_THRESHOLD = 0.5 \ No newline at end of file From 0be476bace86b37056da850db823f85eba5941a7 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 14:35:45 +0530 Subject: [PATCH 0125/1137] Add profanity filter for comments --- gsoc/views.py | 19 ++++++++++++++----- requirements.txt | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 71033e68..0eef974b 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,3 +1,5 @@ +from gsoc import settings + from .forms import ProposalUploadForm from .models import RegLink, validate_proposal_text, Comment @@ -20,7 +22,8 @@ from pdfminer.layout import LAParams from pdfminer.pdfpage import PDFPage -from gsoc import settings +from profanityfilter import ProfanityFilter + # handle proposal upload @@ -207,10 +210,16 @@ def new_comment(request): user = None username = request.POST.get('username') - c = Comment(username=username, content=comment, - user=user, article=article, - parent=parent) - c.save() + pf = ProfanityFilter() + if pf.is_clean(comment) and pf.is_clean(username): + c = Comment(username=username, content=comment, + user=user, article=article, + parent=parent) + c.save() + else: + messages.add_message(request, messages.ERROR, + 'Abusive content detected! Please refrain\ + from using any indecent words while commenting.') else: messages.add_message(request, messages.ERROR, 'reCAPTCHA verification failed') diff --git a/requirements.txt b/requirements.txt index 52128903..72239b5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ pdfminer.six==20181108 chardet==3.0.4 phonenumbers==8.10.6 pre-commit>=1.14.4 +profanityfilter>=2.0.6 From 875c4250d15c01f568116d4dd0378ccd0f578c74 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 14:43:48 +0530 Subject: [PATCH 0126/1137] Update db and fix pep8 warning --- gsoc/views.py | 2 +- project.db | Bin 1339392 -> 1355776 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/views.py b/gsoc/views.py index 0eef974b..10297426 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -229,4 +229,4 @@ def new_comment(request): if redirect_path: return redirect(redirect_path) else: - return redirect('/') \ No newline at end of file + return redirect('/') diff --git a/project.db b/project.db index 9508bc2edc40a3b48ed82a14edf0246bfc98bbad..acf79c3eff6aaacfab51aa331e1ab553eba9a740 100644 GIT binary patch delta 2501 zcmai#drVvR702)IzWusCUwe$^)m%(SVuH=}^#crB8Yh5DQk&qoOYk2t#=aCAV+^*l zfYJrh2ik(#kgMjdZPrxUmQGqW3AyXdw53^7CQYlfp{jXInIhU%{iB@}q^XmpPU(Iw zkC2h7md?HQIiGWW-`~UMH?hz?u@E`4iT@6Ufdkm;hdnma;6_g8wh0Q}#;Rcng}~!8 z`34-86uwuQo&Py!L9X4?`_a=eEurVe(|CCbK5Ztwx=*|cD-7UrMaR{g% z@@%a#=+r=~Qrq|wAS&-P?js_MU17x2IAbrL6^M?X$bYh6zM0V(`C+S=6{Z`4LnXA@S`gdV<9oIh{}bc<501bLI=hb9Z2K`3qnH34i{_I?TyFd zY;JaWBsYzg5#@IU0QNw>6iN)r%jv>!zfh2K1-V8zAj^3nT@*@r=W@-el?@kLyv@kz z^?4E;5>W-9*3Pss~?_p*wC`-dgNSks1SEMeNHisPIoDu8&1iExSWf- z;!?a+kVjimqlx~aqvjiN_ZobMP{BiAU!8TeR{2)$AEeHM#mm$0d27FE>7( zNqI;5-Cfa;6dQ_VyAHRH^<;WHp|;3C&vOpv zjD_3O#R5Ma?eug-yN`#0J!7Hqj)AVWy@OrR&atk;PfD>&@^~l{PKVk8QtWW2Sjp-N zc8>Q%!-JhU@w+SgDoOYMbIna2Uz11sc!U=?*1%;j?jD!X^b`D1u5KF<0^c+fllI~Z zdgu##CyYOf5IT(aHp$_(QpK|YUmk{*=xAjpX z0KQ(8Nqg_tyuTaF{D!h@0jz#j|Em5u{fJf%EcH4dD6e*JK4c|99UeKBmcPH!{D4?R z0y`c_$yvE5uX-Doyh(J>pStgLT1ZffM-r*jD(KMW^p-t!#BTDu%7~T{OIgWJ$#=-} zNb)1(N!uxr!a$wlfid?bazaIeu-pT;pK3&q-SvMWD%g^&`=E0Pd{9>fT=J}d!YKy= zYj)l&#)n;o2nrT+U7r4uHvYXfFGk1 z`!}`!W&Zb5#W{;soU@)WY&YUpFvd^M(%VfrL)oC$@6)})et}^ zo`+Es=zvn=q=&+)>H?LELlj-UmE~VK4a@g}*dltJO`=a~@@vn>zcp@t)P(L$10BI& zmb-4u8*Zan{y=w=S!DX?n{)&f_t34AVERd>I#`EK)}(VO`S{wq@v9CHPp9HuPs;0- z+=;b8a6k>*uv$+pZ{s@FCUk`b+u979u+vs)L8zk5KFLf+nd$>*T>JWQ^NCT^X)7bA z-2P;L((7AW2khS$X%9r(h0btWyZXcpE4wOUed5Dqta3<*e)xCpni|J%m zt|;;M`Mlmf`9UR}OG?z>4XbHY#6yxEuHrsuvp|FnX1DS&Mq~ycw#$$^b?kIcwgaF8tB5L7m&erd8J*wM{i&w^GdV zzNs_gq+QL|TPfB2TNu(_zhmtMTI{;j540omRvFin8`fRmEi?5W$j~@R03H3-L2wV^ KnC_wo%>M%f0QP(U delta 1046 zcmZXSduUr#9LLYO@7yHk+UCbMSO>cOBM9T-j;QNi{X;BBkm(BjW3iMXR_b6F5XbZsk%1og{5a>! zIlt#AR9+}llBLI09{{k8q5x_DkoqV5Au2Z17kE4m*5LfAzI7t&H`H|aQuzYXYP}n4 zUpqk6z?mquYsXlth~H4Sux%-5NZww?wq&X4L7TC(=~Kpj!7_Z0K-U`T=0D5$YV#>* zSoJ> z$5nzV%r*zjMIqh4G8gYj3|7>9O))F(r!H>Ti~bRjB2jD&eZsNC~1#ASfXcnA;C0EaOw8ftpCS?*KA9vLnIlyTg$ zzb6CnEy2f?r5o%!aFN~!n!B=U>dEBm?a%BF?aJ&t)Y*|7?&|2xvM}x7@ za7>TI!hP8-O`+J9_(6?~zpdsVbP&FzBFP3{6dyejfhTh2^}GC9^ZY%&#=LWyb(=>g z)f@)^*JrNGE2HKYH?^-x?WDTEdt@{V@B+SxKfxz)8{UX*_#pZP&C;SvXdPYvTZ4L2 zJfMdH!H8FnBqGs7B-jv+>A`qRBeS2W@gzL^Ij_|~$-}`$CV8NT_@L&q!2#ogWFMK4 zwdSYU1Rpg;UCU9gUveZG(Y`zFaWM11{+B%_=WOSslGH9H`KP!6c7;4~O}omF^*_4a zhsD7ejSSAXO7?mMq=56V-LlJai)~W+ler?L#A(qd7$C(z;Ja`eI!_0nlkAAQhap$K zcb#EW_rqcTvm>#*I}XVY$K0DCnZE5R3mW+UrQ1X9G`w7KaasCNM*;25D9wBrlhK#m G!^+>>x-8@X From ae98a8841218cc05d7dfac0115e62fa0e27fa5c9 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 15:03:33 +0530 Subject: [PATCH 0127/1137] Use in-build messages framework to display messages --- blogs_list/views.py | 6 ++++-- gsoc/templates/base.html | 19 ++++++------------- gsoc/views.py | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/blogs_list/views.py b/blogs_list/views.py index fe4ed389..717cd65a 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -2,6 +2,7 @@ import random from django.shortcuts import render +from django.contrib import messages from gsoc.models import ( GsocYear, @@ -44,9 +45,10 @@ def list_blogs(request): if flag: blogsets.append((year.gsoc_year, blogset)) - err = "No blogs currently! Please visit again later." if not blogsets else None + if not blogsets: + messages.add_message(request, messages.ERROR, + 'No blogs currently! Please visit again later.') return render(request, 'list_view.html', { 'blogsets': blogsets, - 'errors': [err] }) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index d540ecd1..7fb53f52 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -86,21 +86,14 @@ No such user found! Please verify your credentials and enter again.
    {% endif %} - {% if errors %} - {% for e in errors %} - {% if e %} + {% if not request.user.is_authenticated %} + {% if messages %} + {% for message in messages %}
    - {{ e }} + {{ message }}
    - {% endif %} - {% endfor %} - {% endif %} - {% if messages %} - {% for message in messages %} -
    - {{ message }} -
    - {% endfor %} + {% endfor %} + {% endif %} {% endif %} {% if request.current_page.publisher_is_draft %} {% for notification in request.current_page.notifications.all %} diff --git a/gsoc/views.py b/gsoc/views.py index 10297426..bbce70c0 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -222,7 +222,7 @@ def new_comment(request): from using any indecent words while commenting.') else: messages.add_message(request, messages.ERROR, - 'reCAPTCHA verification failed') + 'reCAPTCHA verification failed.') redirect_path = request.POST.get('redirect') From 7c84774b3f7cff601be21793a402cd1a4809d549 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 8 May 2019 15:30:00 +0530 Subject: [PATCH 0128/1137] Change share button text when link copied --- gsoc/static/js/comments.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index e08b8494..7326a467 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -1,3 +1,7 @@ +const sleep = (milliseconds) => { + return new Promise(resolve => setTimeout(resolve, milliseconds)) +} + function showCommentForm(commentPk) { var form = document.getElementById('form-' + commentPk); form.style.display = 'block'; @@ -7,7 +11,6 @@ function showCommentForm(commentPk) { function copyCommentUrl(commentPk) { var href = window.location.href.split('#')[0] var str = href + '#comment-' + commentPk; - console.log(str); var el = document.createElement('textarea'); el.value = str; el.setAttribute('readonly', ''); @@ -16,13 +19,18 @@ function copyCommentUrl(commentPk) { el.select(); document.execCommand('copy'); document.body.removeChild(el); + + var share = document.getElementById(`share-${commentPk}`); + share.innerHTML = "Link copied!" + sleep(3000).then(() => { + share.innerHTML = `Share`; + }) } (function markSelected() { var parts = window.location.href.split('#') if (parts.length == 2 && parts[1].split('-')[0] == 'comment') { var comment = document.getElementById(parts[1]); - console.log(comment); if (comment) { comment.classList.add('selected') } From 304aafa200ffd1e7e370e67b3ce535474153bcb8 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 10 May 2019 08:33:48 +0530 Subject: [PATCH 0129/1137] Migrate db --- project.db | Bin 1355776 -> 1368064 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index acf79c3eff6aaacfab51aa331e1ab553eba9a740..ce5949c4484ec61f31c2f6c9c0517f73fcd67603 100644 GIT binary patch delta 862 zcmZY8TWHfz7zglk(xgd~Hm7Z(Q*>B{L$|eCx~{gHm!e~+;MBT-bAs8WsSdVT=DeVy zt+$F4w=pC*5!5GNM5M4Yr&B@41YzvO4HaYq5kb&8@S%v8&&!J%_>qL~pD*WpA<5Yt z$wv+SOI#6z@K#Wd5MmG#`UboKE`rLnnme(Dgx>}wUuI17spzRNKCapKWiY zn-0k#WnC>jw(Q%IJq#Wmtn)s_Ri|ZNTJ~YU01IFN2dsdnvQLP|GlVFr%DhSQywlc<~!d zYhNk}OcTddgBhMx*o4f-d~y z4)*_vJ>$)^o5kbqz*~b^`glMFr zJ7ye-cXS#(dVGIp3{Jg0hM{-F^v-y%u7$(xMzFFC?*9sz*GEaA**BvI&VOTO|Fp7D z{nMn#>nVajQAvu=>@{2OlCaq}t!$&Y!|KJKUy#vYL0)D!DdY*w_o=++M$$R3onMT9 d|1IbOjOaW_gR|gt)|LB6vYy;58T7QcegKU}2Gsxn delta 512 zcmX}oT}TvB6bJCRGj~4k?%dg#O9c^HXi@HNrbY@rL{U)@G224+L2Y6YQ8CgeVGuhd z?aLs>lD7<^r-~p%Hn^K%2?qAqi!GwT_TpoO;e$jU=cxm~hr>A>4*%lJiDErBx-r!) zN$9Bbzl_2A>2|eS$~F}z`_@tY2#hmvTP31j9Vbc1NMf9JnZK=7bD6xTqza)~S>Aq5 zMz6~U(jU>byLP5zXAqEq0x(d426SO(jFsN8Ruu~z7^@AXwFv{5zybs$KoZ!10tckT z+JtL34e+I_ z8^7dfqep+I{!vfUL-teEhs#;f^U3>$u^mfwXqRxL$GiE3H?))g9Ad{wgyRl<{o!Ug zYUbCb=uW;NWIk%=S3-72ysvq~vS?ZI0wivJa2I3@A%_rVfX4PJ_&Kh7fu+#AL}%$e znuozbD)Q&qD`yiVK(w4huiEp8fGk1^x6PXVL+{mUS`HRFG?5!*PmtJmi;c+q(Eytg XXXd?5Bp!e9F3O_(*=s@1imde?#eAmw From 62cfa935da563f0d84f0d458803eb6ef06efa981 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 10 May 2019 09:39:37 +0530 Subject: [PATCH 0130/1137] Add recaptcha disabling via shell env --- gsoc/views.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index bbce70c0..97c4aac6 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -4,6 +4,7 @@ from .models import RegLink, validate_proposal_text, Comment import io +import os import urllib import json @@ -177,20 +178,30 @@ def register_view(request): def new_comment(request): if request.method == 'POST': - recaptcha_response = request.POST.get('g-recaptcha-response') - url = 'https://www.google.com/recaptcha/api/siteverify' - payload = { - 'secret': settings.RECAPTCHA_PRIVATE_KEY, - 'response': recaptcha_response - } - data = urllib.parse.urlencode(payload).encode() - req = urllib.request.Request(url, data=data) - - response = urllib.request.urlopen(req) - result = json.loads(response.read().decode()) - - if (result['success'] and result['action'] == 'comment' - and result['score'] >= settings.RECAPTCHA_THRESHOLD): + # set environment variable `DISABLE_RECAPTCHA` to disable recaptcha + # verification and delete the variable to enable recaptcha verification + disable_recaptcha = os.getenv('DISABLE_RECAPTCHA', None) + + if not disable_recaptcha: + recaptcha_response = request.POST.get('g-recaptcha-response') + url = 'https://www.google.com/recaptcha/api/siteverify' + payload = { + 'secret': settings.RECAPTCHA_PRIVATE_KEY, + 'response': recaptcha_response + } + data = urllib.parse.urlencode(payload).encode() + req = urllib.request.Request(url, data=data) + + response = urllib.request.urlopen(req) + result = json.loads(response.read().decode()) + result['score'] = 0.1 + + flag = True + if not disable_recaptcha: + flag = (result['success'] and result['action'] == 'comment' + and result['score'] >= settings.RECAPTCHA_THRESHOLD) + + if flag: # if score greater than threshold allow to add comment = request.POST.get('comment') article_pk = request.POST.get('article') From 4d18f4310927e5128cf1ce170e2c5de1f810dd43 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 12 May 2019 17:34:03 +0530 Subject: [PATCH 0131/1137] Add recaptcha to all pages --- gsoc/static/js/comments.js | 16 +++++++++++++++- gsoc/static/js/recaptcha.js | 13 ------------- gsoc/templates/base.html | 2 +- gsoc/templates/contact.html | 10 +++++++++- gsoc/templates/gettingstarted.html | 10 +++++++++- gsoc/templates/homepage.html | 8 ++++++++ gsoc/templates/ideas.html | 10 +++++++++- gsoc/templates/mentors.html | 10 +++++++++- gsoc/templates/myprofile.html | 8 ++++++++ gsoc/templates/schedule.html | 10 +++++++++- gsoc/templates/students.html | 8 ++++++++ gsoc/views.py | 1 - 12 files changed, 85 insertions(+), 21 deletions(-) delete mode 100644 gsoc/static/js/recaptcha.js diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index 7326a467..ec59d5b0 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -35,4 +35,18 @@ function copyCommentUrl(commentPk) { comment.classList.add('selected') } } -}()); \ No newline at end of file +}()); + +grecaptcha.ready(function() { + grecaptcha.execute('6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj', {action: 'comment'}).then(function(token) { + forms = document.getElementsByClassName('comment-form'); + for (var i=0; i - + {% block js %}{% endblock %} diff --git a/gsoc/templates/contact.html b/gsoc/templates/contact.html index ff8e8073..e8aeee2b 100644 --- a/gsoc/templates/contact.html +++ b/gsoc/templates/contact.html @@ -145,4 +145,12 @@

    -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/gettingstarted.html b/gsoc/templates/gettingstarted.html index ea7527c8..fcfd1705 100644 --- a/gsoc/templates/gettingstarted.html +++ b/gsoc/templates/gettingstarted.html @@ -120,4 +120,12 @@

    -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/homepage.html b/gsoc/templates/homepage.html index 3e0b89a6..995daa63 100644 --- a/gsoc/templates/homepage.html +++ b/gsoc/templates/homepage.html @@ -86,3 +86,11 @@

    Other Stuff

    {% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/ideas.html b/gsoc/templates/ideas.html index 78a1e979..88adc084 100644 --- a/gsoc/templates/ideas.html +++ b/gsoc/templates/ideas.html @@ -349,4 +349,12 @@

    -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/mentors.html b/gsoc/templates/mentors.html index 1fb97d44..e1dac41b 100644 --- a/gsoc/templates/mentors.html +++ b/gsoc/templates/mentors.html @@ -323,4 +323,12 @@

    2. Project name

    -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index 3d403bfc..de206984 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -81,3 +81,11 @@

    Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!

    {% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/schedule.html b/gsoc/templates/schedule.html index 3a1abd43..80290952 100644 --- a/gsoc/templates/schedule.html +++ b/gsoc/templates/schedule.html @@ -46,4 +46,12 @@

    Schedule

    You are viewing calendar at GMT {% time_zone 1 %} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/templates/students.html b/gsoc/templates/students.html index 40c8ac44..41a7ee92 100644 --- a/gsoc/templates/students.html +++ b/gsoc/templates/students.html @@ -270,3 +270,11 @@

    How do I apply?

    what you're proposing. We reject a lot of students who haven't listened to mentor feedback. --> {% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gsoc/views.py b/gsoc/views.py index 97c4aac6..6edb2f39 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -194,7 +194,6 @@ def new_comment(request): response = urllib.request.urlopen(req) result = json.loads(response.read().decode()) - result['score'] = 0.1 flag = True if not disable_recaptcha: From aa3c25ab71dc70d9f59d66989186ea240c9047f6 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 15:50:20 -0600 Subject: [PATCH 0132/1137] Update invite.html --- gsoc/templates/email/invite.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index c6fe5693..b014fb3b 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,8 +1,18 @@ Welcome to GSoC with the Python Software Foundation!

    +All students are required to post weekly, there are 2 types of posts students will have to make, the first is the weekly check-in. For a weekly check-in every student will have to answer these 3 questions in a post; with each answer being <100 words.
    +
    + 1. What did you do this week?
    + 2. What is coming up next?
    + 3. Did you get stuck anywhere?
    +
    +The second post is a blog post, here a student will be required to go into some detial on what they are working on, what they struggle with, and what solutions they have come to. There is no formal structure to this and every student is welcome to use their own style but the above three questions should be answered in the blog post at some point.
    +
    +This year we are introducing our new GSoC blogging platform, this platform will be worked on during GSoC so please report any bugs, issues, or sujestions to https://github.com/python-gsoc/python-blogs/issues or email gsoc-admins@python.org The site will be updated every tuesday/wednesday so if you dont get your blogs in on time you may encounter bugs and otherwise, so make sure you get your posts in on time!
    +
    To register you will need to goto {{ register_link }}

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    Thank you
    -Python GSoC Team!
    \ No newline at end of file +Python GSoC Team!
    From df97be96b185e3ff63696ff6535a685fe3064bd1 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 15:55:09 -0600 Subject: [PATCH 0133/1137] Update invite.html --- gsoc/templates/email/invite.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index b014fb3b..de0de828 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -6,11 +6,11 @@  2. What is coming up next?
     3. Did you get stuck anywhere?

    -The second post is a blog post, here a student will be required to go into some detial on what they are working on, what they struggle with, and what solutions they have come to. There is no formal structure to this and every student is welcome to use their own style but the above three questions should be answered in the blog post at some point.
    +The second post is a blog post, here a student will be required to go into some detail on what they are working on, what they struggle with, and what solutions they have come to. There is no formal structure to this and every student is welcome to use their own style but the above three questions should be answered in the blog post at some point.

    -This year we are introducing our new GSoC blogging platform, this platform will be worked on during GSoC so please report any bugs, issues, or sujestions to https://github.com/python-gsoc/python-blogs/issues or email gsoc-admins@python.org The site will be updated every tuesday/wednesday so if you dont get your blogs in on time you may encounter bugs and otherwise, so make sure you get your posts in on time!
    +This year we are introducing our new GSoC blogging platform, this platform will be worked on during GSoC so please report any bugs, issues, or suggestions to https://github.com/python-gsoc/python-blogs/issues or email gsoc-admins@python.org The site will be updated every Tuesday/Wednesday so if you don't get your blogs in on time you may encounter bugs and otherwise, so make sure you get your posts in on time!

    -To register you will need to goto {{ register_link }}
    +To register you will need to go to {{ register_link }}

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From edf693a8a28312855a8e1da3d1d8fb599ea66b07 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 20:36:31 -0600 Subject: [PATCH 0134/1137] Update register.html --- gsoc/templates/registration/register.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 34ad47b3..d8701953 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -20,7 +20,7 @@

    Sign up for a PSF GSoC account!




    - I accept the Python Community Code of Conduct for the duration of the program.
    + I accept the Python Community Code of Conduct for the duration of the program.
    {% endif %} From e3aebb75af04f2144642b8dcd87c5c218e1fffcf Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 21:18:52 -0600 Subject: [PATCH 0135/1137] Update settings.py --- gsoc/settings.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 4a65b18a..928f3f00 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -32,10 +32,7 @@ def gettext(s): return s ALLOWED_HOSTS = ['*'] INTERNAL_IPS = ('127.0.0.1',) -if DEBUG: - INETLOCATION = 'http://localhost:8000' -else: - INETLOCATION = 'https://blogs.python-gsoc.org' +INETLOCATION = 'https://blogs.python-gsoc.org' # Application definition ROOT_URLCONF = 'gsoc.urls' From 88b6304b7f62db354259bb0073dcb7ae1bb3afee Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 09:30:10 +0530 Subject: [PATCH 0136/1137] Fix add user bug and create recaptcha site key context processor --- blogs_list/views.py | 3 +-- gsoc/cms_toolbars.py | 2 +- gsoc/context_processors.py | 6 ++++++ gsoc/settings.py | 3 ++- gsoc/static/js/comments.js | 14 -------------- gsoc/templates/aldryn_newsblog/base.html | 15 +++++++++++++++ gsoc/templates/base.html | 2 +- gsoc/templates/contact.html | 2 +- gsoc/templates/gettingstarted.html | 2 +- gsoc/templates/homepage.html | 2 +- gsoc/templates/ideas.html | 2 +- gsoc/templates/mentors.html | 2 +- gsoc/templates/myprofile.html | 2 +- gsoc/templates/schedule.html | 2 +- gsoc/templates/students.html | 2 +- settings_local.py.template | 1 - 16 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 gsoc/context_processors.py diff --git a/blogs_list/views.py b/blogs_list/views.py index 717cd65a..6afba35a 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -26,8 +26,7 @@ def list_blogs(request): if profile.app_config: flag = True ns = profile.app_config.namespace - page = Page.objects.filter(application_namespace=ns) - page = page.filter(publisher_is_draft=False).first() + page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) student_name = profile.user.get_full_name() student_username = profile.user.username proposal_name = profile.accepted_proposal_pdf.name diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index fe5ed335..29472b7f 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -85,7 +85,7 @@ def add_goto_blog_button(self): user = getattr(self.request, 'user', None) if user and user.is_current_year_student(): profile = user.student_profile() - ns = profile.app_config.app_title + ns = profile.app_config.namespace page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) url = page.get_absolute_url() self.toolbar.add_button(_('My Blog'), url, side=self.toolbar.RIGHT) diff --git a/gsoc/context_processors.py b/gsoc/context_processors.py new file mode 100644 index 00000000..60d6362c --- /dev/null +++ b/gsoc/context_processors.py @@ -0,0 +1,6 @@ +from .settings import RECAPTCHA_PUBLIC_KEY + +def recaptcha_site_key(request): + return { + 'RECAPTCHA_SITE_KEY': RECAPTCHA_PUBLIC_KEY, + } \ No newline at end of file diff --git a/gsoc/settings.py b/gsoc/settings.py index 928f3f00..94b61957 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -83,7 +83,8 @@ def gettext(s): return s 'django.template.context_processors.tz', 'sekizai.context_processors.sekizai', 'django.template.context_processors.static', - 'cms.context_processors.cms_settings' + 'cms.context_processors.cms_settings', + 'gsoc.context_processors.recaptcha_site_key', ], 'loaders': [ 'django.template.loaders.filesystem.Loader', diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index ec59d5b0..e93bc9f4 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -36,17 +36,3 @@ function copyCommentUrl(commentPk) { } } }()); - -grecaptcha.ready(function() { - grecaptcha.execute('6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj', {action: 'comment'}).then(function(token) { - forms = document.getElementsByClassName('comment-form'); - for (var i=0; i + {% endblock %} \ No newline at end of file diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index eed14315..397ef617 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -17,7 +17,7 @@ - + {% load static %} {% block head %}{% endblock %} diff --git a/gsoc/templates/contact.html b/gsoc/templates/contact.html index e8aeee2b..b35a2b98 100644 --- a/gsoc/templates/contact.html +++ b/gsoc/templates/contact.html @@ -150,7 +150,7 @@

    {% block js %} {% endblock %} diff --git a/gsoc/templates/gettingstarted.html b/gsoc/templates/gettingstarted.html index fcfd1705..281b2b5c 100644 --- a/gsoc/templates/gettingstarted.html +++ b/gsoc/templates/gettingstarted.html @@ -125,7 +125,7 @@

    {% block js %} {% endblock %} diff --git a/gsoc/templates/homepage.html b/gsoc/templates/homepage.html index 995daa63..295c991f 100644 --- a/gsoc/templates/homepage.html +++ b/gsoc/templates/homepage.html @@ -90,7 +90,7 @@

    Other Stuff

    {% block js %} {% endblock %} diff --git a/gsoc/templates/ideas.html b/gsoc/templates/ideas.html index 88adc084..8daf6c5e 100644 --- a/gsoc/templates/ideas.html +++ b/gsoc/templates/ideas.html @@ -354,7 +354,7 @@

    {% block js %} {% endblock %} diff --git a/gsoc/templates/mentors.html b/gsoc/templates/mentors.html index e1dac41b..2cda44c2 100644 --- a/gsoc/templates/mentors.html +++ b/gsoc/templates/mentors.html @@ -328,7 +328,7 @@

    2. Project name

    {% block js %} {% endblock %} diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index de206984..5227da0e 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -85,7 +85,7 @@

    Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!

    {% block js %} {% endblock %} diff --git a/gsoc/templates/schedule.html b/gsoc/templates/schedule.html index 80290952..de61407c 100644 --- a/gsoc/templates/schedule.html +++ b/gsoc/templates/schedule.html @@ -51,7 +51,7 @@

    Schedule

    {% block js %} {% endblock %} diff --git a/gsoc/templates/students.html b/gsoc/templates/students.html index 41a7ee92..0ea194e3 100644 --- a/gsoc/templates/students.html +++ b/gsoc/templates/students.html @@ -274,7 +274,7 @@

    How do I apply?

    {% block js %} {% endblock %} diff --git a/settings_local.py.template b/settings_local.py.template index 5a80db98..c3a00374 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -49,7 +49,6 @@ EMAIL_PORT = 25 REPLY_EMAIL = 'gsoc-admins@python.org' # reCAPTCHA settings -# update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually RECAPTCHA_PRIVATE_KEY = '6LdAVqIUAAAAACv_tNPudwx_pD9dbjtwvr3WwQ9Y' RECAPTCHA_PUBLIC_KEY = '6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj' RECAPTCHA_THRESHOLD = 0.5 \ No newline at end of file From 02b62a48468af1a437bc18a6dfd93873bfcaad06 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 09:41:13 +0530 Subject: [PATCH 0137/1137] Fix pep8 warnings --- gsoc/context_processors.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/context_processors.py b/gsoc/context_processors.py index 60d6362c..13013a74 100644 --- a/gsoc/context_processors.py +++ b/gsoc/context_processors.py @@ -1,6 +1,7 @@ from .settings import RECAPTCHA_PUBLIC_KEY + def recaptcha_site_key(request): return { 'RECAPTCHA_SITE_KEY': RECAPTCHA_PUBLIC_KEY, - } \ No newline at end of file + } From 68ea7c507afbcdd38189ff57258097b3726c7228 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 23:12:43 -0600 Subject: [PATCH 0138/1137] Update list_view.html --- blogs_list/templates/list_view.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blogs_list/templates/list_view.html b/blogs_list/templates/list_view.html index 1c31a6d9..bb70d2a6 100644 --- a/blogs_list/templates/list_view.html +++ b/blogs_list/templates/list_view.html @@ -15,7 +15,7 @@

    {{ blog.title }}

    {{ blog.student }}
    {{ blog.suborg }}
    {% if blog.proposal %} - Proposal + Download Proposal {% else %} No proposal available {% endif %} @@ -28,4 +28,4 @@

    {{ blog.title }}

    {% endfor %} -{% endblock content %} \ No newline at end of file +{% endblock content %} From 446d8a3bf07cdea7c79bc9c6b2b27b1a1ac16340 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 23:20:17 -0600 Subject: [PATCH 0139/1137] Update proposal.js --- gsoc/static/js/proposal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js index f0637dc3..cf8e5b33 100644 --- a/gsoc/static/js/proposal.js +++ b/gsoc/static/js/proposal.js @@ -101,7 +101,7 @@ function uploadProposal() { if(privateData["emails"].length > 0 || privateData["possible_phone_numbers"].length > 0 || privateData["locations"].length > 0) { - let confirmText = "We seemed to have found these private data in your pdf file. Are you sure to proceed?"; + let confirmText = "We seemed to have found private data in your pdf file. WE DO NOT RECOMEND UPLOADING A PDF WITH PHONE NUMBERS OR EMAIL ADDRESSE'S AS THIS WILL BE SHOWN PUBLICALLY ON THE INTERNET. Are you sure to proceed?"; if (privateData['emails'].length > 0) confirmText += `
    Email addresses: ${privateData['emails'].toString()}` if(privateData['possible_phone_numbers'].length > 0) @@ -118,4 +118,4 @@ function uploadProposal() { setProposalUploadingStatus(false); console.log(err); }) -} \ No newline at end of file +} From 6ae7073388ecd6b5061048c49c5cb0f45d7b394a Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 23:21:02 -0600 Subject: [PATCH 0140/1137] Update proposal.js --- gsoc/static/js/proposal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js index cf8e5b33..2257bf90 100644 --- a/gsoc/static/js/proposal.js +++ b/gsoc/static/js/proposal.js @@ -101,7 +101,7 @@ function uploadProposal() { if(privateData["emails"].length > 0 || privateData["possible_phone_numbers"].length > 0 || privateData["locations"].length > 0) { - let confirmText = "We seemed to have found private data in your pdf file. WE DO NOT RECOMEND UPLOADING A PDF WITH PHONE NUMBERS OR EMAIL ADDRESSE'S AS THIS WILL BE SHOWN PUBLICALLY ON THE INTERNET. Are you sure to proceed?"; + let confirmText = "We seemed to have found private data in your pdf file. WE DO NOT RECOMEND UPLOADING A PDF WITH PHONE NUMBERS, PHYSICAL ADDRESS, OR EMAIL ADDRESSES AS THIS WILL BE SHOWN PUBLICALLY ON THE INTERNET. Are you sure to proceed?"; if (privateData['emails'].length > 0) confirmText += `
    Email addresses: ${privateData['emails'].toString()}` if(privateData['possible_phone_numbers'].length > 0) From af2ac678e2a9526b644e20ee4e1c6e9684d54d10 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 23:38:51 -0600 Subject: [PATCH 0141/1137] Update proposal.js --- gsoc/static/js/proposal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js index 2257bf90..76956840 100644 --- a/gsoc/static/js/proposal.js +++ b/gsoc/static/js/proposal.js @@ -50,7 +50,7 @@ function beforeUpload() { const offlineCancel = function(){ inPageInfo("Proposal upload canceled.") }; - const infoText = 'Please make sure there is no private data in your pdf file. Confirm?' + const infoText = 'Please make sure there is no private data in your pdf file as this WILL be shown publically on the internet. Confirm?' inPageInfo(infoText, false); showInfoBoxBtns(uploadProposal, offlineCancel) } From 8843946a82ee044d6df7949ac431070e665561f4 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 16 May 2019 23:44:17 -0600 Subject: [PATCH 0142/1137] Update list_view.html --- blogs_list/templates/list_view.html | 1 - 1 file changed, 1 deletion(-) diff --git a/blogs_list/templates/list_view.html b/blogs_list/templates/list_view.html index bb70d2a6..b07efc22 100644 --- a/blogs_list/templates/list_view.html +++ b/blogs_list/templates/list_view.html @@ -12,7 +12,6 @@

    GSoC {{ blogset.0 }}

    {{ blog.title }}

    -
    {{ blog.student }}
    {{ blog.suborg }}
    {% if blog.proposal %} Download Proposal From 52623fc9341fb75f56726e8417706e069dbce71c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 12:17:21 +0530 Subject: [PATCH 0143/1137] Add confirmed_proposal field for confirmation of proposal --- blogs_list/views.py | 4 ++-- .../0017_userprofile_proposal_confirmed.py | 18 ++++++++++++++++++ gsoc/models.py | 7 ++++++- gsoc/static/js/proposal.js | 1 + gsoc/urls.py | 1 + gsoc/views.py | 12 +++++++++++- project.db | Bin 1368064 -> 1368064 bytes 7 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 gsoc/migrations/0017_userprofile_proposal_confirmed.py diff --git a/blogs_list/views.py b/blogs_list/views.py index 6afba35a..11c25e10 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -29,8 +29,8 @@ def list_blogs(request): page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) student_name = profile.user.get_full_name() student_username = profile.user.username - proposal_name = profile.accepted_proposal_pdf.name - proposal_path = os.path.join(MEDIA_URL, proposal_name) + proposal_name = profile.accepted_proposal_pdf.name if profile.proposal_confirmed else None + proposal_path = os.path.join(MEDIA_URL, proposal_name) if proposal_name else None blogset.append({ 'title': profile.app_config.app_title, diff --git a/gsoc/migrations/0017_userprofile_proposal_confirmed.py b/gsoc/migrations/0017_userprofile_proposal_confirmed.py new file mode 100644 index 00000000..427299ff --- /dev/null +++ b/gsoc/migrations/0017_userprofile_proposal_confirmed.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.8 on 2019-05-17 05:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0016_comment'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='proposal_confirmed', + field=models.BooleanField(default=False), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 4b9f7f40..7c7862d9 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -68,6 +68,7 @@ class UserProfile(models.Model): gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) accepted_proposal_pdf = models.FileField(blank=True, null=True, upload_to=PROPOSALS_PATH) + proposal_confirmed = models.BooleanField(default=False) app_config = AppHookConfigField(NewsBlogConfig, verbose_name=_('Section'), blank=True, null=True,) @@ -76,10 +77,14 @@ class UserProfile(models.Model): objects = UserProfileManager() all_objects = models.Manager() + def confirm_proposal(self): + self.proposal_confirmed = True + self.save() + def has_proposal(self): try: - if self.userprofile_set.get(role=3).accepted_proposal_pdf.path: + if self.userprofile_set.get(role=3).proposal_confirmed: return True except BaseException: diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js index 76956840..e7ad2132 100644 --- a/gsoc/static/js/proposal.js +++ b/gsoc/static/js/proposal.js @@ -72,6 +72,7 @@ function showInfoBoxBtns(callback1, callback2) { } function onFindPrivateData(text) { const successCallback = function() { + axios.get('/confirm_proposal'); inPageInfo("Upload succeeded! Please refresh to get rid of the GIANT banner!",false); setProposalUploadingStatus(false); }; diff --git a/gsoc/urls.py b/gsoc/urls.py index 28e93dfa..cb0f7ff7 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -54,6 +54,7 @@ url('after-login/', gsoc.views.after_login_view, name='after-login'), url('upload-proposal/', gsoc.views.upload_proposal_view, name='upload-proposal'), url('cancel_proposal_upload/', gsoc.views.cancel_proposal_upload_view, name='cancel-proposal-upload'), + url('confirm_proposal/', gsoc.views.confirm_proposal_view, name='confirm-proposal'), ] # Add comment routes diff --git a/gsoc/views.py b/gsoc/views.py index 6edb2f39..3710cd0c 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -93,10 +93,11 @@ def upload_proposal_view(request): profile = request.user.student_profile() form = ProposalUploadForm(request.POST, request.FILES, instance=profile) if form.is_valid(): - form.save() + print("Scanning file for private data") scan_result = scan_proposal(file) if scan_result: resp['private_data'] = scan_result.message_dict + form.save() return JsonResponse(resp) @@ -108,6 +109,15 @@ def cancel_proposal_upload_view(request): return shortcuts.HttpResponse() +@decorators.login_required +@decorators.user_passes_test(is_user_accepted_student) +def confirm_proposal_view(request): + profile = request.user.student_profile() + if profile.accepted_proposal_pdf: + profile.confirm_proposal() + return shortcuts.HttpResponse() + + def register_view(request): reglink_id = request.GET.get('reglink_id', request.POST.get('reglink_id', '')) try: diff --git a/project.db b/project.db index ce5949c4484ec61f31c2f6c9c0517f73fcd67603..bd31faffdb7051fd4d967dd4910ce2515f2d6ba4 100644 GIT binary patch delta 664 zcmZY7Ur19?7y$6|@9y5a?cU>du36~ALQ|%jZRxa(NCOQ6N3?<{@J~9!(l)nD2!ai> z5cg0BJ5&^fP(3Px?n6w4pht^B>mi*FB}FgNYXp7v+By8r;rqS==MN{d63eW_?p1m^ z5W-Q=gAgJJY5DtsCZz*4)?^lkG@s^rX}I)leY@c4PwCXkMhZXt7YVkfIsm75-x7{v% z?}eW!UF15PBT4My2Qf!B?VCu)Q^{0fcw{u*2Xiu!9vJN# zOk5itNsYya4u!%;f}uz--0BOpv^BT1MGiNHBau+J*QKGmb41HHfc3eraVBm=RExxdoyjl#!THoH4yq()|0y; z69>gHd3;@*vtu``Cz$$C9TclswmLT^gSV`CmW|{_<>LwK74_w=%HB2c%&xj=85CXs zMPcnJ*R+xwQ7eK$GILZu-w?GpDhJ+rg}t>Q#d zOut+%iM`(cA3^TC^IA5`SZaIuN%%b+7_i?gm;qA-PyZIrF9=2DCB%1l2@)UqeomYS ULo=|ZQOXH5@xO$=n;?+*3zPD^rvLx| delta 558 zcmY+>O=uHA6ae7)+0D*of2Nxqtr8ClLUog^1gai9SP`P9)@nRR1xciOjuxdVC5f%J z=wj7~g9@Vb;!PxwIoN1b=rI%_dXdtCNX3KETM@*kHwV6fH;>`Hc~f01R2K`k)7~IL zco+;Jga|@x?oKqI529VUeJ{pXz|1e!GskkQhF3gqQc=q_zJ@0WZcgQ+nS6u+0TgIJ z2MJ&R2279y7O;T>T);sO@IXrCBmedMHk-7N{nO5x57{r$VkeAOMkY~r9_hdJQ5h`z zVLd2vO+Q4=KrbbtTJQavWHS=b_kXJv#7E?nOX4Z@;$ivbg1D>heej=C4Ia4;S3hHw z{p6RiHC>8_jNbl!GCMA>PKrK!SeBMjzFL?R=dirhv3l=!{2uvas|nkPEcpAvg9BYX|Q;f(f8J4;uRDw&29-d|CDWwD!TMi))jy<%0Z zjEk38tL=$L Date: Fri, 17 May 2019 12:40:38 +0530 Subject: [PATCH 0144/1137] Minor change --- gsoc/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 3710cd0c..7a3d75d1 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -93,11 +93,10 @@ def upload_proposal_view(request): profile = request.user.student_profile() form = ProposalUploadForm(request.POST, request.FILES, instance=profile) if form.is_valid(): - print("Scanning file for private data") + form.save() scan_result = scan_proposal(file) if scan_result: resp['private_data'] = scan_result.message_dict - form.save() return JsonResponse(resp) From df7c7cbe163627e0a7c847b5d21e7e7c4f80317f Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 17 May 2019 01:16:11 -0600 Subject: [PATCH 0145/1137] Update settings.py --- gsoc/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 94b61957..39c95f56 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -245,7 +245,7 @@ def gettext(s): return s if DEBUG: ERROR_LEVEL = 'DEBUG' else: - ERROR_LEVEL = 'INFO' + ERROR_LEVEL = 'WARNING' ERROR_HANDLERS = ['file', 'mail_admins'] From 8f71028c9dceb52f4f05d36fbf071f27c9ba9c8c Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 17 May 2019 01:17:07 -0600 Subject: [PATCH 0146/1137] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f73d2802..cacf7ad9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ logs/* # Project root static folder /static/ +/media/proposals/ # Pyenv .python-version From ac5f3994f573fa54ad48ff9e8ae8523f289648df Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 17 May 2019 01:26:36 -0600 Subject: [PATCH 0147/1137] Update settings.py --- gsoc/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 39c95f56..2207f237 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -243,7 +243,7 @@ def gettext(s): return s LOGGING_CONFIG = None if DEBUG: - ERROR_LEVEL = 'DEBUG' + ERROR_LEVEL = 'INFO' else: ERROR_LEVEL = 'WARNING' From 6690d333af6f7f430136202e2d59f4ff7148b579 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 13:10:49 +0530 Subject: [PATCH 0148/1137] Fix minor bug --- gsoc/static/js/proposal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js index e7ad2132..04dea14d 100644 --- a/gsoc/static/js/proposal.js +++ b/gsoc/static/js/proposal.js @@ -112,6 +112,7 @@ function uploadProposal() { onFindPrivateData(confirmText); return } + axios.get('/confirm_proposal'); setProposalUploadingStatus(false); inPageInfo('Proposal upload succeeded! Please refresh to get rid of the GIANT banner!', false); }) From 050acd461e5177ae062b7c14ae4d0295069fa194 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 15:34:48 +0530 Subject: [PATCH 0149/1137] Add all blogs feed --- blogs_list/feeds.py | 20 ++++++++++++++++++++ blogs_list/urls.py | 2 ++ 2 files changed, 22 insertions(+) create mode 100644 blogs_list/feeds.py diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py new file mode 100644 index 00000000..3d84e9ac --- /dev/null +++ b/blogs_list/feeds.py @@ -0,0 +1,20 @@ +from django.contrib.syndication.views import Feed + +from aldryn_newsblog.cms_appconfig import NewsBlogConfig +from cms.models import Page + + +class BlogsFeed(Feed): + title = "GSoC@PSF Blogs" + link = '/blogs/' + description = 'Updates on different student blogs of GSoC@PSF' + + def items(self): + return NewsBlogConfig.objects.all() + + def item_title(self, item): + return item.app_title + + def item_link(self, item): + p = Page.objects.get(application_namespace=item.namespace, publisher_is_draft=False) + return p.get_absolute_url() diff --git a/blogs_list/urls.py b/blogs_list/urls.py index 47daa90a..b6ade03f 100644 --- a/blogs_list/urls.py +++ b/blogs_list/urls.py @@ -1,7 +1,9 @@ from django.conf.urls import url from .views import list_blogs +from .feeds import BlogsFeed urlpatterns = [ url('^$', list_blogs, name='list_blogs'), + url('feed/', BlogsFeed(), name='feed') ] From d4da7579330b37b79d0b0bc999a79ac88cd3c692 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 15:40:05 +0530 Subject: [PATCH 0150/1137] Remove add user and scheduler button for non-superusers --- gsoc/cms_toolbars.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 29472b7f..0f2a8273 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -40,21 +40,25 @@ def add_admin_menu(self): sites_menu.add_link_item(site.name, url='http://%s' % site.domain, active=site.pk == self.current_site.pk) + user = getattr(self.request, 'user', None) + # admin self._admin_menu.add_sideframe_item(_('Administration'), url=admin_reverse('index')) # scheduler - self._admin_menu.add_sideframe_item(_('Schedulers'), url=admin_reverse('gsoc_scheduler_changelist')) + if user and user.is_superuser: + self._admin_menu.add_sideframe_item(_('Schedulers'), url=admin_reverse('gsoc_scheduler_changelist')) self._admin_menu.add_break(ADMINISTRATION_BREAK) # cms users settings self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change')) self._admin_menu.add_break(USER_SETTINGS_BREAK) - self._admin_menu.add_modal_item( - name='Add Users', - url=admin_reverse('gsoc_adduserlog_add'), - on_close=None, - ) + if user and user.is_superuser: + self._admin_menu.add_modal_item( + name='Add Users', + url=admin_reverse('gsoc_adduserlog_add'), + on_close=None, + ) # clipboard if self.toolbar.edit_mode_active: # True if the clipboard exists and there's plugins in it. From 9b4fe76fc6da944f73eb93899dd7d33f6475a6f6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 19:15:35 +0530 Subject: [PATCH 0151/1137] Remove the blog when a userprofile is deleted --- gsoc/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 7c7862d9..af356017 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -114,9 +114,17 @@ def student_profile(self): auth.models.User.add_to_class('is_current_year_student', is_current_year_student) auth.models.User.add_to_class('student_profile', student_profile) -# Auto Delete Redundant Proposal + +@receiver(models.signals.post_delete, sender=UserProfile) +def delete_blog(sender, instance, **kwargs): + """ + Deletes the blog of the deleted user if a student + """ + if instance.app_config: + instance.app_config.delete() +# Auto Delete Redundant Proposal @receiver(models.signals.post_delete, sender=UserProfile) def auto_delete_proposal_on_delete(sender, instance, **kwargs): """ From 08f76ee1c32eee08a614b74a2ccb9bd187e2da5e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 19:17:44 +0530 Subject: [PATCH 0152/1137] Fix pep8 warnings --- gsoc/cms_toolbars.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 0f2a8273..f1ca6d83 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -47,7 +47,8 @@ def add_admin_menu(self): # scheduler if user and user.is_superuser: - self._admin_menu.add_sideframe_item(_('Schedulers'), url=admin_reverse('gsoc_scheduler_changelist')) + self._admin_menu.add_sideframe_item(_('Schedulers'), + url=admin_reverse('gsoc_scheduler_changelist')) self._admin_menu.add_break(ADMINISTRATION_BREAK) # cms users settings From eb6ff117e2982b135d595d7ff0bc21763d94daac Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 17 May 2019 19:46:04 +0530 Subject: [PATCH 0153/1137] Shorten scheduler data in admin list display --- gsoc/admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index a2780f00..c0b899a3 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -253,10 +253,13 @@ def get_readonly_fields(self, request, obj=None): class SchedulerAdmin(admin.ModelAdmin): - list_display = ('command', 'data', 'success', 'last_error', 'created') + list_display = ('command', 'get_short_data', 'success', 'last_error', 'created') list_filter = ('command', 'success') sortable_by = ('created', 'last_error') + def get_short_data(self, obj): + return '{}...'.format(obj.data[:50]) + admin.site.register(Scheduler, SchedulerAdmin) From 8d9d03e7f61903239885a38daec6217decce83ac Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 07:11:36 +0530 Subject: [PATCH 0154/1137] Change queryset for blog --- blogs_list/feeds.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py index 3d84e9ac..b221a92e 100644 --- a/blogs_list/feeds.py +++ b/blogs_list/feeds.py @@ -1,6 +1,9 @@ from django.contrib.syndication.views import Feed +from gsoc.models import UserProfile, GsocYear + from aldryn_newsblog.cms_appconfig import NewsBlogConfig + from cms.models import Page @@ -10,7 +13,10 @@ class BlogsFeed(Feed): description = 'Updates on different student blogs of GSoC@PSF' def items(self): - return NewsBlogConfig.objects.all() + gsoc_year = GsocYear.objects.first() + ups = UserProfile.objects.filter(role=3, gsoc_year=gsoc_year).all() + pks = [_.app_config.pk for _ in ups] + return NewsBlogConfig.objects.filter(pk__in=pks).all() def item_title(self, item): return item.app_title From 9d84a6446dbd6f69344301e256e797bbcf799b8b Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 09:03:39 +0530 Subject: [PATCH 0155/1137] Fetch content in feeds --- blogs_list/feeds.py | 63 ++++++++++++++++++++++++++++++++++++++++----- gsoc/cms_wizards.py | 6 +++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py index b221a92e..1bd4603f 100644 --- a/blogs_list/feeds.py +++ b/blogs_list/feeds.py @@ -1,26 +1,77 @@ from django.contrib.syndication.views import Feed +from django.utils.feedgenerator import DefaultFeed +from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from django.test.client import RequestFactory +from django.template import RequestContext from gsoc.models import UserProfile, GsocYear from aldryn_newsblog.cms_appconfig import NewsBlogConfig -from cms.models import Page +from cms.models import Page, Site +from cms.plugin_rendering import ContentRenderer + + +def get_request(language=None): + request_factory = RequestFactory() + request = request_factory.get('/') + request.session = {} + request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE + + # Needed for plugin rendering. + request.current_page = None + request.user = AnonymousUser() + return request + + +class CorrectMimeTypeFeed(DefaultFeed): + content_type = 'application/xml; charset=utf-8' class BlogsFeed(Feed): title = "GSoC@PSF Blogs" link = '/blogs/' + feed_url = '/blogs/feed/' + feed_type = CorrectMimeTypeFeed description = 'Updates on different student blogs of GSoC@PSF' def items(self): gsoc_year = GsocYear.objects.first() ups = UserProfile.objects.filter(role=3, gsoc_year=gsoc_year).all() - pks = [_.app_config.pk for _ in ups] - return NewsBlogConfig.objects.filter(pk__in=pks).all() + articles = [] + for up in ups: + section = up.app_config + articles.extend(list(section.article_set.all())) + return articles + + def item_author_name(self, item): + return item.owner.username + + def item_author_email(self, item): + return item.owner.email def item_title(self, item): - return item.app_title + return item.title + + def item_description(self, item): + request = get_request() + c = ContentRenderer(request) + html = c.render_placeholder(item.content, RequestContext(request)) + return html + + def item_pubdate(self, item): + return item.publishing_date + + def item_guid(self, item): + site = Site.objects.first() + return 'http://{}{}'.format(site.domain, self.item_link(item)) + + def item_guid_is_permalink(self, item): + return True def item_link(self, item): - p = Page.objects.get(application_namespace=item.namespace, publisher_is_draft=False) - return p.get_absolute_url() + section = item.app_config + p = Page.objects.get(application_namespace=section.namespace, publisher_is_draft=False) + url = '{}{}/'.format(p.get_absolute_url(), item.slug, '/') + return url diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py index ac445489..cb83b065 100644 --- a/gsoc/cms_wizards.py +++ b/gsoc/cms_wizards.py @@ -1,3 +1,5 @@ +from .models import UserProfile + from django import forms from django.utils.translation import ugettext_lazy as _ @@ -48,6 +50,10 @@ def __init__(self, **kwargs): get_published_app_configs() userprofiles = self.user.userprofile_set.all() + + if self.user.is_superuser: + userprofiles = UserProfile.objects.all() + app_config_choices = [] for profile in userprofiles: app_config_choices.append((profile.app_config.pk, profile.app_config.get_app_title())) From 7a9994356036d7bc2461a1e3ee9ecd031dc32e72 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 09:44:47 +0530 Subject: [PATCH 0156/1137] Change permissions for toolbar buttons --- gsoc/admin.py | 12 ++++++++---- gsoc/cms_toolbars.py | 29 ++++++++++++++++------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index c0b899a3..a41b307d 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -50,6 +50,7 @@ def return_func(self, request, obj=None, **kwargs): ori_fieldsets = getattr(self, 'fieldsets', ()) or () self.readonly_fields = ori_readonly_fields self.fieldsets = ori_fieldsets + self.actions = None form = ori_get_form(self, request, obj, **kwargs) if is_request_by_student: self.fieldsets = ( @@ -60,14 +61,13 @@ def return_func(self, request, obj=None, **kwargs): 'is_published', 'is_featured', 'featured_image', - 'lead_in', )}), # (_('Meta Options'), # {'classes': ('collapse',), # 'fields':()}), (_('Advanced Settings'), - {'classes': ('collapse',), - 'fields': ('app_config',)}), + {'classes': ('collapse',), + 'fields': ('app_config',)}), ) self.readonly_fields = ( 'author', @@ -80,7 +80,7 @@ def return_func(self, request, obj=None, **kwargs): 'meta_keywords', 'owner', ) - return form + return form return return_func @@ -200,12 +200,16 @@ def Article_get_queryset(self, request): return qs +def has_add_permission(self, request, obj=None): + return False + ArticleAdmin.save_model = Article_save_model ArticleAdmin.delete_model = Article_delete_model ArticleAdmin.get_queryset = Article_get_queryset ArticleAdmin.get_form = article_get_form() ArticleAdmin.add_view = Article_add_view ArticleAdmin.change_view = Article_change_view +ArticleAdmin.has_add_permission = has_add_permission admin.site.unregister(Article) admin.site.register(Article, ArticleAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index f1ca6d83..6a6b21f0 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -140,15 +140,18 @@ def populate(self): 'aldryn_newsblog.add_newsblogconfig') config_perms = [change_config_perm, add_config_perm] - add_article_perm = False - change_article_perm = False + change_article_perm = False userprofiles = user.userprofile_set.all() - for profile in userprofiles: - if profile.app_config == config: - add_article_perm = True - change_article_perm = True - break + if user.is_superuser: + change_article_perm = True + else: + for profile in userprofiles: + if profile.app_config == config: + change_article_perm = True + break + + add_article_perm = user.is_superuser if article else False delete_article_perm = user.is_superuser if article else False article_perms = [change_article_perm, add_article_perm, @@ -173,12 +176,12 @@ def populate(self): **url_args) menu.add_sideframe_item(_('Article list'), url=url) - if add_article_perm: - url_args = {'app_config': config.pk, 'owner': user.pk, } - if language: - url_args.update({'language': language, }) - url = get_admin_url('aldryn_newsblog_article_add', **url_args) - menu.add_modal_item(_('Add new article'), url=url) + # if add_article_perm: + # url_args = {'app_config': config.pk, 'owner': user.pk, } + # if language: + # url_args.update({'language': language, }) + # url = get_admin_url('aldryn_newsblog_article_add', **url_args) + # menu.add_modal_item(_('Add new article'), url=url) if change_article_perm and article: url_args = {} From 60634b92e86eaa624f6059b3c6978f8f35230552 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 10:11:20 +0530 Subject: [PATCH 0157/1137] Change template for article and give permission to students to create blogs --- gsoc/cms_wizards.py | 2 +- gsoc/templates/aldryn_newsblog/includes/article.html | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py index cb83b065..42bd564b 100644 --- a/gsoc/cms_wizards.py +++ b/gsoc/cms_wizards.py @@ -73,7 +73,7 @@ def save(self, commit=True): # If 'content' field has value, create a TextPlugin with same and add it to the PlaceholderField content = clean_html(self.cleaned_data.get('content', ''), False) - if content and permissions.has_plugin_permission(self.user, 'TextPlugin', 'add'): + if content: add_plugin( placeholder=article.content, plugin_type='TextPlugin', diff --git a/gsoc/templates/aldryn_newsblog/includes/article.html b/gsoc/templates/aldryn_newsblog/includes/article.html index 1fdddd4a..cffa0875 100644 --- a/gsoc/templates/aldryn_newsblog/includes/article.html +++ b/gsoc/templates/aldryn_newsblog/includes/article.html @@ -42,6 +42,8 @@

    {% if not detail_view %}
    {% render_model article "lead_in" "" "" "truncatewords_html:'20'" %} + {% render_placeholder article.content language placeholder_language as content %} + {{ content|truncatewords_html:20 }}
    Read More ... {% endif %} From 4cd898bf09bb7d9d7c665e8008a40e5c66018a75 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 10:23:47 +0530 Subject: [PATCH 0158/1137] Auto publish blogs --- gsoc/cms_wizards.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py index 42bd564b..5d9427bd 100644 --- a/gsoc/cms_wizards.py +++ b/gsoc/cms_wizards.py @@ -8,6 +8,8 @@ from djangocms_text_ckeditor.html import clean_html +from aldryn_newsblog.models import Article + from aldryn_newsblog.cms_appconfig import NewsBlogConfig from aldryn_newsblog.cms_wizards import ( NewsBlogArticleWizard, @@ -69,6 +71,7 @@ def save(self, commit=True): article = super(CreateNewsBlogArticleForm, self).save(commit=False) article.owner = self.user article.app_config = NewsBlogConfig.objects.filter(pk=self.cleaned_data['app_config']).first() + article.is_published = True article.save() # If 'content' field has value, create a TextPlugin with same and add it to the PlaceholderField From 67c7f240611115d28f6c1825d5614395c69d7001 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 10:32:46 +0530 Subject: [PATCH 0159/1137] Fix pep8 warnings --- blogs_list/feeds.py | 2 +- gsoc/admin.py | 2 +- gsoc/cms_toolbars.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py index 1bd4603f..20617328 100644 --- a/blogs_list/feeds.py +++ b/blogs_list/feeds.py @@ -53,7 +53,7 @@ def item_author_email(self, item): def item_title(self, item): return item.title - + def item_description(self, item): request = get_request() c = ContentRenderer(request) diff --git a/gsoc/admin.py b/gsoc/admin.py index a41b307d..c58e42ec 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -67,7 +67,7 @@ def return_func(self, request, obj=None, **kwargs): # 'fields':()}), (_('Advanced Settings'), {'classes': ('collapse',), - 'fields': ('app_config',)}), + 'fields': ('app_config',)}), ) self.readonly_fields = ( 'author', diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 6a6b21f0..59bf59ee 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -140,7 +140,7 @@ def populate(self): 'aldryn_newsblog.add_newsblogconfig') config_perms = [change_config_perm, add_config_perm] - change_article_perm = False + change_article_perm = False userprofiles = user.userprofile_set.all() if user.is_superuser: From a94f57959f55750b395e8c273e9b1c25f51e43fc Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 11:02:45 +0530 Subject: [PATCH 0160/1137] Fix typo --- gsoc/admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index c58e42ec..4f452301 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -50,7 +50,7 @@ def return_func(self, request, obj=None, **kwargs): ori_fieldsets = getattr(self, 'fieldsets', ()) or () self.readonly_fields = ori_readonly_fields self.fieldsets = ori_fieldsets - self.actions = None + # self.actions = None form = ori_get_form(self, request, obj, **kwargs) if is_request_by_student: self.fieldsets = ( @@ -80,7 +80,7 @@ def return_func(self, request, obj=None, **kwargs): 'meta_keywords', 'owner', ) - return form + return form return return_func @@ -206,7 +206,7 @@ def has_add_permission(self, request, obj=None): ArticleAdmin.save_model = Article_save_model ArticleAdmin.delete_model = Article_delete_model ArticleAdmin.get_queryset = Article_get_queryset -ArticleAdmin.get_form = article_get_form() +# ArticleAdmin.get_form = article_get_form() ArticleAdmin.add_view = Article_add_view ArticleAdmin.change_view = Article_change_view ArticleAdmin.has_add_permission = has_add_permission From 476e5420b936c95859d1c1789fc57f6e3216e047 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 18 May 2019 13:08:54 +0530 Subject: [PATCH 0161/1137] Fix debug warnings --- gsoc/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 397ef617..ad2ca8d5 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -7,7 +7,7 @@ Python GSoC – Splash - + From 11a1a1d1803e06429576481ee115e4a6c5f341ba Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 21 May 2019 06:29:28 +0530 Subject: [PATCH 0162/1137] Fix file upload error if filename > 100 --- gsoc/admin.py | 6 ++++-- gsoc/views.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 4f452301..b409d4ae 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -271,13 +271,15 @@ def get_short_data(self, obj): class HiddenUserProfileAdmin(admin.ModelAdmin): list_display = ('user', 'gsoc_year', 'suborg_full_name', 'hidden') list_filter = ('hidden', ) - readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'app_config', + 'proposal_confirmed') fieldsets = ( ('Unhide', { 'fields': ('hidden', ) }), ('User Profile Details', { - 'fields': ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'app_config') + 'fields': ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'proposal_confirmed', + 'app_config') }) ) diff --git a/gsoc/views.py b/gsoc/views.py index 7a3d75d1..dbb74aa1 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -7,6 +7,7 @@ import os import urllib import json +import uuid from django.contrib import messages from django.contrib.auth import decorators, password_validation, validators @@ -49,6 +50,9 @@ def convert_pdf_to_txt(f): def is_user_accepted_student(user): return user.is_current_year_student() +def is_superuser(user): + return user.is_superuser + def scan_proposal(file): """ @@ -88,6 +92,10 @@ def upload_proposal_view(request): if request.method == 'POST': file = request.FILES.get('accepted_proposal_pdf') resp['file_type_valid'] = file and file.name.endswith('.pdf') + if len(file.name) > 100 and resp['file_type_valid']: + file.name = str(uuid.uuid4()) + '.pdf' + print(file.name) + resp['file_type_valid'] = file and file.name.endswith('.pdf') resp['file_not_too_large'] = file.size < 20 * 1024 * 1024 if resp['file_type_valid'] and resp['file_not_too_large']: profile = request.user.student_profile() From d892fed11ba059b125b2bae0086896478c5ce277 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 21 May 2019 06:29:57 +0530 Subject: [PATCH 0163/1137] Add comment delete for superusers --- gsoc/static/css/comments.css | 4 ++++ gsoc/static/js/comments.js | 13 +++++++++++++ gsoc/templates/aldryn_newsblog/base.html | 1 + .../aldryn_newsblog/includes/comments.html | 14 ++++++++++++++ gsoc/urls.py | 1 + gsoc/views.py | 16 ++++++++++++++++ 6 files changed, 49 insertions(+) diff --git a/gsoc/static/css/comments.css b/gsoc/static/css/comments.css index f1354e66..92bee2d7 100644 --- a/gsoc/static/css/comments.css +++ b/gsoc/static/css/comments.css @@ -48,6 +48,10 @@ cursor: pointer; } +.comment-container .c-actions .delete { + cursor: pointer; +} + .comment-container .c-content { font-size: 14px; } \ No newline at end of file diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index e93bc9f4..14fad216 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -27,6 +27,19 @@ function copyCommentUrl(commentPk) { }) } +function deleteComment(commentPk) { + var del = document.getElementById(`delete-${commentPk}`); + if (del.innerHTML == 'Delete') { + del.innerHTML = 'Confirm Delete?'; + sleep(3000).then(() => { + del.innerHTML = 'Delete'; + }) + } + else if (del.innerHTML == 'Confirm Delete?') { + document.getElementById(`delete-form-${commentPk}`).submit(); + } +} + (function markSelected() { var parts = window.location.href.split('#') if (parts.length == 2 && parts[1].split('-')[0] == 'comment') { diff --git a/gsoc/templates/aldryn_newsblog/base.html b/gsoc/templates/aldryn_newsblog/base.html index 36151cbe..316ba3b5 100644 --- a/gsoc/templates/aldryn_newsblog/base.html +++ b/gsoc/templates/aldryn_newsblog/base.html @@ -13,6 +13,7 @@ {% endblock content %} {% block js %} + + + + Delete + + {% endif %}

    diff --git a/gsoc/urls.py b/gsoc/urls.py index cb0f7ff7..ea562449 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -60,4 +60,5 @@ # Add comment routes urlpatterns += [ url('comment/new/', gsoc.views.new_comment, name='new_comment'), + url('comment/delete/', gsoc.views.delete_comment, name='delete_comment') ] diff --git a/gsoc/views.py b/gsoc/views.py index dbb74aa1..1e389705 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -257,3 +257,19 @@ def new_comment(request): return redirect(redirect_path) else: return redirect('/') + + +@decorators.user_passes_test(is_superuser) +def delete_comment(request): + if request.method == 'POST': + pk = request.POST.get('comment_pk') + redirect_path = request.POST.get('redirect') + + if pk: + comment = Comment.objects.get(pk=pk) + comment.delete() + + if redirect_path: + return redirect(redirect_path) + else: + return redirect('/') From 131efc0398e7fe551d4b131b5b9cdfa20292c76b Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 21 May 2019 06:33:33 +0530 Subject: [PATCH 0164/1137] Fix pep8 warnings --- gsoc/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 1e389705..4e25100d 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -50,6 +50,7 @@ def convert_pdf_to_txt(f): def is_user_accepted_student(user): return user.is_current_year_student() + def is_superuser(user): return user.is_superuser @@ -264,11 +265,11 @@ def delete_comment(request): if request.method == 'POST': pk = request.POST.get('comment_pk') redirect_path = request.POST.get('redirect') - + if pk: comment = Comment.objects.get(pk=pk) comment.delete() - + if redirect_path: return redirect(redirect_path) else: From a9608ce2f43bf7c13671eec64d3960aaf129d11e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 21 May 2019 06:35:12 +0530 Subject: [PATCH 0165/1137] Remove axios --- gsoc/templates/aldryn_newsblog/base.html | 1 - 1 file changed, 1 deletion(-) diff --git a/gsoc/templates/aldryn_newsblog/base.html b/gsoc/templates/aldryn_newsblog/base.html index 316ba3b5..36151cbe 100644 --- a/gsoc/templates/aldryn_newsblog/base.html +++ b/gsoc/templates/aldryn_newsblog/base.html @@ -13,7 +13,6 @@ {% endblock content %} {% block js %} - +<<<<<<< HEAD +======= +>>>>>>> Add view for the suborg appliaction form {% block js %}{% endblock %} diff --git a/suborg_application/__init__.py b/suborg_application/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/suborg_application/admin.py b/suborg_application/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/suborg_application/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/suborg_application/apps.py b/suborg_application/apps.py new file mode 100644 index 00000000..9a677c4c --- /dev/null +++ b/suborg_application/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SuborgApplicationConfig(AppConfig): + name = 'suborg_application' diff --git a/suborg_application/cms_apps.py b/suborg_application/cms_apps.py new file mode 100644 index 00000000..d756fd18 --- /dev/null +++ b/suborg_application/cms_apps.py @@ -0,0 +1,14 @@ +from cms.app_base import CMSApp +from cms.apphook_pool import apphook_pool +from django.utils.translation import ugettext_lazy as _ + + +class suborg_application(CMSApp): + app_name = "suborg_application" + name = _("Suborg Application") + + def get_urls(self, page=None, language=None, **kwargs): + return ["suborg_application.urls"] + + +apphook_pool.register(suborg_application) diff --git a/suborg_application/migrations/__init__.py b/suborg_application/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/suborg_application/models.py b/suborg_application/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/suborg_application/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/suborg_application/templates/register_suborg.html b/suborg_application/templates/register_suborg.html new file mode 100644 index 00000000..e0b2f4bd --- /dev/null +++ b/suborg_application/templates/register_suborg.html @@ -0,0 +1,37 @@ +{% extends CMS_TEMPLATE %} + +{% block head %} + +{% endblock %} + +{% block content %} +
    +
    + {{ form }} + + +
    +{% endblock content %} + +{% block js %} + +{% endblock %} \ No newline at end of file diff --git a/suborg_application/tests.py b/suborg_application/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/suborg_application/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/suborg_application/urls.py b/suborg_application/urls.py new file mode 100644 index 00000000..72213d3a --- /dev/null +++ b/suborg_application/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url + +from .views import register_suborg + +urlpatterns = [ + url('^$', register_suborg, name='register_suborg'), +] diff --git a/suborg_application/views.py b/suborg_application/views.py new file mode 100644 index 00000000..a3cb1f1c --- /dev/null +++ b/suborg_application/views.py @@ -0,0 +1,11 @@ +from gsoc.forms import SubOrgApplicationForm + +from django.shortcuts import render + + +def register_suborg(request): + if request.method == 'GET': + form = SubOrgApplicationForm() + return render(request, 'register_suborg.html', { + 'form': form, + }) \ No newline at end of file From c7b55eff1d1ab717701c44ede0f7d439515d0b70 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 13 Jun 2019 16:26:09 +0530 Subject: [PATCH 0298/1137] Resolve merge conflict --- gsoc/admin.py | 7 +------ gsoc/forms.py | 11 ++++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index b2f15718..17b4eafa 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,11 +1,6 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, -<<<<<<< HEAD - BlogPostDueDate, Builder, Timeline, ArticleReview, Event) + BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails) from .forms import UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm -======= - SubOrgDetails) -from .forms import UserProfileForm, UserDetailsForm, RegLinkForm ->>>>>>> Add model for SubOrg details from django.contrib.auth.models import User from django.contrib import admin diff --git a/gsoc/forms.py b/gsoc/forms.py index 68b2f8c2..63840da8 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,8 +1,5 @@ -<<<<<<< HEAD -from .models import UserDetails, UserProfile, RegLink, BlogPostDueDate, Event -======= -from .models import UserDetails, UserProfile, RegLink, SubOrgDetails ->>>>>>> Add model for SubOrg details +from .models import (UserDetails, UserProfile, RegLink, BlogPostDueDate, Event, + SubOrgDetails) from django import forms @@ -41,13 +38,13 @@ class Meta: fields = ('email', 'user_role', 'user_suborg', 'user_gsoc_year') -class BlogPostDueDateForm(ModelForm): +class BlogPostDueDateForm(forms.ModelForm): class Meta: model = BlogPostDueDate fields = ('title', 'date') -class EventForm(ModelForm): +class EventForm(forms.ModelForm): class Meta: model = Event fields = ('title', 'start_date', 'end_date') From 25cb7d657e1dad613ba78458d0fd30cee311f77e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 13 Jun 2019 16:38:25 +0530 Subject: [PATCH 0299/1137] Migrate db --- gsoc/migrations/0029_suborgdetails.py | 43 ++++++++++++++++++++++++++ project.db | Bin 1449984 -> 1495040 bytes 2 files changed, 43 insertions(+) create mode 100644 gsoc/migrations/0029_suborgdetails.py diff --git a/gsoc/migrations/0029_suborgdetails.py b/gsoc/migrations/0029_suborgdetails.py new file mode 100644 index 00000000..985a2b77 --- /dev/null +++ b/gsoc/migrations/0029_suborgdetails.py @@ -0,0 +1,43 @@ +# Generated by Django 2.1.9 on 2019-06-13 11:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0028_blogpostduedate_title'), + ] + + operations = [ + migrations.CreateModel( + name='SubOrgDetails', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reason_for_participation', models.TextField(verbose_name='Why does your org want to participate in Google Summer of Code?')), + ('suborg_admin_email', models.EmailField(max_length=254, verbose_name='Suborg admin email')), + ('mentors_student_engagement', models.TextField(verbose_name='How will you keep mentors engaged with their students?')), + ('students_on_schedule', models.TextField(verbose_name='How will you help your students stay on schedule to complete their projects?')), + ('students_involvement_gsoc', models.TextField(verbose_name='How will you get your students involved in your community during GSoC?')), + ('students_involvement_after', models.TextField(verbose_name='How will you keep students involved with your community after GSoC?')), + ('past_gsoc_experience', models.BooleanField(verbose_name='Has your org been accepted as a mentor org in Google Summer of Code before?')), + ('suborg_in_past', models.BooleanField(verbose_name='Was this as a Suborg?')), + ('year_of_start', models.IntegerField(verbose_name='What year was your project started?')), + ('source_code', models.URLField(verbose_name='Where does your source code live?')), + ('docs', models.URLField(verbose_name='Please provide the URL that points to the repository, GitHub organization, or a web page that describes how to get your source code')), + ('anything_else', models.TextField(blank=True, null=True, verbose_name='Anything else we should know (optional)')), + ('suborg_name', models.CharField(max_length=80, verbose_name='Name')), + ('description', models.TextField(verbose_name='A very short description of your organization')), + ('logo', models.ImageField(help_text='Must be a 24-bit PNG, minimum height 256 pixels.', upload_to='logos/', verbose_name='Your organization logo')), + ('primary_os_license', models.CharField(max_length=50, verbose_name='Primary Open Source License')), + ('ideas_list', models.TextField(help_text='Write the ideas one by one, separated with newlines.', verbose_name='Ideas List')), + ('applied_but_not_selected', models.ManyToManyField(null=True, related_name='applied_not_selected', to='gsoc.GsocYear', verbose_name='If your org has applied for GSoC before but not been accepted, select the years')), + ('gsoc_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='suborg_details', to='gsoc.GsocYear')), + ('past_years', models.ManyToManyField(null=True, to='gsoc.GsocYear', verbose_name='Which years did your org participate in GSoC?')), + ], + options={ + 'verbose_name_plural': 'Suborg Details', + }, + ), + ] diff --git a/project.db b/project.db index 046a8394d3b6768f43b86ab531c74c8e44321dc6..90e7ca176661bae98daaab011a396e1ae55de160 100644 GIT binary patch delta 3785 zcmbVPTWs6r73NQgqHZ?jNQ{~^7e?`#xbTIvEnk|Zjho7Hn#5TKr%_uJG(}PhQ<+jk z*^PUXtpp9yMXL>WVfrv54_&dhWKdfI1%~y0dzn*U2!{2c?MpA{mJI6+NP+H9vJ+jM zH3p6;o&P`QKi_xG(SJDeMr7vvBXhU6C>Vw&uWxVsxOD{INL3KDEg8w7=9fe8iZyV6tg;4umgd&CcjxHRHEWFvZ+Y+%|Tp-!= z=wjhz)^78!wbI*ATuX}DWHgzMOL9g~5?ZA2e&0?9?7gr+?z({6E)-hEe<1vH#EQ|| zYIYai8@}hXWrWNX$feV`oz%%W@-%#F8UNb4!Dc~t1dWs);nHKc^a!0;ynJf$G6KOt zEFe}80>lPFg4m74%Z}KeJXY!}6Y>OgjrxwdLVacUW}JVgY@lk*4WGL5yc5I);s$8~ zX$GM{JjRvhTj=waG3<4nWY@f${SWr~by~6B+%SQm;7~+7BB*hhui#*}pfV*r19}F? zVV2qH%!R}}TfEO~pZ zu1~Pv!?$^(<&yWm&sf&DF6!j3eOqzYMIiE|Yalqgo4{yp$fTcTy9X;z?q<2c4z{m@ z>tQ(VE_UE9uD_G(8({l5%y{@|Ul8F~Kdki_4@&gi=(QOi`t%{!Y0QbWV$GjBUUazZ&yv@OF9^zd zr)3&zb^qIaAL-|~XAkB><`X}&HzPz7vI(*hV66s4bx)G^5yisQH+`_?ZeLm!w4}<% zl4(9Aq%(@Bqy+u5%bvAOrPT6*G@+=x{+AQPhK=n^U_w?i$+X66nMnyg^0FEi;8ACK zFSL+jVp5U=%#@IZ+D&ioX4$rKD+wCC@v5LsXO1XpoRlqM{=&5FA4 z+RjEHBlS2vckZ=aq?0K)uYTWkc9+Gv)=B0GC9 zKT7(Kh4I-*MU~{^wG~`UEpKL0E7m|Ur;5ajK{49j+tak?PmT1%z@Eb!wS0qv%TnfIkZ6A?09}Z>3=ee=Nzk}$`)Z&{G{@LE!Zvf$-&q_ zb+{F$qQ_RpRz;Ls@UsEv>oTW6hcmwy=x7r0Tt_21gj6b_$Pyo& z%T2V=grkkFA^C#7Kk%gkhJF5iY>n9NkWgK{~T z-vjgviueiY7U(%tHi??;TwXwe&>Is6dgMmE)R3=Oqt0q%v3j3X#BtM#TFG8^r77z2 zR^4^8+w&u&|4|N~-QS2slbYfMV)DdtoFv2sdyC47tRT5mE!jqw1^l4ab=3&jN=K}m z4LMKc!=yhP#!o)lV1k3&Xd4VW<}a028)DgAg{0zO=P!RFDO+9`atD{^x#fXe-!kpn zR;8MM!rDff$G~~|XLBBZ<=mmPomihACjFv_pPX(KaHGg7xZ8o6j!!Y__u)^&xc@29oEfqik*jUPHK@b>=1WWyhSNs^46C Tb73%nH>7%llxbAbSDXD0FRnQ<=y-OTM)zqksLe-z1V}0g9>6n9MK2A%YT?*-jwQNrI#00 z_UIXe@Wej|BP0i^J-rYf2$uxy6WXQa5EN6@eB9L2@n?q$ifT?Sl3` zfM453dxkJh+0|%_;6d|MY=EZ3O;d{3?Z9hdT!ls8^L c;fcue*Nv4`2FL#Y@Hx2q2pHzN7k}0M0QQ%-;{X5v From 40b29e9fe8b5ea67e67c010f6c25424ce4a81e7c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 13 Jun 2019 17:34:19 +0530 Subject: [PATCH 0300/1137] Change base template --- gsoc/templates/base.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 5ca584b1..ad2ca8d5 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -117,10 +117,7 @@ {% render_block 'js' %} -<<<<<<< HEAD -======= ->>>>>>> Add view for the suborg appliaction form {% block js %}{% endblock %} From f25c2ec69eb0e158ca6aa4d53e272a2c1dcccb88 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 11:31:53 +0530 Subject: [PATCH 0301/1137] Add new fields in suborg application form, ... --- gsoc/admin.py | 16 +++++++- gsoc/forms.py | 37 ++++++++++++++++- gsoc/models.py | 8 ++++ .../admin/suborg_details_change_form.html | 41 +++++++++++++++++++ .../templates/post_register.html | 5 +++ .../templates/register_suborg.html | 3 +- suborg_application/urls.py | 3 +- suborg_application/views.py | 23 +++++++++-- 8 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 gsoc/templates/admin/suborg_details_change_form.html create mode 100644 suborg_application/templates/post_register.html diff --git a/gsoc/admin.py b/gsoc/admin.py index 17b4eafa..f3d62c7c 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -425,4 +425,18 @@ def has_change_permission(self, request, obj=None): admin.site.register(Event, EventAdmin) -admin.site.register(SubOrgDetails) + + +class SubOrgDetailsAdmin(admin.ModelAdmin): + list_display = ('suborg_name', 'gsoc_year') + list_filter = ('gsoc_year', ) + change_form_template = 'admin/suborg_details_change_form.html' + + def has_change_permission(self, request, obj=None): + return False + + def has_add_permission(self, request, obj=None): + return False + + +admin.site.register(SubOrgDetails, SubOrgDetailsAdmin) diff --git a/gsoc/forms.py b/gsoc/forms.py index 63840da8..d105ff1b 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -2,6 +2,7 @@ SubOrgDetails) from django import forms +from django.core.exceptions import ValidationError class UserProfileForm(forms.ModelForm): @@ -53,4 +54,38 @@ class Meta: class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails - exclude = ('gsoc_year', ) + exclude = ('accepted', ) + widgets = { + 'gsoc_year': forms.HiddenInput(), + } + + def clean(self): + cd = self.cleaned_data + past_exp = cd.get('past_gsoc_experience') + past_years = cd.get('past_years').all() + applied_not_selected = cd.get('applied_but_not_selected').all() + + contact = [ + cd.get('chat', None), + cd.get('mailing_list', None), + cd.get('twitter_url', None), + cd.get('blog_url', None), + cd.get('link', None) + ] + + contact = list(filter(lambda a: a != None, contact)) + + if len(contact) < 3: + raise ValidationError('At least three out of the five contact details should be entered') + + if past_exp and len(past_years) == 0: + raise ValidationError('No past years mentioned but past experience selected') + elif not past_exp and len(past_years) > 0: + raise ValidationError('Past years mentioned but past experience not selected') + + for _y in applied_not_selected: + for y in past_years: + if y == _y: + raise ValidationError('Applied but not selected year can not match with past years') + + return cd diff --git a/gsoc/models.py b/gsoc/models.py index 62a9b766..0dc106e4 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -162,6 +162,14 @@ class SubOrgDetails(models.Model): primary_os_license = models.CharField(max_length=50, verbose_name='Primary Open Source License') ideas_list = models.TextField(verbose_name='Ideas List', help_text='Write the ideas one by one, separated with newlines.') + chat = models.URLField(null=True, blank=True) + mailing_list = models.EmailField(null=True, blank=True) + twitter_url = models.URLField(null=True, blank=True) + blog_url = models.URLField(null=True, blank=True) + link = models.URLField(null=True, blank=True, verbose_name='Any other link') + + accepted = models.BooleanField(default=False) + class Meta: verbose_name_plural = 'Suborg Details' diff --git a/gsoc/templates/admin/suborg_details_change_form.html b/gsoc/templates/admin/suborg_details_change_form.html new file mode 100644 index 00000000..8e859fa9 --- /dev/null +++ b/gsoc/templates/admin/suborg_details_change_form.html @@ -0,0 +1,41 @@ +{% extends "admin/change_form.html" %} +{% load static %} + +{% block extrastyle %} + {{ block.super }} + + +{% endblock %} + + +{% block submit_buttons_bottom %} + {% if not original.is_reviewed %} +
    + Accept + Reject +
    + {% endif %} +{% endblock %} diff --git a/suborg_application/templates/post_register.html b/suborg_application/templates/post_register.html new file mode 100644 index 00000000..b7a6d4d6 --- /dev/null +++ b/suborg_application/templates/post_register.html @@ -0,0 +1,5 @@ +{% extends CMS_TEMPLATE %} + +{% block content %} +

    Thanks!

    +{% endblock %} \ No newline at end of file diff --git a/suborg_application/templates/register_suborg.html b/suborg_application/templates/register_suborg.html index e0b2f4bd..21e73d25 100644 --- a/suborg_application/templates/register_suborg.html +++ b/suborg_application/templates/register_suborg.html @@ -18,7 +18,8 @@ {% block content %}
    -
    + + {% csrf_token %} {{ form }} diff --git a/suborg_application/urls.py b/suborg_application/urls.py index 72213d3a..2d0c12f9 100644 --- a/suborg_application/urls.py +++ b/suborg_application/urls.py @@ -1,7 +1,8 @@ from django.conf.urls import url -from .views import register_suborg +from .views import register_suborg, post_register urlpatterns = [ url('^$', register_suborg, name='register_suborg'), + url('^thanks/', post_register, name='post_register'), ] diff --git a/suborg_application/views.py b/suborg_application/views.py index a3cb1f1c..bb2df204 100644 --- a/suborg_application/views.py +++ b/suborg_application/views.py @@ -1,11 +1,26 @@ from gsoc.forms import SubOrgApplicationForm +from gsoc.models import GsocYear -from django.shortcuts import render +from django.shortcuts import render, redirect +from django.urls import reverse def register_suborg(request): if request.method == 'GET': - form = SubOrgApplicationForm() - return render(request, 'register_suborg.html', { + gsoc_year = GsocYear.objects.first() + form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year}) + + elif request.method == 'POST': + form = SubOrgApplicationForm(request.POST, request.FILES) + if form.is_valid(): + suborg_details = form.save() + return redirect(reverse('suborg_application:post_register')) + + return render(request, 'register_suborg.html', { 'form': form, - }) \ No newline at end of file + }) + + +def post_register(request): + if request.method == 'GET': + return render(request, 'post_register.html') From 0d51dc32016495aa9aac5b5615ae9ca111dc23e7 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 11:34:44 +0530 Subject: [PATCH 0302/1137] Migrate db --- gsoc/migrations/0030_auto_20190614_0559.py | 43 +++++++++++++++++++++ project.db | Bin 1495040 -> 1499136 bytes 2 files changed, 43 insertions(+) create mode 100644 gsoc/migrations/0030_auto_20190614_0559.py diff --git a/gsoc/migrations/0030_auto_20190614_0559.py b/gsoc/migrations/0030_auto_20190614_0559.py new file mode 100644 index 00000000..46ca7a44 --- /dev/null +++ b/gsoc/migrations/0030_auto_20190614_0559.py @@ -0,0 +1,43 @@ +# Generated by Django 2.1.9 on 2019-06-14 05:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0029_suborgdetails'), + ] + + operations = [ + migrations.AddField( + model_name='suborgdetails', + name='accepted', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='suborgdetails', + name='blog_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='suborgdetails', + name='chat', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='suborgdetails', + name='link', + field=models.URLField(blank=True, null=True, verbose_name='Any other link'), + ), + migrations.AddField( + model_name='suborgdetails', + name='mailing_list', + field=models.EmailField(blank=True, max_length=254, null=True), + ), + migrations.AddField( + model_name='suborgdetails', + name='twitter_url', + field=models.URLField(blank=True, null=True), + ), + ] diff --git a/project.db b/project.db index 90e7ca176661bae98daaab011a396e1ae55de160..f2a14e3bebe45788ccfe22a84c33875da65733ce 100644 GIT binary patch delta 3633 zcmb_fd2AHd8J{;ZyEE(EnYW8~*WTsu0Y`cBkzt~`=%^|3DL>6Jw3d-cs-h5=whUax6`{)PrDkGXM;j;K5guWn*Uf%0QD4I1zUdS5~kU$NU26YA5 z!F^M+$My&8E=dkJC7)CFI3!=C{q z@8(!1E0_g0p#EYY!$(cRy)|=JdOYQ>fYvr~>gRnDU$Wh>B9cs85CQDvVcQ^Vsfh z^7jz9=$nC$1cu~nCEO~QHvSRb3fw}EaN8AT`yK{;K=5^(&5@KMY~4h>lI?`Kj2jq? zY)NjyiZnQmddP4W+YNz>3HPcRxC$3T;2UDOm66f-&J8zpb+xBrO_@|@+Wz=q8(F)b zJuxkpxX;H`g3sW$@d-%c5xf@|?jSP`uvQxHyr1$M0iBx5V>tr7u!U3t8*wi{=HHX0 zkdvigQwC16TaCbT4&g4SFgKn9usb5Ty-UaG?RK6uA(TklY)aL~?0YJ*0d%G!Be(&O zGeKvJ2@v-2&ZacqW%ot^7bILn1Gr^42sj-!YV9ngZY`}%?QK=c>Q2q5x+UTi{u{mu zQ~Dh~jZfm&@ge*Q-i*6(91N?mpcgpGqT?x@BuUju_i0Vm0am*y{7-xf>@L9udLRE9 zpTo!TK1j=MuG_YlsZEoQ}0agS2w(6&!p3g_dOV&bq(IbW`= zoC%H&f|gWAZ;J<&vQ^s5|LTZa2CL=pwCF{5{ArRAeip3JQY3 zAjR_(HI=7$Std<~wbp?Lh2C8$yUN{uk1SP8t`}0d>~&T61C!s}FEq<)o?W_TS?%N< zF?l4B7VJYs_8cSOQA&${`6#7a9rMXv-{S-OqTZKvRuwL}J#{lCyA3{SGG zO7^t28)W(Q=ZdoVu6Bz<%crSA?f__)bF$n4o`aQX;RX^m6J?SGL6Bvh3hH<$@rm@> z+y{Xp_^_;uCvl>TCyz`@5v-@jEy*6g&7QDN48uPNvlc!)zP&hhTi$#snKebC%Eb~+ z;vg$Y0zDLucASI>m)}2fZMh@YuJkP8ej>qv&k(MQORxr`fdN=nZ`UtiL8tdle0|k~ zhwal}=AiM=bkOpk>7f~v=`Ra^{mv7G+-cl4CTtrMw~gUbV@7sRMEFAre~9hlf51SG z(N&noDD5mJ&vlw+8suMMy((^DAA$tgC?t3`VcN(01_JduDhT2NO-<4UY%I6SEqVPe?_{;n^pLqOPqiHiKGqn@LBJjGdmm@q z_|mLcOH;ZzL4I~Me<88_E8nv0nc5taxk#s*!~|4Y;dM(M-`6;6ipLZ2S(T2LHf>r@ zE8Tz3&o(modN>VNnTu(?dKNC#FQ^=<)=-xDLG^oDlH5|bsV~zRR%1y%*%Ow$-mlgP zSEb8W?w37opQIdbG}oFLBlQ}n(Ck33!OF+TD_v&2@=4k<11WEHn?pB?pfnihHe|SC zi0ePr*XkDYE!-CEA2c534BaMP0V2=Q%8gcQ9a3(#TcF89Kh3(>L zeHmpqr!NC&o}NZWM}p=Tw&Y_?Xsb8)-_+C4zoaA7`rTBhwoa;PZS$^cjl=UYPeXr$ zT+^}I9jadH&9&B;R8!l~UsK!Cx~`fyKC%pZTWeZl{WaAg7zB97nwFKdLC?zPYf_>9 zkhJdkHSG=k%Uf%z>)S%LvHnnNecQ_FwtnLHvn6WwIVX!xId|Q%aQ0zip5yRh#t6+Z zL(pBIwCEbepW_Z`&uXrtYq%C3b3e@$$mdN~cYz&)n?LD)NDVh|1+q-xIZ1xza4mKzFo zz&}o@baS{p)ti~+c(JJmFg+zMucy>eQ@3J8nPXOFLn@O=^r&7Y03>M~P^w%{SU+W; z$Y_25Ycr_P50wnB;|kWx^iYVLXtoxTW9?Q8>2J5@qbNDsV#z0wq*YW$7oEJ#N~7%U ZFNzMIu^J8IQ&z7is+f%E79Lrp`42rg)`9>4 delta 1790 zcma)6YfKbZ6rS0+JF}13IlHhsP!$Nt+C=l=MtPam4+^;NtrTWbIrF^Cym>=aclb7e01kL*#*Ol#V0E=sAs5jGImif(q-Y z^uXlBDy9QnKVdqB(C)*MU?6KC6`!_)(2npfDmhbl`6SNTvV}d%r`TbXgGcZXet^4h z8@_=r;W}J{%P@)|P68c`3MKy~VxuiWNepw;VKu27i-6Wm2*(7~vHq|`QuR4#L%+*L zUvm&Qi#f>KCe-U#6Y;Vo8mWXs6$;g}NnA#lgCVTrB=k{>lVq_Xjl3|)&nNLGxs^Q` z#%QSSqEbbwgleE&0n$7fAV}s2$Dtb!833k7N!&?{#8h^Yd;$BBBBgKj*L)=SjK&L57? zz&_Xwaa2_Si=Q*=8mf9Y_!;h_?kk+@MfeCtU;uic4^^$edhVMeg@US#Qb`kYS)nFw zRq%!_V50?MNe&WMuzdzmAvUE5ibQ}G1;ne9jdcEHfH;Z&)f}d`h~?VcVqeI=cv0aZ zZ^R#3=v}zjAM%E3BN1=m;^K(UU+WL~i~SDsHBJ>0xI3b8{w)Gayeo#~e`aU;IWdlB z`TGX2_6e~?6K$oOYPp8Tl#DRF7IrO9DwI&(Nb&FeaEVZYmyAp<<@Pe^Kp`$(7L{UB z81;kpj;x&$ngz{@W#!Q`DmuSQ)t%7Q@iws2!fZR5+4CT~s(Tk880Hd`}uF6+8*gYk~}3W^H~1NGsC z=78~>Z3~1O0!1aU=Frx1n}Qx-aIo6YF+~!3{?p}fYP0q&6;*^`#qgK0Ruk@?=mKkticW|@hd-5|}GIH!b+WVXB3G0TW_LOgFCN1rQZHXc^L73B7(I@ee-+A?ZP z?JMZuCN-0Gg*C`~6|n`8#L-D?b1L From f1ce60aab8d0bd53e7929b89d8cc103c22afea62 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 11:35:14 +0530 Subject: [PATCH 0303/1137] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3073c54e..b0900404 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ logs/* # Project root static folder /static/ -/media/proposals/ +/media/ # Pyenv .python-version From b321508eac6cd936dff7525a9f79fd6d20c4ad8d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 12:01:04 +0530 Subject: [PATCH 0304/1137] Update suborg application model and accept/reject views --- gsoc/models.py | 2 +- suborg_application/urls.py | 10 +++++++++- suborg_application/views.py | 23 ++++++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 0dc106e4..dc43721d 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -168,7 +168,7 @@ class SubOrgDetails(models.Model): blog_url = models.URLField(null=True, blank=True) link = models.URLField(null=True, blank=True, verbose_name='Any other link') - accepted = models.BooleanField(default=False) + accepted = models.BooleanField(default=None, null=True) class Meta: verbose_name_plural = 'Suborg Details' diff --git a/suborg_application/urls.py b/suborg_application/urls.py index 2d0c12f9..9dc0fa26 100644 --- a/suborg_application/urls.py +++ b/suborg_application/urls.py @@ -1,8 +1,16 @@ from django.conf.urls import url -from .views import register_suborg, post_register +from .views import register_suborg, post_register, accept_application, reject_application urlpatterns = [ url('^$', register_suborg, name='register_suborg'), url('^thanks/', post_register, name='post_register'), ] + +# Review application routes +urlpatterns += [ + url(r'^accept/(?P[0-9]+)/$', accept_application, + name='accept_application'), + url(r'^reject/(?P[0-9]+)/$', reject_application, + name='unpublish_article'), +] diff --git a/suborg_application/views.py b/suborg_application/views.py index bb2df204..5ec59c0c 100644 --- a/suborg_application/views.py +++ b/suborg_application/views.py @@ -1,10 +1,15 @@ from gsoc.forms import SubOrgApplicationForm -from gsoc.models import GsocYear +from gsoc.models import GsocYear, SubOrgDetails +from django.contrib.auth import decorators from django.shortcuts import render, redirect from django.urls import reverse +def is_superuser(user): + return user.is_superuser + + def register_suborg(request): if request.method == 'GET': gsoc_year = GsocYear.objects.first() @@ -24,3 +29,19 @@ def register_suborg(request): def post_register(request): if request.method == 'GET': return render(request, 'post_register.html') + + +@decorators.user_passes_test(is_superuser) +def accept_application(request, application_id): + if request.method == 'GET': + application = SubOrgDetails.objects.get(id=application_id) + # application.accept() + return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) + + +@decorators.user_passes_test(is_superuser) +def reject_application(request, application_id): + if request.method == 'GET': + application = SubOrgDetails.objects.get(id=application_id) + # application.reject() + return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) From 71be1a48de9a81408c43ecc233c4422d32982d3a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 12:01:15 +0530 Subject: [PATCH 0305/1137] Migrate db --- gsoc/migrations/0031_auto_20190614_0630.py | 18 ++++++++++++++++++ project.db | Bin 1499136 -> 1499136 bytes 2 files changed, 18 insertions(+) create mode 100644 gsoc/migrations/0031_auto_20190614_0630.py diff --git a/gsoc/migrations/0031_auto_20190614_0630.py b/gsoc/migrations/0031_auto_20190614_0630.py new file mode 100644 index 00000000..2e8212f3 --- /dev/null +++ b/gsoc/migrations/0031_auto_20190614_0630.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.9 on 2019-06-14 06:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0030_auto_20190614_0559'), + ] + + operations = [ + migrations.AlterField( + model_name='suborgdetails', + name='accepted', + field=models.BooleanField(default=None, null=True), + ), + ] diff --git a/project.db b/project.db index f2a14e3bebe45788ccfe22a84c33875da65733ce..bf8fc048c20d33f56868b14b70a5f9efb5c4af88 100644 GIT binary patch delta 354 zcmWm9PfNmZ90u@h?vLB7xtu?RAk+>a%=pd8U`9m2j)4a+vBIehFX0M}E*%^lq=S?N z?Jo2J#KVqR(A^ghbSOHF=+wDy4}2bYj?Z9j59apms_YX4*##~EMG*YnSk*~?>d-~9 zK68uj6#05(sY6R80R>`!1`J>U2jYMS0uVt0EP-Vpfh3T@3P^>Psywa76wwH{nzBKO z#!Pltal7hU43Xa2?ex4h#=6#Mp7*^*8EYmsv`PaTI{qIeY?QQ04jWY+SIfqxp_`g% z4tur|_U!cNb6gWbVS#?7$xrgZL2Nc6;xF7I`^-dSB-P0XjzXA^d)fI85>50b@e7(B zUXE-oqiNJlKG<>7nLucv6xj{eS|}e24qS96tGPn6-F8p=?r|a4^1PGq%SC5={E*8W TX_UVoOgktWUU=x*nGBMDMn7Yd delta 287 zcmZoT5Z!PfdV;iIJp%({9t1NmGB9x3P1G@FtlyZxqRAM%IaTvM6XTI)SDkiO9Y!E# z0%B$$W&vVWAZ7z%b|B^eVoo6D0%C3;<^f_}Am#&NejpYAVnHAl0%GCqt~w(7m6?UP zi>I>(h^TKCOmO9HFOC-3UK}mj`(&8|v%<6lF*b=83>=5pm$DsXeZ%;IKZNlG0~oMA zo-CN~ZaPn**d6W;hTceaaYI9COqsRbp|3n#OROm|Kdo3#CPs#p#uM-;nwtRYjQ@AQr7ViMcS^Tj$7mN|$C F004e0Q=tF= From 4b8211a9aa91a158f1a89be4d8600f74e91cb1e5 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 12:11:13 +0530 Subject: [PATCH 0306/1137] Add accept/reject functionality for suborg application --- gsoc/models.py | 8 ++++++++ gsoc/templates/admin/suborg_details_change_form.html | 6 +++--- suborg_application/urls.py | 2 +- suborg_application/views.py | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index dc43721d..cd6bd246 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -173,6 +173,14 @@ class SubOrgDetails(models.Model): class Meta: verbose_name_plural = 'Suborg Details' + def accept(self): + self.accepted = True + self.save() + + def reject(self): + self.accepted = False + self.save() + class UserProfileManager(models.Manager): def get_queryset(self): diff --git a/gsoc/templates/admin/suborg_details_change_form.html b/gsoc/templates/admin/suborg_details_change_form.html index 8e859fa9..80d76cc1 100644 --- a/gsoc/templates/admin/suborg_details_change_form.html +++ b/gsoc/templates/admin/suborg_details_change_form.html @@ -32,10 +32,10 @@ {% block submit_buttons_bottom %} - {% if not original.is_reviewed %} + {% if original.accepted == None %} {% endif %} {% endblock %} diff --git a/suborg_application/urls.py b/suborg_application/urls.py index 9dc0fa26..5e213075 100644 --- a/suborg_application/urls.py +++ b/suborg_application/urls.py @@ -12,5 +12,5 @@ url(r'^accept/(?P[0-9]+)/$', accept_application, name='accept_application'), url(r'^reject/(?P[0-9]+)/$', reject_application, - name='unpublish_article'), + name='reject_application'), ] diff --git a/suborg_application/views.py b/suborg_application/views.py index 5ec59c0c..34e33aed 100644 --- a/suborg_application/views.py +++ b/suborg_application/views.py @@ -35,7 +35,7 @@ def post_register(request): def accept_application(request, application_id): if request.method == 'GET': application = SubOrgDetails.objects.get(id=application_id) - # application.accept() + application.accept() return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) @@ -43,5 +43,5 @@ def accept_application(request, application_id): def reject_application(request, application_id): if request.method == 'GET': application = SubOrgDetails.objects.get(id=application_id) - # application.reject() + application.reject() return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) From cec73060730226a48a13c45335301c252fe0553e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 14:05:32 +0530 Subject: [PATCH 0307/1137] Add accept/reject functions in Suborg Application model --- gsoc/models.py | 35 +++++++++++++++++++++++-- gsoc/templates/email/suborg_accept.html | 2 ++ gsoc/templates/email/suborg_reject.html | 2 ++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 gsoc/templates/email/suborg_accept.html create mode 100644 gsoc/templates/email/suborg_reject.html diff --git a/gsoc/models.py b/gsoc/models.py index cd6bd246..0c9618fc 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -177,10 +177,41 @@ def accept(self): self.accepted = True self.save() + template_data = { + 'gsoc_year': self.gsoc_year.gsoc_year, + 'suborg_name': self.suborg_name, + } + scheduler_data = build_send_mail_json(self.suborg_admin_email, + template='suborg_accept.html', + subject='Acceptance for GSoC@PSF {}'. + format(self.gsoc_year.gsoc_year), + template_data=template_data) + Scheduler.objects.create(command='send_email', + data=scheduler_data) + + suborg = SubOrg.objects.create(suborg_name=self.suborg_name) + + RegLink.objects.create(user_role=1, + user_suborg=suborg, + user_gsoc_year=self.gsoc_year, + email=self.suborg_admin_email) + def reject(self): self.accepted = False self.save() + template_data = { + 'gsoc_year': self.gsoc_year.gsoc_year, + 'suborg_name': self.suborg_name, + } + scheduler_data = build_send_mail_json(self.suborg_admin_email, + template='suborg_reject.html', + subject='Rejection for GSoC@PSF {}'. + format(self.gsoc_year.gsoc_year), + template_data=template_data) + Scheduler.objects.create(command='send_email', + data=scheduler_data) + class UserProfileManager(models.Manager): def get_queryset(self): @@ -791,14 +822,14 @@ def due_date_delete_from_calendar(sender, instance, **kwargs): # Add Send RegLink Schedulers when RegLink is created @receiver(models.signals.post_save, sender=RegLink) def create_send_reglink_schedulers(sender, instance, **kwargs): - if instance.adduserlog is not None and instance.scheduler is None: + if instance.scheduler is None: instance.create_scheduler() # Add Send RegLink Reminder Schedulers when RegLink is created @receiver(models.signals.post_save, sender=RegLink) def create_send_reg_reminder_schedulers(sender, instance, **kwargs): - if instance.adduserlog is not None and instance.reminder is None: + if instance.reminder is None: instance.create_reminder() diff --git a/gsoc/templates/email/suborg_accept.html b/gsoc/templates/email/suborg_accept.html new file mode 100644 index 00000000..5f0213b6 --- /dev/null +++ b/gsoc/templates/email/suborg_accept.html @@ -0,0 +1,2 @@ +{{ gsoc_year }} +{{ suborg_name }} \ No newline at end of file diff --git a/gsoc/templates/email/suborg_reject.html b/gsoc/templates/email/suborg_reject.html new file mode 100644 index 00000000..5f0213b6 --- /dev/null +++ b/gsoc/templates/email/suborg_reject.html @@ -0,0 +1,2 @@ +{{ gsoc_year }} +{{ suborg_name }} \ No newline at end of file From 6b7cf3c014b700827671aac6c53f0aba16b8e136 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 14:33:11 +0530 Subject: [PATCH 0308/1137] Fix pep8 warnings --- gsoc/forms.py | 10 ++- gsoc/models.py | 75 +++++++++++++++---- .../templates/post_register.html | 4 +- .../templates/register_suborg.html | 1 + suborg_application/views.py | 2 +- 5 files changed, 70 insertions(+), 22 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index d105ff1b..ac05038e 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -69,14 +69,15 @@ def clean(self): cd.get('chat', None), cd.get('mailing_list', None), cd.get('twitter_url', None), - cd.get('blog_url', None), + cd.get('blog_url', None), cd.get('link', None) ] - contact = list(filter(lambda a: a != None, contact)) + contact = list(filter(lambda a: a is not None, contact)) if len(contact) < 3: - raise ValidationError('At least three out of the five contact details should be entered') + raise ValidationError('At least three out of the five contact'\ + 'details should be entered') if past_exp and len(past_years) == 0: raise ValidationError('No past years mentioned but past experience selected') @@ -86,6 +87,7 @@ def clean(self): for _y in applied_not_selected: for y in past_years: if y == _y: - raise ValidationError('Applied but not selected year can not match with past years') + raise ValidationError('Applied but not selected year can not'\ + 'match with past years') return cd diff --git a/gsoc/models.py b/gsoc/models.py index 0c9618fc..939383f1 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -138,29 +138,72 @@ def __str__(self): class SubOrgDetails(models.Model): - gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, related_name='suborg_details') - - reason_for_participation = models.TextField(verbose_name='Why does your org want to participate in Google Summer of Code?') - suborg_admin_email = models.EmailField(verbose_name='Suborg admin email') - mentors_student_engagement = models.TextField(verbose_name='How will you keep mentors engaged with their students?') - students_on_schedule = models.TextField(verbose_name='How will you help your students stay on schedule to complete their projects?') - students_involvement_gsoc = models.TextField(verbose_name='How will you get your students involved in your community during GSoC?') - students_involvement_after = models.TextField(verbose_name='How will you keep students involved with your community after GSoC?') - past_gsoc_experience = models.BooleanField(verbose_name='Has your org been accepted as a mentor org in Google Summer of Code before?') - past_years = models.ManyToManyField(GsocYear, null=True, verbose_name='Which years did your org participate in GSoC?') + gsoc_year = models.ForeignKey( + GsocYear, + on_delete=models.CASCADE, + related_name='suborg_details' + ) + + reason_for_participation = models.TextField( + verbose_name='Why does your org want to participate in Google Summer of Code?' + ) + suborg_admin_email = models.EmailField( + verbose_name='Suborg admin email' + ) + mentors_student_engagement = models.TextField( + verbose_name='How will you keep mentors engaged with their students?' + ) + students_on_schedule = models.TextField( + verbose_name='How will you help your students stay'\ + 'on schedule to complete their projects?' + ) + students_involvement_gsoc = models.TextField( + verbose_name='How will you get your students involved in your community during GSoC?' + ) + students_involvement_after = models.TextField( + verbose_name='How will you keep students involved with your community after GSoC?' + ) + past_gsoc_experience = models.BooleanField( + verbose_name='Has your org been accepted as a mentor org'\ + 'in Google Summer of Code before?' + ) + past_years = models.ManyToManyField( + GsocYear, + blank=True, + verbose_name='Which years did your org participate in GSoC?' + ) suborg_in_past = models.BooleanField(verbose_name='Was this as a Suborg?') - applied_but_not_selected = models.ManyToManyField(GsocYear, null=True, related_name='applied_not_selected', verbose_name='If your org has applied for GSoC before but not been accepted, select the years') + applied_but_not_selected = models.ManyToManyField( + GsocYear, + blank=True, + related_name='applied_not_selected', + verbose_name='If your org has applied for GSoC'\ + 'before but not been accepted, select the years' + ) year_of_start = models.IntegerField(verbose_name='What year was your project started?') source_code = models.URLField(verbose_name='Where does your source code live?') - docs = models.URLField(verbose_name='Please provide the URL that points to the repository, GitHub organization, or a web page that describes how to get your source code') - anything_else = models.TextField(null=True, blank=True, verbose_name='Anything else we should know (optional)') + docs = models.URLField( + verbose_name='Please provide the URL that points to the repository, '\ + 'GitHub organization, or a web page that describes how to'\ + ' get your source code' + ) + anything_else = models.TextField( + null=True, + blank=True, + verbose_name='Anything else we should know (optional)' + ) suborg_name = models.CharField(max_length=80, verbose_name='Name') description = models.TextField(verbose_name='A very short description of your organization') - logo = models.ImageField(upload_to='logos/', verbose_name='Your organization logo', help_text='Must be a 24-bit PNG, minimum height 256 pixels.') - primary_os_license = models.CharField(max_length=50, verbose_name='Primary Open Source License') - ideas_list = models.TextField(verbose_name='Ideas List', help_text='Write the ideas one by one, separated with newlines.') + logo = models.ImageField(upload_to='logos/', verbose_name='Your organization logo', + help_text='Must be a 24-bit PNG, minimum height 256 pixels.') + primary_os_license = models.CharField(max_length=50, + verbose_name='Primary Open Source License') + ideas_list = models.TextField( + verbose_name='Ideas List', + help_text='Write the ideas one by one, separated with newlines.' + ) chat = models.URLField(null=True, blank=True) mailing_list = models.EmailField(null=True, blank=True) diff --git a/suborg_application/templates/post_register.html b/suborg_application/templates/post_register.html index b7a6d4d6..d9ecfbdd 100644 --- a/suborg_application/templates/post_register.html +++ b/suborg_application/templates/post_register.html @@ -1,5 +1,7 @@ {% extends CMS_TEMPLATE %} {% block content %} -

    Thanks!

    +
    +

    Thanks for registering! We will be in touch with you.

    +
    {% endblock %} \ No newline at end of file diff --git a/suborg_application/templates/register_suborg.html b/suborg_application/templates/register_suborg.html index 21e73d25..5d76cbe4 100644 --- a/suborg_application/templates/register_suborg.html +++ b/suborg_application/templates/register_suborg.html @@ -18,6 +18,7 @@ {% block content %}
    +

    Apply for participating in GSoC@PSF as a SubOrg!

    {% csrf_token %} {{ form }} diff --git a/suborg_application/views.py b/suborg_application/views.py index 34e33aed..660cac46 100644 --- a/suborg_application/views.py +++ b/suborg_application/views.py @@ -14,7 +14,7 @@ def register_suborg(request): if request.method == 'GET': gsoc_year = GsocYear.objects.first() form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year}) - + elif request.method == 'POST': form = SubOrgApplicationForm(request.POST, request.FILES) if form.is_valid(): From 35444c580a69d52aabae6da0b4e90c38bd0a5419 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 14 Jun 2019 14:37:06 +0530 Subject: [PATCH 0309/1137] Fix pep8 warnings --- gsoc/forms.py | 4 ++-- gsoc/models.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index ac05038e..12e05e0e 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -76,7 +76,7 @@ def clean(self): contact = list(filter(lambda a: a is not None, contact)) if len(contact) < 3: - raise ValidationError('At least three out of the five contact'\ + raise ValidationError('At least three out of the five contact' 'details should be entered') if past_exp and len(past_years) == 0: @@ -87,7 +87,7 @@ def clean(self): for _y in applied_not_selected: for y in past_years: if y == _y: - raise ValidationError('Applied but not selected year can not'\ + raise ValidationError('Applied but not selected year can not' 'match with past years') return cd diff --git a/gsoc/models.py b/gsoc/models.py index 939383f1..4b93de24 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -154,7 +154,7 @@ class SubOrgDetails(models.Model): verbose_name='How will you keep mentors engaged with their students?' ) students_on_schedule = models.TextField( - verbose_name='How will you help your students stay'\ + verbose_name='How will you help your students stay ' 'on schedule to complete their projects?' ) students_involvement_gsoc = models.TextField( @@ -164,7 +164,7 @@ class SubOrgDetails(models.Model): verbose_name='How will you keep students involved with your community after GSoC?' ) past_gsoc_experience = models.BooleanField( - verbose_name='Has your org been accepted as a mentor org'\ + verbose_name='Has your org been accepted as a mentor org ' 'in Google Summer of Code before?' ) past_years = models.ManyToManyField( @@ -178,14 +178,14 @@ class SubOrgDetails(models.Model): GsocYear, blank=True, related_name='applied_not_selected', - verbose_name='If your org has applied for GSoC'\ + verbose_name='If your org has applied for GSoC ' 'before but not been accepted, select the years' ) year_of_start = models.IntegerField(verbose_name='What year was your project started?') source_code = models.URLField(verbose_name='Where does your source code live?') docs = models.URLField( - verbose_name='Please provide the URL that points to the repository, '\ - 'GitHub organization, or a web page that describes how to'\ + verbose_name='Please provide the URL that points to the repository, ' + 'GitHub organization, or a web page that describes how to' ' get your source code' ) anything_else = models.TextField( @@ -237,7 +237,7 @@ def accept(self): RegLink.objects.create(user_role=1, user_suborg=suborg, user_gsoc_year=self.gsoc_year, - email=self.suborg_admin_email) + email=self.suborg_admin_email) def reject(self): self.accepted = False From c4e081fef85e8ccac0eeafcb56c67b9696117203 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 16 Jun 2019 10:07:44 +0530 Subject: [PATCH 0310/1137] Change name of app from suborg_application to suborg --- blogs_list/urls.py | 2 +- gsoc/settings.py | 4 ++-- .../admin/suborg_details_change_form.html | 4 ++-- gsoc/urls.py | 12 ++++++------ project.db | Bin 1499136 -> 1499136 bytes {suborg_application => suborg}/__init__.py | 0 {suborg_application => suborg}/admin.py | 0 {suborg_application => suborg}/apps.py | 2 +- {suborg_application => suborg}/cms_apps.py | 10 +++++----- .../migrations/__init__.py | 0 {suborg_application => suborg}/models.py | 0 suborg/templates/add_mentor.html | 0 .../templates/post_register.html | 0 .../templates/register_suborg.html | 2 +- {suborg_application => suborg}/tests.py | 0 suborg/urls.py | 17 +++++++++++++++++ {suborg_application => suborg}/views.py | 6 +++++- suborg_application/urls.py | 16 ---------------- 18 files changed, 40 insertions(+), 35 deletions(-) rename {suborg_application => suborg}/__init__.py (100%) rename {suborg_application => suborg}/admin.py (100%) rename {suborg_application => suborg}/apps.py (70%) rename {suborg_application => suborg}/cms_apps.py (50%) rename {suborg_application => suborg}/migrations/__init__.py (100%) rename {suborg_application => suborg}/models.py (100%) create mode 100644 suborg/templates/add_mentor.html rename {suborg_application => suborg}/templates/post_register.html (100%) rename {suborg_application => suborg}/templates/register_suborg.html (85%) rename {suborg_application => suborg}/tests.py (100%) create mode 100644 suborg/urls.py rename {suborg_application => suborg}/views.py (93%) delete mode 100644 suborg_application/urls.py diff --git a/blogs_list/urls.py b/blogs_list/urls.py index b6ade03f..42a56471 100644 --- a/blogs_list/urls.py +++ b/blogs_list/urls.py @@ -4,6 +4,6 @@ from .feeds import BlogsFeed urlpatterns = [ - url('^$', list_blogs, name='list_blogs'), + url('^', list_blogs, name='list_blogs'), url('feed/', BlogsFeed(), name='feed') ] diff --git a/gsoc/settings.py b/gsoc/settings.py index 57460858..832567b8 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -70,7 +70,7 @@ def gettext(s): return s 'DIRS': [ os.path.join(BASE_DIR, 'gsoc', 'templates'), os.path.join(BASE_DIR, 'blogs_list', 'templates'), - os.path.join(BASE_DIR, 'suborg_application', 'templates'), + os.path.join(BASE_DIR, 'suborg', 'templates'), ], 'OPTIONS': { 'context_processors': [ @@ -149,7 +149,7 @@ def gettext(s): return s 'taggit', 'gsoc', 'blogs_list', - 'suborg_application', + 'suborg', 'debug_toolbar' ) THUMBNAIL_PROCESSORS = ( diff --git a/gsoc/templates/admin/suborg_details_change_form.html b/gsoc/templates/admin/suborg_details_change_form.html index 80d76cc1..10fe5955 100644 --- a/gsoc/templates/admin/suborg_details_change_form.html +++ b/gsoc/templates/admin/suborg_details_change_form.html @@ -34,8 +34,8 @@ {% block submit_buttons_bottom %} {% if original.accepted == None %} {% endif %} {% endblock %} diff --git a/gsoc/urls.py b/gsoc/urls.py index f672b59f..0464adc6 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -18,13 +18,13 @@ admin.autodiscover() urlpatterns = [ - url(r'^sitemap\.xml$', sitemap, + url(r'^sitemap\.xml', sitemap, {'sitemaps': { 'cmspages': CMSSitemap, } }), - url(r'^robots.txt$', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots_file"), - url(r'^favicon.ico$', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), + url(r'^robots.txt', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots_file"), + url(r'^favicon.ico', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), ] # Add Django site authentication urls (for login, logout, password management) @@ -65,10 +65,10 @@ # Review article routes urlpatterns += [ - url(r'^article/review/(?P[0-9]+)/$', gsoc.views.review_article, + url(r'^article/review/(?P[0-9]+)/', gsoc.views.review_article, name='review_article'), - url(r'^article/unpublish/(?P[0-9]+)/$', gsoc.views.unpublish_article, + url(r'^article/unpublish/(?P[0-9]+)/', gsoc.views.unpublish_article, name='unpublish_article'), - url(r'^article/publish/(?P[0-9]+)/$', gsoc.views.publish_article, + url(r'^article/publish/(?P[0-9]+)/', gsoc.views.publish_article, name='publish_article') ] diff --git a/project.db b/project.db index bf8fc048c20d33f56868b14b70a5f9efb5c4af88..f30e26e13c00e55dac2c8f853cccdb4780f99db4 100644 GIT binary patch delta 1330 zcmah|U1%It6rMXXJ2Sh<%w{q>Np>64#nz@Z?lS+IU7NfQ`4-KWE54TuhYlJFlYh|Sap_Cd66<^$H9*X!NNTJX?)J72nt#=bNLQ`b; z4(D>_p6{OTyLa|h_w23ib34Gr#oB}5;%7aD20BqSM-r+Ne({wG55T^{U|=N2 z8-7WPC`v@vgGxv-LXD>%;c*G7!ne_jDJP9(TyNM8e27%s-W#mdmzNR)zE3SFfTpYOa05OBNm zCQ-Nxnjn3S41%T){IfiF-rw^WuDQ#EnhMNCc){3&mC5ycw^HAHXP&e>^Nzwba0Aju zs6E6T?uIQLp~g89!?0b8QIGha-?vix?ihVx=@mmKz%_HOV?wu^RjgQiTeTgovL(lf4T_$0~@O=X(yK1p5Oe!{Qw6 ztHG+PqtPhFFc`Lj!5FU>zVZ=O1)sb}mBAmEC`YX!>xLr9;eccW02l=aTh-fg-jFK>2zZqrKCLka~oMGNT$Os)$a_|p<4xc=?pv-9s$ z)?1Y8Wvqs&gS4LxkjykwhejEoNoEx5M-!w-RL(6%1-I$rFY!B6UGwN$8`U)@V1FbGZr7eX0AIRcBoA-EAL?A5)z5~7NjyXgMVF7M&< zZqS9echkXr4vcqp!+RNi7nPl`hSRC*S^kNece;?REa%6#zqmo}bIxy9%<>-Yh1AI4 z*yQ-hQztXi*@6DdlwoBC`exGo#*n2A<+8eaH_-;**K4BRPM_s@f2AswZoO(mBqM0( zVJWQ7FI?pjD(!`%~2g{ItBphP!JNN?l6)YopP5>*MKq4SHfD>%%Tu4QqqIYgw zRev;_MMqDLr!%8CKIbuArM+{}?KYPzGs0nAgKfVE)$p5j!L-6%&mz+zQaGrpMo5yM zK~^5$B$J;eQzKd^7}hi+v_)3UWlr_xCUfcW-Zv+5!_UKxaohtde-~=zkFN{%@paF~ zKTymHMw3NI7sWiutdK7vN%oL-aaDXwpyvh-pmhXX|6U}i3d(UCD|_43%3Pom2NJNoKWdrRw(dGzpL+o8k5?eW%hBHl4+&K#3tGi|yVH`SQZ zI^5pUFUMMjreiSrt#}qzuZbgY|97IluCJr1=^ulh_S(K&6dOqU%X?xIuvgZ^1M7bS D{Iz(* delta 992 zcmaiyO=ufO6vt=0tDTkP-StLZ$5NbnA(0Wa*WC}T)|)0x)g~#W*d)ji$e~0ZmTGM) zSx9c1no_K$phMG=lEYjA5^>0-#7R-f910=nspJ$BK_KLsHYFdV=-iK~DXk%e9DawH z_vZiJ|IJ&waboSpiL<+4^P90ZVe`9ecM_#Xnk-}a@y(xnp{_QyAHrzY(<*g2uIh<> zy16f|PrGqDVY{avw(-L@9AnbL?-+Gpfz&&&A)HgQDtD_bI85#uDcFoeH6$bLrrHSW$Ulld8I#&%1l zwwO0^TD+LZX>QRrHAA-xPNGmM)h^tCS(0Q8N)Vb92e*Mp<(>_Iu~-bcm2GNVpWJV$0Prv-nqb8 z*z1RaoarVEcbnwI3njz89%!ADv!_XWoy&j9@l`O2IvA-)V^N1k)YTG_ieVYZh9C%r z!Gok9YcZtbcVcJ|zZ*jlF&rsI{!73w$B^}Jbc_wW3QDv>zSWB%3QzG<0+b+SkRnh- zibNSAy_jq|+$NAVr3e|!%5916$pq4(J|O!u`0ld2$G2F|l~%6R}l6<9LH##*^nta$eE4MsU)+9Nn`XZ^cEco&$QD05J~rkzrD}V`ay6B zquWZGEc~KOvShQT{Kk+k9x1QEYxfkE;|GFMVDQJ_6nz>9;*AH&OX(N&msx$IGV2{F z&(0s$=e+mr1#gj_-;QTiGsaxym^FLou)T#>(C6ket8?>9-ohcQK2gG0`c+xMSAJG1 cIPz3ccbASGIAH3AtLX{tIhW+-W94A~FQ|twjQ{`u diff --git a/suborg_application/__init__.py b/suborg/__init__.py similarity index 100% rename from suborg_application/__init__.py rename to suborg/__init__.py diff --git a/suborg_application/admin.py b/suborg/admin.py similarity index 100% rename from suborg_application/admin.py rename to suborg/admin.py diff --git a/suborg_application/apps.py b/suborg/apps.py similarity index 70% rename from suborg_application/apps.py rename to suborg/apps.py index 9a677c4c..f8a138be 100644 --- a/suborg_application/apps.py +++ b/suborg/apps.py @@ -2,4 +2,4 @@ class SuborgApplicationConfig(AppConfig): - name = 'suborg_application' + name = 'suborg' diff --git a/suborg_application/cms_apps.py b/suborg/cms_apps.py similarity index 50% rename from suborg_application/cms_apps.py rename to suborg/cms_apps.py index d756fd18..94d8648c 100644 --- a/suborg_application/cms_apps.py +++ b/suborg/cms_apps.py @@ -3,12 +3,12 @@ from django.utils.translation import ugettext_lazy as _ -class suborg_application(CMSApp): - app_name = "suborg_application" - name = _("Suborg Application") +class suborg(CMSApp): + app_name = "suborg" + name = _("Suborg") def get_urls(self, page=None, language=None, **kwargs): - return ["suborg_application.urls"] + return ["suborg.urls"] -apphook_pool.register(suborg_application) +apphook_pool.register(suborg) diff --git a/suborg_application/migrations/__init__.py b/suborg/migrations/__init__.py similarity index 100% rename from suborg_application/migrations/__init__.py rename to suborg/migrations/__init__.py diff --git a/suborg_application/models.py b/suborg/models.py similarity index 100% rename from suborg_application/models.py rename to suborg/models.py diff --git a/suborg/templates/add_mentor.html b/suborg/templates/add_mentor.html new file mode 100644 index 00000000..e69de29b diff --git a/suborg_application/templates/post_register.html b/suborg/templates/post_register.html similarity index 100% rename from suborg_application/templates/post_register.html rename to suborg/templates/post_register.html diff --git a/suborg_application/templates/register_suborg.html b/suborg/templates/register_suborg.html similarity index 85% rename from suborg_application/templates/register_suborg.html rename to suborg/templates/register_suborg.html index 5d76cbe4..3a08d72d 100644 --- a/suborg_application/templates/register_suborg.html +++ b/suborg/templates/register_suborg.html @@ -19,7 +19,7 @@ {% block content %}

    Apply for participating in GSoC@PSF as a SubOrg!

    - + {% csrf_token %} {{ form }} diff --git a/suborg_application/tests.py b/suborg/tests.py similarity index 100% rename from suborg_application/tests.py rename to suborg/tests.py diff --git a/suborg/urls.py b/suborg/urls.py new file mode 100644 index 00000000..97aa55a8 --- /dev/null +++ b/suborg/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls import url, include + +from . import views + +urlpatterns = [ + url('^application/', include([ + url('^', views.register_suborg, name='register_suborg'), + url('^thanks/', views.post_register, name='post_register'), + url(r'^accept/(?P[0-9]+)/', views.accept_application, + name='accept_application'), + url(r'^reject/(?P[0-9]+)/', views.reject_application, + name='reject_application'), + ])), + url('^mentor/', include([ + url('^add/', views.add_mentor, name='add_mentor') + ])), +] diff --git a/suborg_application/views.py b/suborg/views.py similarity index 93% rename from suborg_application/views.py rename to suborg/views.py index 660cac46..64ebbdaa 100644 --- a/suborg_application/views.py +++ b/suborg/views.py @@ -19,7 +19,7 @@ def register_suborg(request): form = SubOrgApplicationForm(request.POST, request.FILES) if form.is_valid(): suborg_details = form.save() - return redirect(reverse('suborg_application:post_register')) + return redirect(reverse('suborg:post_register')) return render(request, 'register_suborg.html', { 'form': form, @@ -45,3 +45,7 @@ def reject_application(request, application_id): application = SubOrgDetails.objects.get(id=application_id) application.reject() return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) + + +def add_mentor(request): + pass \ No newline at end of file diff --git a/suborg_application/urls.py b/suborg_application/urls.py deleted file mode 100644 index 5e213075..00000000 --- a/suborg_application/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.conf.urls import url - -from .views import register_suborg, post_register, accept_application, reject_application - -urlpatterns = [ - url('^$', register_suborg, name='register_suborg'), - url('^thanks/', post_register, name='post_register'), -] - -# Review application routes -urlpatterns += [ - url(r'^accept/(?P[0-9]+)/$', accept_application, - name='accept_application'), - url(r'^reject/(?P[0-9]+)/$', reject_application, - name='reject_application'), -] From db21faa362e118dccd40867bf716954bf4057e68 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 16 Jun 2019 14:48:16 +0530 Subject: [PATCH 0311/1137] Add add mentors form for suborg admins --- gsoc/models.py | 23 ++++++++++++++++++++++ suborg/templates/add_mentor.html | 12 ++++++++++++ suborg/views.py | 33 ++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 4b93de24..fa3f022b 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -71,6 +71,29 @@ def is_current_year_student(self): auth.models.User.add_to_class('is_current_year_student', is_current_year_student) +def is_current_year_suborg_admin(self): + profile = self.suborg_admin_profile() + if not profile: + return False + year = profile.gsoc_year.gsoc_year + current_year = timezone.now().year + return current_year == year + + +auth.models.User.add_to_class('is_current_year_suborg_admin', is_current_year_suborg_admin) + + +def suborg_admin_profile(self, year=timezone.now().year): + gsoc_year = GsocYear.objects.filter(gsoc_year=year).first() + if gsoc_year is None: + return None + return self.userprofile_set.filter(role=1, + gsoc_year=gsoc_year).first() + + +auth.models.User.add_to_class('suborg_admin_profile', suborg_admin_profile) + + def student_profile(self, year=timezone.now().year): gsoc_year = GsocYear.objects.filter(gsoc_year=year).first() if gsoc_year is None: diff --git a/suborg/templates/add_mentor.html b/suborg/templates/add_mentor.html index e69de29b..47afbc3b 100644 --- a/suborg/templates/add_mentor.html +++ b/suborg/templates/add_mentor.html @@ -0,0 +1,12 @@ +{% extends CMS_TEMPLATE %} + +{% block content %} +
    +

    Add mentors to your SubOrg

    + + {% csrf_token %} + {{ formset.as_p }} + + +
    +{% endblock %} \ No newline at end of file diff --git a/suborg/views.py b/suborg/views.py index 64ebbdaa..af82abe3 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -1,8 +1,9 @@ from gsoc.forms import SubOrgApplicationForm -from gsoc.models import GsocYear, SubOrgDetails +from gsoc.models import GsocYear, SubOrgDetails, RegLink from django.contrib.auth import decorators from django.shortcuts import render, redirect +from django.forms import modelformset_factory from django.urls import reverse @@ -10,6 +11,10 @@ def is_superuser(user): return user.is_superuser +def is_suborg_admin(user): + return user.is_current_year_suborg_admin() + + def register_suborg(request): if request.method == 'GET': gsoc_year = GsocYear.objects.first() @@ -47,5 +52,29 @@ def reject_application(request, application_id): return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) +@decorators.user_passes_test(is_suborg_admin) def add_mentor(request): - pass \ No newline at end of file + profile = request.user.suborg_admin_profile() + MentorFormSet = modelformset_factory(RegLink, fields=('email', ), extra=4) + + if request.method == 'POST': + formset = MentorFormSet(request.POST) + if formset.is_valid(): + instances = formset.save(commit=False) + for instance in instances: + instance.user_suborg = profile.suborg_full_name + instance.user_gsoc_year = profile.gsoc_year + instance.user_role = 2 + instance.save() + else: + return render(request, 'add_mentor.html', { + 'formset': formset, + }) + + formset = MentorFormSet(queryset=RegLink.objects.filter(user_gsoc_year=profile.gsoc_year, + user_suborg=profile.suborg_full_name, + user_role=2)) + + return render(request, 'add_mentor.html', { + 'formset': formset, + }) From d630eb7690d7eb16f416d591d37901b74b0f5b3a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 17 Jun 2019 12:18:13 +0530 Subject: [PATCH 0312/1137] Update pep8speaks config to ignore migrations --- .pep8speaks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pep8speaks.yml b/.pep8speaks.yml index f02e488c..ada36b46 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -7,6 +7,7 @@ scanner: pycodestyle: # Same as scanner.linter value. Other option is flake8 max-line-length: 100 # Default is 79 in PEP 8 ignore: # Errors and warnings to ignore + exclude: ["*migrations*"] no_blank_comment: True # If True, no comment is made on PR without any errors. descending_issues_order: False # If True, PEP 8 issues in message will be displayed in descending order of line numbers in the file From cd62c5bfc39ae0ffff64b44ea4ab6e1f9fed7480 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 18 Jun 2019 11:56:40 +0530 Subject: [PATCH 0313/1137] Fix pep8 warnings --- gsoc/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/urls.py b/gsoc/urls.py index 0464adc6..2c597fab 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -23,7 +23,8 @@ 'cmspages': CMSSitemap, } }), - url(r'^robots.txt', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots_file"), + url(r'^robots.txt', TemplateView.as_view(template_name="robots.txt", + content_type="text/plain"), name="robots_file"), url(r'^favicon.ico', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), ] From 2b7740868caeb472ef9fbfe56f2492ae4e7b77df Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 21 Jun 2019 09:13:06 +0530 Subject: [PATCH 0314/1137] Add review application instead of reject application --- gsoc/admin.py | 32 ++++++++++++++++--- gsoc/forms.py | 4 +-- gsoc/models.py | 12 ++++--- .../admin/suborg_details_change_form.html | 15 ++++----- gsoc/templates/email/suborg_reject.html | 2 -- gsoc/templates/email/suborg_review.html | 3 ++ suborg/urls.py | 6 ++-- suborg/views.py | 16 ++++++---- 8 files changed, 60 insertions(+), 30 deletions(-) delete mode 100644 gsoc/templates/email/suborg_reject.html create mode 100644 gsoc/templates/email/suborg_review.html diff --git a/gsoc/admin.py b/gsoc/admin.py index f3d62c7c..dc391d07 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -428,12 +428,36 @@ def has_change_permission(self, request, obj=None): class SubOrgDetailsAdmin(admin.ModelAdmin): - list_display = ('suborg_name', 'gsoc_year') - list_filter = ('gsoc_year', ) + list_display = ('suborg_name', 'gsoc_year', 'changed') + list_filter = ('gsoc_year', 'changed') + # fields = ('last_message', ) + readonly_fields = ( + 'gsoc_year', 'reason_for_participation', 'suborg_admin_email', + 'mentors_student_engagement', 'students_on_schedule', 'students_involvement_gsoc', + 'students_involvement_after', 'past_gsoc_experience', 'past_years', + 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', + 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', + 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', + 'blog_url', 'link', 'accepted', 'changed' + ) + fieldsets = (('Details', { + 'fields': ( + 'gsoc_year', 'reason_for_participation', 'suborg_admin_email', + 'mentors_student_engagement', 'students_on_schedule', 'students_involvement_gsoc', + 'students_involvement_after', 'past_gsoc_experience', 'past_years', + 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', + 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', + 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', + 'blog_url', 'link', 'changed', 'accepted')} + ), ('Review', { + 'fields': ('last_message', )} + )) change_form_template = 'admin/suborg_details_change_form.html' - def has_change_permission(self, request, obj=None): - return False + def save_model(self, request, obj, form, change): + obj.changed = False + obj.send_review() + super(SubOrgDetailsAdmin, self).save_model(request, obj, form, change) def has_add_permission(self, request, obj=None): return False diff --git a/gsoc/forms.py b/gsoc/forms.py index 12e05e0e..c32ac504 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -54,7 +54,7 @@ class Meta: class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails - exclude = ('accepted', ) + exclude = ('accepted', 'last_message', 'changed') widgets = { 'gsoc_year': forms.HiddenInput(), } @@ -88,6 +88,6 @@ def clean(self): for y in past_years: if y == _y: raise ValidationError('Applied but not selected year can not' - 'match with past years') + ' match with past years') return cd diff --git a/gsoc/models.py b/gsoc/models.py index fa3f022b..d02a7a3a 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -234,7 +234,9 @@ class SubOrgDetails(models.Model): blog_url = models.URLField(null=True, blank=True) link = models.URLField(null=True, blank=True, verbose_name='Any other link') - accepted = models.BooleanField(default=None, null=True) + last_message = models.TextField(null=True, blank=True) + accepted = models.BooleanField(default=False) + changed = models.BooleanField(default=None, null=True) class Meta: verbose_name_plural = 'Suborg Details' @@ -262,17 +264,19 @@ def accept(self): user_gsoc_year=self.gsoc_year, email=self.suborg_admin_email) - def reject(self): + def send_review(self): self.accepted = False self.save() template_data = { 'gsoc_year': self.gsoc_year.gsoc_year, 'suborg_name': self.suborg_name, + 'message': self.last_message, } scheduler_data = build_send_mail_json(self.suborg_admin_email, - template='suborg_reject.html', - subject='Rejection for GSoC@PSF {}'. + template='suborg_review.html', + subject='Review your SubOrg Application'\ + ' for GSoC@PSF {}'. format(self.gsoc_year.gsoc_year), template_data=template_data) Scheduler.objects.create(command='send_email', diff --git a/gsoc/templates/admin/suborg_details_change_form.html b/gsoc/templates/admin/suborg_details_change_form.html index 10fe5955..0ddbca0b 100644 --- a/gsoc/templates/admin/suborg_details_change_form.html +++ b/gsoc/templates/admin/suborg_details_change_form.html @@ -14,11 +14,12 @@ color: white !important; } - .submit-row .closelink.reject { + .submit-row .reject { background-color: orangered !important; + color: white !important; } - .submit-row .closelink.reject:hover { + .submit-row .reject:hover { background-color: rgb(200, 0, 0) !important; color: white !important; } @@ -32,10 +33,8 @@ {% block submit_buttons_bottom %} - {% if original.accepted == None %} -
    - Accept - Reject -
    - {% endif %} +
    + Accept + +
    {% endblock %} diff --git a/gsoc/templates/email/suborg_reject.html b/gsoc/templates/email/suborg_reject.html deleted file mode 100644 index 5f0213b6..00000000 --- a/gsoc/templates/email/suborg_reject.html +++ /dev/null @@ -1,2 +0,0 @@ -{{ gsoc_year }} -{{ suborg_name }} \ No newline at end of file diff --git a/gsoc/templates/email/suborg_review.html b/gsoc/templates/email/suborg_review.html new file mode 100644 index 00000000..29ebe4c4 --- /dev/null +++ b/gsoc/templates/email/suborg_review.html @@ -0,0 +1,3 @@ +{{ gsoc_year }} +{{ suborg_name }} +{{ message }} \ No newline at end of file diff --git a/suborg/urls.py b/suborg/urls.py index 97aa55a8..dd2009f0 100644 --- a/suborg/urls.py +++ b/suborg/urls.py @@ -4,12 +4,12 @@ urlpatterns = [ url('^application/', include([ - url('^', views.register_suborg, name='register_suborg'), + url('^$', views.register_suborg, name='register_suborg'), url('^thanks/', views.post_register, name='post_register'), url(r'^accept/(?P[0-9]+)/', views.accept_application, name='accept_application'), - url(r'^reject/(?P[0-9]+)/', views.reject_application, - name='reject_application'), + # url(r'^reject/(?P[0-9]+)/', views.reject_application, + # name='reject_application'), ])), url('^mentor/', include([ url('^add/', views.add_mentor, name='add_mentor') diff --git a/suborg/views.py b/suborg/views.py index af82abe3..474d045c 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -23,7 +23,9 @@ def register_suborg(request): elif request.method == 'POST': form = SubOrgApplicationForm(request.POST, request.FILES) if form.is_valid(): - suborg_details = form.save() + suborg_details = form.save(commit=False) + suborg_details.changed = True + suborg_details.save() return redirect(reverse('suborg:post_register')) return render(request, 'register_suborg.html', { @@ -44,12 +46,12 @@ def accept_application(request, application_id): return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) -@decorators.user_passes_test(is_superuser) -def reject_application(request, application_id): - if request.method == 'GET': - application = SubOrgDetails.objects.get(id=application_id) - application.reject() - return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) +# @decorators.user_passes_test(is_superuser) +# def reject_application(request, application_id): +# if request.method == 'GET': +# application = SubOrgDetails.objects.get(id=application_id) +# application.reject() +# return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) @decorators.user_passes_test(is_suborg_admin) From 11f6690634c79e42829f73867bf3044f65c30f55 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 21 Jun 2019 09:17:36 +0530 Subject: [PATCH 0315/1137] Migrate db --- gsoc/migrations/0032_auto_20190621_0347.py | 38 +++++++++++++++++++++ project.db | Bin 1499136 -> 1499136 bytes 2 files changed, 38 insertions(+) create mode 100644 gsoc/migrations/0032_auto_20190621_0347.py diff --git a/gsoc/migrations/0032_auto_20190621_0347.py b/gsoc/migrations/0032_auto_20190621_0347.py new file mode 100644 index 00000000..73e7b37d --- /dev/null +++ b/gsoc/migrations/0032_auto_20190621_0347.py @@ -0,0 +1,38 @@ +# Generated by Django 2.1.9 on 2019-06-21 03:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0031_auto_20190614_0630'), + ] + + operations = [ + migrations.AddField( + model_name='suborgdetails', + name='changed', + field=models.BooleanField(default=None, null=True), + ), + migrations.AddField( + model_name='suborgdetails', + name='last_message', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='suborgdetails', + name='accepted', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='suborgdetails', + name='applied_but_not_selected', + field=models.ManyToManyField(blank=True, related_name='applied_not_selected', to='gsoc.GsocYear', verbose_name='If your org has applied for GSoC before but not been accepted, select the years'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='past_years', + field=models.ManyToManyField(blank=True, to='gsoc.GsocYear', verbose_name='Which years did your org participate in GSoC?'), + ), + ] diff --git a/project.db b/project.db index f30e26e13c00e55dac2c8f853cccdb4780f99db4..4e20063da66f0ebc6e8423af42da1e7b620e601f 100644 GIT binary patch delta 880 zcmYL{PiPcZ9LML)%g9i_}rVzY{V70#qRC#eO89*KfYPpeoaU@$9)(+&0x#GpbY}_l%(!a!=(G5-xJ|>YD zky`k9cv=~g8`4tnabS|a<*$`k<&GH0lV_OqDe=_+s@+74ZU~*IuK0>9q56@8owWP0 zlqm1#ohewhXpi@@PB$eHzqS_*-Q>oW=!xg(>j!tY)ca$C+SeA?@VCj5k;&<8$53>XJB7u}h|ZKd4_hWX1$!)(hMqc$gIi}sc}`0F zFwVCWShZOGrp5Najs}xq(4rBj)LA#2Xs{UG%dlXvQPO~)E!HHpm2cW8?6=rHc&MZE zOoPoRbvfa*8GI}4$4dIs)eUbOYz$1B9`scluFkQekyf{BqBVimBk!zxA!E}#ywU0V z2_A{yv;6m{5mFxswZkp>QKH$^E^`XVjX0&{r);r{{!65{;&W5 delta 650 zcmX}oO=uHA6bJB~-AQ)0o3Az5mo{oY@X~6EEoucl7^Jn-rZt6%LJHY_<=eJiih@d$ z7IRu-3G@j)2%@zozjD~4QSjuYhzCWXLQg`F;zbYzU#vLr8)kOjFmL`#wfIskes{mV zE(mOn%nCFKf_io}e2lF>Hy^f1r_`y6+`d@R?<5Eq`d3LCLSmebqYY0bjP{t=&P%78l9=wwVKgP0f z7?0**o);IaK^!t+kj>buCUi1wIb@xagVWRE%5{vH(A+3)#*cXjpb>k;TEoE;54@8LC2q2xUjKcJllv!zJ}FU z6J!69T=PEI^|iXyEy2m^g7R8^`qiisEF2sZi+fru+0A}Z*oC!tW Date: Fri, 21 Jun 2019 14:42:10 +0530 Subject: [PATCH 0316/1137] Login to fill suborg application, new account form --- gsoc/forms.py | 1 + gsoc/models.py | 2 +- gsoc/templates/registration/login.html | 7 ++++-- gsoc/templates/registration/new_account.html | 23 ++++++++++++++++++++ gsoc/urls.py | 1 + gsoc/views.py | 18 ++++++++++++++- suborg/templates/register_suborg.html | 5 +++++ suborg/views.py | 22 ++++++++++++++----- 8 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 gsoc/templates/registration/new_account.html diff --git a/gsoc/forms.py b/gsoc/forms.py index c32ac504..d4dcde7c 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -56,6 +56,7 @@ class Meta: model = SubOrgDetails exclude = ('accepted', 'last_message', 'changed') widgets = { + 'suborg_admin_email': forms.HiddenInput(), 'gsoc_year': forms.HiddenInput(), } diff --git a/gsoc/models.py b/gsoc/models.py index d02a7a3a..9bd29503 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -659,7 +659,7 @@ def is_usable(self): def create_user(self, *args, is_staff=True, **kwargs): namespace = str(uuid.uuid4()) email = kwargs.get('email', self.email) - user = User.objects.create(*args, is_staff=is_staff, + user, status = User.objects.get_or_create(*args, is_staff=is_staff, email=email, **kwargs) role = {k: v for v, k in UserProfile.ROLES} profile = UserProfile.objects.create(user=user, role=self.user_role, diff --git a/gsoc/templates/registration/login.html b/gsoc/templates/registration/login.html index 7eec68b9..a27d214d 100644 --- a/gsoc/templates/registration/login.html +++ b/gsoc/templates/registration/login.html @@ -33,18 +33,21 @@
    {{ form.password.label_tag }} - {{ form.password }} + {{ form.password }}
    - {# Assumes you setup the password_reset view in your URLconf #} + {# Assumes you setup the password_reset view in your URLconf #} Lost password?
    + {% endif %} diff --git a/gsoc/templates/registration/new_account.html b/gsoc/templates/registration/new_account.html new file mode 100644 index 00000000..77f10a87 --- /dev/null +++ b/gsoc/templates/registration/new_account.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %} +New Account +{% endblock %} + +{% block content %} +
    +
    +
    + {% csrf_token %} +

    Create New Account

    + +

    + + +

    + + +
    + +
    +{% endblock %} \ No newline at end of file diff --git a/gsoc/urls.py b/gsoc/urls.py index 2c597fab..225c52e4 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -31,6 +31,7 @@ # Add Django site authentication urls (for login, logout, password management) urlpatterns += [ url('accounts/', include('django.contrib.auth.urls')), + url('accounts/new', gsoc.views.new_account_view, name='new_account'), url('accounts/register', gsoc.views.register_view, name='register') ] diff --git a/gsoc/views.py b/gsoc/views.py index 3b12790e..54521dbf 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,7 +1,8 @@ from gsoc import settings from .forms import ProposalUploadForm -from .models import RegLink, ProposalTextValidator, Comment, ArticleReview +from .models import (RegLink, ProposalTextValidator, Comment, ArticleReview, + GsocYear) import io import os @@ -128,6 +129,21 @@ def confirm_proposal_view(request): return shortcuts.HttpResponse() +def new_account_view(request): + if request.method == 'POST': + email = request.POST.get('email', None) + gsoc_year = GsocYear.objects.first() + if email: + RegLink.objects.create(user_role=0, + user_gsoc_year=gsoc_year, + email=email) + messages.success(request, 'You will get the registration link sent to your email soon') + else: + messages.error(request, 'An error occured, try again!') + return shortcuts.redirect('/') + return shortcuts.render(request, 'registration/new_account.html') + + def register_view(request): reglink_id = request.GET.get('reglink_id', request.POST.get('reglink_id', '')) try: diff --git a/suborg/templates/register_suborg.html b/suborg/templates/register_suborg.html index 3a08d72d..ce7435cd 100644 --- a/suborg/templates/register_suborg.html +++ b/suborg/templates/register_suborg.html @@ -18,6 +18,11 @@ {% block content %}
    + {% if message %} +
    + Please review your application. {{ message }} +
    + {% endif %}

    Apply for participating in GSoC@PSF as a SubOrg!

    {% csrf_token %} diff --git a/suborg/views.py b/suborg/views.py index 474d045c..6084653f 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -1,6 +1,7 @@ from gsoc.forms import SubOrgApplicationForm from gsoc.models import GsocYear, SubOrgDetails, RegLink +from django.contrib.auth.models import User from django.contrib.auth import decorators from django.shortcuts import render, redirect from django.forms import modelformset_factory @@ -15,10 +16,25 @@ def is_suborg_admin(user): return user.is_current_year_suborg_admin() +@decorators.login_required def register_suborg(request): if request.method == 'GET': + email = request.user.email + user = User.objects.filter(email=email).first() gsoc_year = GsocYear.objects.first() - form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year}) + instance = SubOrgDetails.objects.filter(suborg_admin_email=email, + gsoc_year=gsoc_year).first() + if instance: + form = SubOrgApplicationForm(instance=instance) + message = instance.last_message + else: + form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year, + 'suborg_admin_email': request.user.email}) + message = None + return render(request, 'register_suborg.html', { + 'form': form, + 'message': message + }) elif request.method == 'POST': form = SubOrgApplicationForm(request.POST, request.FILES) @@ -28,10 +44,6 @@ def register_suborg(request): suborg_details.save() return redirect(reverse('suborg:post_register')) - return render(request, 'register_suborg.html', { - 'form': form, - }) - def post_register(request): if request.method == 'GET': From b0fa9223a4f21c3a26f3763ea413b981bb70c5bb Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 21 Jun 2019 14:43:19 +0530 Subject: [PATCH 0317/1137] Fix pep8 warnings --- gsoc/admin.py | 2 +- gsoc/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index dc391d07..d268a18d 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -451,7 +451,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'blog_url', 'link', 'changed', 'accepted')} ), ('Review', { 'fields': ('last_message', )} - )) + )) change_form_template = 'admin/suborg_details_change_form.html' def save_model(self, request, obj, form, change): diff --git a/gsoc/models.py b/gsoc/models.py index 9bd29503..3ec6b3df 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -275,7 +275,7 @@ def send_review(self): } scheduler_data = build_send_mail_json(self.suborg_admin_email, template='suborg_review.html', - subject='Review your SubOrg Application'\ + subject='Review your SubOrg Application' ' for GSoC@PSF {}'. format(self.gsoc_year.gsoc_year), template_data=template_data) From 5bd2bce8fef4611eed290da5eea22e6467a2e2b1 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 21 Jun 2019 14:44:47 +0530 Subject: [PATCH 0318/1137] Fix pep8 warnings --- gsoc/admin.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index d268a18d..6c975012 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -440,18 +440,28 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', 'blog_url', 'link', 'accepted', 'changed' ) - fieldsets = (('Details', { - 'fields': ( - 'gsoc_year', 'reason_for_participation', 'suborg_admin_email', - 'mentors_student_engagement', 'students_on_schedule', 'students_involvement_gsoc', - 'students_involvement_after', 'past_gsoc_experience', 'past_years', - 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', - 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', - 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', - 'blog_url', 'link', 'changed', 'accepted')} - ), ('Review', { - 'fields': ('last_message', )} - )) + fieldsets = ( + ( + 'Details', { + 'fields': ( + 'gsoc_year', 'reason_for_participation', + 'suborg_admin_email', + 'mentors_student_engagement', 'students_on_schedule', + 'students_involvement_gsoc', + 'students_involvement_after', 'past_gsoc_experience', + 'past_years', + 'suborg_in_past', 'applied_but_not_selected', + 'year_of_start', + 'source_code', 'docs', 'anything_else', 'suborg_name', + 'description', + 'logo', 'primary_os_license', 'ideas_list', 'chat', + 'mailing_list', 'twitter_url', + 'blog_url', 'link', 'changed', 'accepted' + ) + } + ), + ('Review', {'fields': ('last_message', )}) + ) change_form_template = 'admin/suborg_details_change_form.html' def save_model(self, request, obj, form, change): From 0ec141018dc7f8d4d8277ed645631bc864bfb889 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 23 Jun 2019 12:33:24 +0530 Subject: [PATCH 0319/1137] Fix minor typo --- suborg/views.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/suborg/views.py b/suborg/views.py index 6084653f..7643e1b9 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -18,23 +18,18 @@ def is_suborg_admin(user): @decorators.login_required def register_suborg(request): + user = User.objects.filter(email=email).first() + email = request.user.email + gsoc_year = GsocYear.objects.first() + instance = SubOrgDetails.objects.filter(suborg_admin_email=email, + gsoc_year=gsoc_year).first() + message = instance.last_message if instance else None if request.method == 'GET': - email = request.user.email - user = User.objects.filter(email=email).first() - gsoc_year = GsocYear.objects.first() - instance = SubOrgDetails.objects.filter(suborg_admin_email=email, - gsoc_year=gsoc_year).first() if instance: form = SubOrgApplicationForm(instance=instance) - message = instance.last_message else: form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year, 'suborg_admin_email': request.user.email}) - message = None - return render(request, 'register_suborg.html', { - 'form': form, - 'message': message - }) elif request.method == 'POST': form = SubOrgApplicationForm(request.POST, request.FILES) @@ -43,6 +38,11 @@ def register_suborg(request): suborg_details.changed = True suborg_details.save() return redirect(reverse('suborg:post_register')) + + return render(request, 'register_suborg.html', { + 'form': form, + 'message': message + }) def post_register(request): From 121695151646184f06e5d61e0aa31ce314e7c275 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 23 Jun 2019 13:24:09 +0530 Subject: [PATCH 0320/1137] Change ideas list to url field; Migrate db --- gsoc/migrations/0033_auto_20190623_0751.py | 18 ++++++++++++++++++ gsoc/models.py | 5 +---- project.db | Bin 1499136 -> 1499136 bytes 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 gsoc/migrations/0033_auto_20190623_0751.py diff --git a/gsoc/migrations/0033_auto_20190623_0751.py b/gsoc/migrations/0033_auto_20190623_0751.py new file mode 100644 index 00000000..4845c582 --- /dev/null +++ b/gsoc/migrations/0033_auto_20190623_0751.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.9 on 2019-06-23 07:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0032_auto_20190621_0347'), + ] + + operations = [ + migrations.AlterField( + model_name='suborgdetails', + name='ideas_list', + field=models.URLField(verbose_name='Ideas List'), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 3ec6b3df..544abf5d 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -223,10 +223,7 @@ class SubOrgDetails(models.Model): help_text='Must be a 24-bit PNG, minimum height 256 pixels.') primary_os_license = models.CharField(max_length=50, verbose_name='Primary Open Source License') - ideas_list = models.TextField( - verbose_name='Ideas List', - help_text='Write the ideas one by one, separated with newlines.' - ) + ideas_list = models.URLField(verbose_name='Ideas List') chat = models.URLField(null=True, blank=True) mailing_list = models.EmailField(null=True, blank=True) diff --git a/project.db b/project.db index 4e20063da66f0ebc6e8423af42da1e7b620e601f..5ba16c5e8890d6c332b45a3f142abc8f1e217ad3 100644 GIT binary patch delta 514 zcmXZY&ui0g6aet=H%;@qO=H_{58@2-;5xICH0weuY88SL#_XXE>0n9LWo}q3tkrYJ zgzYkDDHy!8;9+>xfEv~zs4wMBN4E;bb+Mo?E#ysAw@L-GWEOHot7uq@hnKZfcv(wx(c4rP z@o0*rsk9}Yi&ehN?XU;T1Wm7l`q)?8LGZ&(FNT8#8DKj8mwo~ZJfy?Pb~^XFR}nD& z|A!Cqr;Ef)41b4#HWHr+8b5<<4zstIWhy3D;hFzn+}+T2NgLDB09B!^h)34(Yc^vT zR5ue;T#^a?cqEA%LC+;>C~uPYRKQM>nc$+E;QAd#3RI6ogGcj8BlzV*d(0d~8(K;n5(eoOAHA>$!)X3y4EG#3KQTNJ27F zkcu>zF+F$q*cr&En60@8Dk=gBhdfpv39OC zm74rS>WU+w&V8JoW6M8f-+X&{;P{__7nWfWCoBMOjB^D{@1_a5m|j6P+O}{1IjiNI zxf$VtsKg`i)okjg+I`hk=7=YQbX8mt#yE5_^L(0u_I9ZkNTq6(JCaz#rzN%&(pgAY z-=_|{{=0BD`w-GPm*0t#VE=J@XFr{H(&RJnv3lsX|N)ScK^E8R)ov_nsJ8Si( zv-WUxwPM^OTiCRbt!&eEQR`mnMN%n>xHm7SO=VUK>~eN1up{8{hswA@5%||PdSSNF Yp`UC0hN{9IzU-nnv+{6Zo(-w Date: Sun, 23 Jun 2019 13:24:47 +0530 Subject: [PATCH 0321/1137] Fix pep8 warnings --- suborg/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suborg/views.py b/suborg/views.py index 7643e1b9..4344ce09 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -38,7 +38,7 @@ def register_suborg(request): suborg_details.changed = True suborg_details.save() return redirect(reverse('suborg:post_register')) - + return render(request, 'register_suborg.html', { 'form': form, 'message': message From 487bf3cbb2dc33b0b8cf158430159e18eaed6c77 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 23 Jun 2019 16:32:03 +0530 Subject: [PATCH 0322/1137] Fix typo --- project.db | Bin 1499136 -> 1499136 bytes suborg/views.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/project.db b/project.db index 5ba16c5e8890d6c332b45a3f142abc8f1e217ad3..30fcb0b68cf3417f44c02d813047eea1a07cc6c1 100644 GIT binary patch delta 494 zcmZoT5Z!PfdV)0L;)ycOjEgrWaOyH@ZO+ooDH1X=RxmWQGBC6?!{F>@otPFyjzNvZ1xyA9udS-eC@wusarSWqZ#uin1 zMQMg9`Q?TJ+kwiS2rBUL`7`i8;y=v4guj*FonM2WlRu5`72j#THGI8%d3^qx1rxOS zrsu5_sHr z^$1HF(4YdAxF%}>R^Jv@M?%hva(By(Gz!Z0Gm6TL%*rFb|~n#txF*curaS_0jT#p&CR JJQcP11pvP?tKk3u delta 264 zcmZoT5Z!PfdV)0L{E0HojPo}paOyH@ZqCxpDH1X?Q!p^GGBUC?!{1XFM7$YVNCa7$`kS}N=z{bm`z`*x!J5Ph)d)~`zm?yeUxS~MKaKAd-)X)ze7$^mn*|em_@?`>6Xa^ISSJX?LO?7G#3I`( r)`|8Q3b3&rVc>4za^sl69>sHr^$1YIELJAQbrT&$wjX&aYV!*K=6hnt diff --git a/suborg/views.py b/suborg/views.py index 4344ce09..9c65db40 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -18,8 +18,8 @@ def is_suborg_admin(user): @decorators.login_required def register_suborg(request): - user = User.objects.filter(email=email).first() email = request.user.email + user = User.objects.filter(email=email).first() gsoc_year = GsocYear.objects.first() instance = SubOrgDetails.objects.filter(suborg_admin_email=email, gsoc_year=gsoc_year).first() From 40a53d86dd53dfd99461c271661a80ea598c120d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 23 Jun 2019 16:54:22 +0530 Subject: [PATCH 0323/1137] Minor changes --- gsoc/forms.py | 4 ++-- gsoc/migrations/0034_auto_20190623_1123.py | 23 +++++++++++++++++++++ gsoc/models.py | 4 ++-- project.db | Bin 1499136 -> 1499136 bytes 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 gsoc/migrations/0034_auto_20190623_1123.py diff --git a/gsoc/forms.py b/gsoc/forms.py index d4dcde7c..cb956b66 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -76,8 +76,8 @@ def clean(self): contact = list(filter(lambda a: a is not None, contact)) - if len(contact) < 3: - raise ValidationError('At least three out of the five contact' + if len(contact) < 1: + raise ValidationError('At least one out of the five contact' 'details should be entered') if past_exp and len(past_years) == 0: diff --git a/gsoc/migrations/0034_auto_20190623_1123.py b/gsoc/migrations/0034_auto_20190623_1123.py new file mode 100644 index 00000000..fc6cec9b --- /dev/null +++ b/gsoc/migrations/0034_auto_20190623_1123.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.9 on 2019-06-23 11:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0033_auto_20190623_0751'), + ] + + operations = [ + migrations.AlterField( + model_name='suborgdetails', + name='chat', + field=models.CharField(blank=True, max_length=80, null=True), + ), + migrations.AlterField( + model_name='suborgdetails', + name='mailing_list', + field=models.CharField(blank=True, max_length=80, null=True), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 544abf5d..444ec60a 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -225,8 +225,8 @@ class SubOrgDetails(models.Model): verbose_name='Primary Open Source License') ideas_list = models.URLField(verbose_name='Ideas List') - chat = models.URLField(null=True, blank=True) - mailing_list = models.EmailField(null=True, blank=True) + chat = models.CharField(max_length=80, null=True, blank=True) + mailing_list = models.CharField(max_length=80, null=True, blank=True) twitter_url = models.URLField(null=True, blank=True) blog_url = models.URLField(null=True, blank=True) link = models.URLField(null=True, blank=True, verbose_name='Any other link') diff --git a/project.db b/project.db index 30fcb0b68cf3417f44c02d813047eea1a07cc6c1..f9b2c24212380d7f0cdd9aea06b77b1cf3ba88cc 100644 GIT binary patch delta 486 zcmX}nKWGzC7y$71-u=s6{#;{o?`pK6rJxpadVkDeBZ7(t)hQJRX(hRMUV@7kv<^;9 ztXP*|Meu<*=ui+hiSiVRb`;zkoCF=*9CRtV9Q4cL2fq(KKKTChcUJm4E1L(3TL|GB zAV!c7Qag{T9^QH#7IfSlCHn4ud}S2Re<%3UdQja6su&O;01-$)1`5ak6=)y}av%>1 zKnD|`2=;+V9#l&o=LAz*V@jiB5p8W$49{z)>VriI87I$lZzYj!d&gVt+p9^-wVegq zcfFS5xZb$1Y~OOd8ON!+UcKfYwcVP(;B9O#m-zOw5lFM}PaZc>VIqGwm*#G2P4#}} zh4M$fD5k;>@&z{^7sF@lLufqbK}c6IuUw~nA-i54Mv56l!4iAeHOl;N$M~f@naPM;>>26rX|;kRqN5B}rsYMV>z9a*Q#b_SU7W(c z!=hTj@8Qq4_@G)5FN|CN<3VkhD5m3(&B^g*lOMZE-w}4GLuc8~YxEWS{7=fVr!g(F kuQ6?~H!-Ec345NO?a*&TL`e2wvMY;&Bf}oQoY0rOJ*QTK(f|Me delta 382 zcmWm9ze_?<6bJBouUFqsf0gfwg+(-!@EWwDEh2bSn}b6s$jf>b6zR|(U^wU^SPhpN zBygy;gl>VTTWh#9Iy5yULZi@;%jdv39KM`e>Sap3Ono-kLt*vtpST5a0q7xPb>qKn4m>fd+Ko1wP;h0T2XJU|LvasJrN5 zYMV>R&`K+4SJg^89TM%dVY%l?9Vuvp^^tz84YZW9C7()VPalmYkKEk`?FR4I#)IhO zjRdB?Z@LW;n^X)>jDYN({3Z&62)-FoL5s%tH;UgO(n8vvYASQclCc!^>=Lg(1-oMi zToTzs;}TQ7fl|;WwdzLgObW;2q#ovD+X#zzj=d4?b=V`ncbLw*4x8bzJY%lJ`WpXq k*a81JW(#5`&%TK0=ZunzB1B%{kQYL)mTTu?zrdb~|B#(;djJ3c From 39c3a9c0f723fff140233dd44e9f5df69fae789a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 23 Jun 2019 17:19:54 +0530 Subject: [PATCH 0324/1137] Update suborg form --- gsoc/forms.py | 2 ++ gsoc/templates/admin/suborg_details_change_form.html | 2 +- suborg/views.py | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index cb956b66..86ac6cb9 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -58,6 +58,8 @@ class Meta: widgets = { 'suborg_admin_email': forms.HiddenInput(), 'gsoc_year': forms.HiddenInput(), + 'past_years': forms.CheckboxSelectMultiple(), + 'applied_but_not_selected': forms.CheckboxSelectMultiple(), } def clean(self): diff --git a/gsoc/templates/admin/suborg_details_change_form.html b/gsoc/templates/admin/suborg_details_change_form.html index 0ddbca0b..280e7edd 100644 --- a/gsoc/templates/admin/suborg_details_change_form.html +++ b/gsoc/templates/admin/suborg_details_change_form.html @@ -35,6 +35,6 @@ {% block submit_buttons_bottom %}
    Accept - +
    {% endblock %} diff --git a/suborg/views.py b/suborg/views.py index 9c65db40..651e375e 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -33,8 +33,10 @@ def register_suborg(request): elif request.method == 'POST': form = SubOrgApplicationForm(request.POST, request.FILES) + if instance: + form = SubOrgApplicationForm(request.POST, request.FILES, instance=instance) if form.is_valid(): - suborg_details = form.save(commit=False) + suborg_details = form.save() suborg_details.changed = True suborg_details.save() return redirect(reverse('suborg:post_register')) From 4fd995f3e789399231f794929cfc1be1d871714d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 26 Jun 2019 10:18:13 +0530 Subject: [PATCH 0325/1137] Add support for multiple applications --- gsoc/admin.py | 7 +++- gsoc/forms.py | 16 +++++++- gsoc/models.py | 16 ++++++-- suborg/templates/application_list.html | 21 ++++++++++ suborg/urls.py | 5 ++- suborg/views.py | 53 ++++++++++++++++++++------ 6 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 suborg/templates/application_list.html diff --git a/gsoc/admin.py b/gsoc/admin.py index 6c975012..19e15323 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -438,7 +438,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', - 'blog_url', 'link', 'accepted', 'changed' + 'blog_url', 'link', 'accepted', 'changed', 'last_updated_at', 'last_updated_by', ) fieldsets = ( ( @@ -456,7 +456,8 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', - 'blog_url', 'link', 'changed', 'accepted' + 'blog_url', 'link', 'changed', 'accepted', + 'last_updated_at', 'last_updated_by' ) } ), @@ -466,6 +467,8 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.changed = False + obj.last_updated_at = timezone.now() + obj.last_updated_by = request.user obj.send_review() super(SubOrgDetailsAdmin, self).save_model(request, obj, form, change) diff --git a/gsoc/forms.py b/gsoc/forms.py index 86ac6cb9..55e97096 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -54,7 +54,7 @@ class Meta: class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails - exclude = ('accepted', 'last_message', 'changed') + exclude = ('accepted', 'last_message', 'changed', 'last_updated_at', 'last_updated_by') widgets = { 'suborg_admin_email': forms.HiddenInput(), 'gsoc_year': forms.HiddenInput(), @@ -67,6 +67,8 @@ def clean(self): past_exp = cd.get('past_gsoc_experience') past_years = cd.get('past_years').all() applied_not_selected = cd.get('applied_but_not_selected').all() + suborg_name = cd.get('suborg_name') + suborg = cd.get('suborg') contact = [ cd.get('chat', None), @@ -78,6 +80,18 @@ def clean(self): contact = list(filter(lambda a: a is not None, contact)) + if not (suborg or suborg_name): + raise ValidationError('Either suborg should be selected or' + 'the suborg name') + + if not suborg and suborg_name == suborg.suborg_name: + raise ValidationError('Suborg with the entered name exists,' + 'Please select from the list') + + if suborg and suborg_name: + raise ValidationError('Either suborg should be selected or' + 'the suborg name, not both') + if len(contact) < 1: raise ValidationError('At least one out of the five contact' 'details should be entered') diff --git a/gsoc/models.py b/gsoc/models.py index 444ec60a..9b34f15e 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -217,7 +217,12 @@ class SubOrgDetails(models.Model): verbose_name='Anything else we should know (optional)' ) - suborg_name = models.CharField(max_length=80, verbose_name='Name') + suborg = models.ForeignKey(SubOrg, null=True, blank=True, + on_delete=models.CASCADE, verbose_name='Select your suborg, if '\ + 'you have applied before') + suborg_name = models.CharField(max_length=80, verbose_name='If applying for the first time'\ + ' enter the name of your suborg', + null=True, blank=True) description = models.TextField(verbose_name='A very short description of your organization') logo = models.ImageField(upload_to='logos/', verbose_name='Your organization logo', help_text='Must be a 24-bit PNG, minimum height 256 pixels.') @@ -232,6 +237,9 @@ class SubOrgDetails(models.Model): link = models.URLField(null=True, blank=True, verbose_name='Any other link') last_message = models.TextField(null=True, blank=True) + last_updated_at = models.DateTimeField(null=True, blank=True) + last_updated_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) + accepted = models.BooleanField(default=False) changed = models.BooleanField(default=None, null=True) @@ -240,11 +248,13 @@ class Meta: def accept(self): self.accepted = True + if not self.suborg: + self.suborg = SubOrg.objects.create(suborg_name=self.suborg_name) self.save() template_data = { 'gsoc_year': self.gsoc_year.gsoc_year, - 'suborg_name': self.suborg_name, + 'suborg_name': self.suborg.suborg_name, } scheduler_data = build_send_mail_json(self.suborg_admin_email, template='suborg_accept.html', @@ -254,8 +264,6 @@ def accept(self): Scheduler.objects.create(command='send_email', data=scheduler_data) - suborg = SubOrg.objects.create(suborg_name=self.suborg_name) - RegLink.objects.create(user_role=1, user_suborg=suborg, user_gsoc_year=self.gsoc_year, diff --git a/suborg/templates/application_list.html b/suborg/templates/application_list.html new file mode 100644 index 00000000..e3f5a21b --- /dev/null +++ b/suborg/templates/application_list.html @@ -0,0 +1,21 @@ +{% extends CMS_TEMPLATE %} + +{% block content %} +
    +

    Select any of the applications to change

    + +

    + Want to create a new application? +

    +
    +{% endblock %} \ No newline at end of file diff --git a/suborg/urls.py b/suborg/urls.py index dd2009f0..5ad5f9cd 100644 --- a/suborg/urls.py +++ b/suborg/urls.py @@ -4,7 +4,10 @@ urlpatterns = [ url('^application/', include([ - url('^$', views.register_suborg, name='register_suborg'), + url('^$', views.application_list, name='application_list'), + url('^new/', views.register_suborg, name='register_suborg'), + url('^update/(?P[0-9]+)/', views.update_application, + name='update_application'), url('^thanks/', views.post_register, name='post_register'), url(r'^accept/(?P[0-9]+)/', views.accept_application, name='accept_application'), diff --git a/suborg/views.py b/suborg/views.py index 651e375e..332e5ca5 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -6,7 +6,7 @@ from django.shortcuts import render, redirect from django.forms import modelformset_factory from django.urls import reverse - +from django.contrib import messages def is_superuser(user): return user.is_superuser @@ -16,25 +16,54 @@ def is_suborg_admin(user): return user.is_current_year_suborg_admin() +@decorators.login_required +def application_list(request): + applications = SubOrgDetails.objects.filter(suborg_admin_email=request.user.email) + if len(applications) == 0: + return redirect(reverse('suborg:register_suborg')) + + return render(request, 'application_list.html', { + 'applications': applications, + }) + + @decorators.login_required def register_suborg(request): email = request.user.email - user = User.objects.filter(email=email).first() gsoc_year = GsocYear.objects.first() - instance = SubOrgDetails.objects.filter(suborg_admin_email=email, - gsoc_year=gsoc_year).first() - message = instance.last_message if instance else None + if request.method == 'GET': - if instance: - form = SubOrgApplicationForm(instance=instance) - else: - form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year, - 'suborg_admin_email': request.user.email}) + form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year, + 'suborg_admin_email': email}) elif request.method == 'POST': form = SubOrgApplicationForm(request.POST, request.FILES) - if instance: - form = SubOrgApplicationForm(request.POST, request.FILES, instance=instance) + if form.is_valid(): + suborg_details = form.save() + suborg_details.changed = True + suborg_details.save() + return redirect(reverse('suborg:post_register')) + + return render(request, 'register_suborg.html', { + 'form': form + }) + + +@decorators.login_required +def update_application(request, application_id): + email = request.user.email + instance = SubOrgDetails.objects.get(id=application_id) + if instance.suborg_admin_email != email: + messages.error(request, 'You do not have access to the application') + return redirect(reverse('suborg:application_list')) + + message = instance.last_message if instance else None + + if request.method == 'GET': + form = SubOrgApplicationForm(instance=instance) + + elif request.method == 'POST': + form = SubOrgApplicationForm(request.POST, request.FILES, instance=instance) if form.is_valid(): suborg_details = form.save() suborg_details.changed = True From 98adca22e150bea012ae94ab478d1bf7b4ca6969 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 26 Jun 2019 10:19:51 +0530 Subject: [PATCH 0326/1137] Migrate db --- gsoc/migrations/0035_auto_20190626_0437.py | 36 +++++++++++++++++++++ project.db | Bin 1499136 -> 1507328 bytes 2 files changed, 36 insertions(+) create mode 100644 gsoc/migrations/0035_auto_20190626_0437.py diff --git a/gsoc/migrations/0035_auto_20190626_0437.py b/gsoc/migrations/0035_auto_20190626_0437.py new file mode 100644 index 00000000..57cfe808 --- /dev/null +++ b/gsoc/migrations/0035_auto_20190626_0437.py @@ -0,0 +1,36 @@ +# Generated by Django 2.1.9 on 2019-06-26 04:37 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('gsoc', '0034_auto_20190623_1123'), + ] + + operations = [ + migrations.AddField( + model_name='suborgdetails', + name='last_updated_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='suborgdetails', + name='last_updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='suborgdetails', + name='suborg', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.SubOrg', verbose_name='Select your suborg, if you have applied before'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='suborg_name', + field=models.CharField(blank=True, max_length=80, null=True, verbose_name='If applying for the first time enter the name of your suborg'), + ), + ] diff --git a/project.db b/project.db index f9b2c24212380d7f0cdd9aea06b77b1cf3ba88cc..dc9d49cf1ff22be7aef19d3309bb82d310fdc118 100644 GIT binary patch delta 1105 zcmaKsOKclO7{_>$TU)R-1Us^miC!}QXo>7hlD2vi2G#GxvvNN}R60C8##s7M?lrSt+wsEk_?o?@im zj5PDj_y6mg$MVhi^75ny=mE)==Gnx_xKKA56bgR& z1;I7Fy>{?r>xJHD0od0nc-PWIp1Eszvt>)(W?FYIDgeZrwq&fM-US$}?olW^<7qCg zX%)jxd8>ndIN?&!pc4m7GZm$8mA+l?*kWZ zh+86%{1AMMTOvqkks*u*3dC0q-v9_4VL%uW1Y!?@M3@j1f<~AT9S8(MG8Rzwaii(2n3ag0?H_~>8#Xa8SVQyr;g{AyuX867_*tFd9f`ckx}j=x+~p=+Dp3)vs}3jE*){t*1-8{Q_Ks^*UO z_dhlk^8UM``mKBX^Xioy(ZLW~7>V7%D8b|-r?5MFt`iSfG@>i^iTNDWsF8MA#+wJ> zZ%N(`<+Q+o8Mu++MO{k^zXJI41uF+f&l6^SJu4gl(32CMg5PSw3Db0{TuIE$WZ``e z$5|$h9qSd{yK`F$Mjn3EVu#>6J=_GmbkX|cGbqGED_9qLCVy-7NMn(-=ViRE!9+=L zIRD6!s%Y6ndOnfQCIX?5mdp9mj~!}oEW?^h&N0RR91 delta 702 zcmZ9~OH30%7zgm3eRR89w!2H&En2B1G2sy&yDcpiA3&lY#+F9$&=s{nfv~lJ2~p(I zR*-rS0`@R?fPx8-n24H&Z1h0TV1hReUOY%VhzSP_ns6~8akzMJGQYX}Ghe=MGRb6n za{b1XLtMl#(2sOu=)f?hXS%`z7T-k3GSHsuE?dEAO-_-7hk@@mDO;wp!VWfb7b{Ky zP@dB(vz^o+w{5+b6C+z(ZhO`PGAY+>HpKxBOO-V;_Dpilk(tb1$*WRSucCN5qVc}| z(Y-9(%K|&0aXXNn2cyQ5d2o%J4w>QpNegN0z61`O(5&^4ehpSp?NKgWzXlK-ArKOw z5EDWpW`sdl#De4@M|Z3UZ`7{|9~W4hU&i)igT8lT%=<4wm`L?qvcuh?KCQ3BpUdf(QH8jAwluffURwnF3c zFPn0Uu(JtBhYG4u*21O}T7i+!r1T?U9J7qEP39R|%lklWkmD#HBP4R#p8xD>@ z*Pe6?^1IRyeDy=}74<9U+=}Y2RBF-we-LA1M>?xbhgc*3oBWeU5B_{#aB_?jqi`NG z1G>m`3FrAXoCk;KKVaYh9A?Bw3CTFYTh#I@IKeo{%SX|__1HTxK-*OnpR&QP5xE@P zfiF5Fmr>dw{{e7eRw#xCGlC3TCn<~3&?TP+@L5z2!QrUvG}?RRFF0=Uq1tYfB~>~x kNZW6!cq#z5JEcPC?3E?B+anJx39N!zU1}hNrFGf$7dl Date: Wed, 26 Jun 2019 10:23:25 +0530 Subject: [PATCH 0327/1137] Fix pep8 warnings --- gsoc/forms.py | 10 +++++----- gsoc/models.py | 4 ++-- suborg/views.py | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index 55e97096..5e2284bc 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -81,19 +81,19 @@ def clean(self): contact = list(filter(lambda a: a is not None, contact)) if not (suborg or suborg_name): - raise ValidationError('Either suborg should be selected or' + raise ValidationError('Either suborg should be selected or ' 'the suborg name') - + if not suborg and suborg_name == suborg.suborg_name: - raise ValidationError('Suborg with the entered name exists,' + raise ValidationError('Suborg with the entered name exists, ' 'Please select from the list') if suborg and suborg_name: - raise ValidationError('Either suborg should be selected or' + raise ValidationError('Either suborg should be selected or ' 'the suborg name, not both') if len(contact) < 1: - raise ValidationError('At least one out of the five contact' + raise ValidationError('At least one out of the five contact ' 'details should be entered') if past_exp and len(past_years) == 0: diff --git a/gsoc/models.py b/gsoc/models.py index 9b34f15e..52133c50 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -218,9 +218,9 @@ class SubOrgDetails(models.Model): ) suborg = models.ForeignKey(SubOrg, null=True, blank=True, - on_delete=models.CASCADE, verbose_name='Select your suborg, if '\ + on_delete=models.CASCADE, verbose_name='Select your suborg, if ' 'you have applied before') - suborg_name = models.CharField(max_length=80, verbose_name='If applying for the first time'\ + suborg_name = models.CharField(max_length=80, verbose_name='If applying for the first time' ' enter the name of your suborg', null=True, blank=True) description = models.TextField(verbose_name='A very short description of your organization') diff --git a/suborg/views.py b/suborg/views.py index 332e5ca5..2db5b9f0 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -8,6 +8,7 @@ from django.urls import reverse from django.contrib import messages + def is_superuser(user): return user.is_superuser @@ -31,7 +32,7 @@ def application_list(request): def register_suborg(request): email = request.user.email gsoc_year = GsocYear.objects.first() - + if request.method == 'GET': form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year, 'suborg_admin_email': email}) @@ -56,9 +57,9 @@ def update_application(request, application_id): if instance.suborg_admin_email != email: messages.error(request, 'You do not have access to the application') return redirect(reverse('suborg:application_list')) - + message = instance.last_message if instance else None - + if request.method == 'GET': form = SubOrgApplicationForm(instance=instance) From f0236fb883118d0651d837c8670b2775691df8d6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 26 Jun 2019 22:17:14 +0530 Subject: [PATCH 0328/1137] Redirect to application page on /suborg --- suborg/urls.py | 1 + suborg/views.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/suborg/urls.py b/suborg/urls.py index 5ad5f9cd..6564c44e 100644 --- a/suborg/urls.py +++ b/suborg/urls.py @@ -3,6 +3,7 @@ from . import views urlpatterns = [ + url('^$', views.home, name='home'), url('^application/', include([ url('^$', views.application_list, name='application_list'), url('^new/', views.register_suborg, name='register_suborg'), diff --git a/suborg/views.py b/suborg/views.py index 2db5b9f0..2b766c0a 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -17,6 +17,10 @@ def is_suborg_admin(user): return user.is_current_year_suborg_admin() +def home(request): + return redirect(reverse('suborg:register_suborg')) + + @decorators.login_required def application_list(request): applications = SubOrgDetails.objects.filter(suborg_admin_email=request.user.email) From b57791aed4485f2f790f7a4824c0ad5732f3aa3e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 27 Jun 2019 11:09:24 +0530 Subject: [PATCH 0329/1137] Fix event admin error regarding cal_link --- gsoc/admin.py | 8 +------- gsoc/models.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 19e15323..dd3d40e8 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -409,13 +409,7 @@ def has_change_permission(self, request, obj=None): class EventAdmin(admin.ModelAdmin): - list_display = ('title', 'start_date', 'end_date', 'cal_link') - - def cal_link(self, obj): - if obj.link: - return mark_safe('Goto Event'.format(obj.link)) - else: - return None + list_display = ('title', 'start_date', 'end_date', 'calendar_link') def has_add_permission(self, request, obj=None): return False diff --git a/gsoc/models.py b/gsoc/models.py index 52133c50..06e66c85 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -400,6 +400,17 @@ class Event(models.Model): blank=True) event_id = models.CharField(max_length=255, null=True, blank=True) + @property + def calendar_link(self): + if self.event_id: + with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + creds = pickle.load(token) + service = build('calendar', 'v3', credentials=creds) + event = service.events().get(calendarId=self.timeline.calendar_id, + eventId=self.event_id).execute() + return event.get('htmlLink', None) + return None + def add_to_calendar(self): with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: creds = pickle.load(token) From 74ee1820e091e0f9091b199ea34e30d8fd2395c3 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 27 Jun 2019 22:34:49 -0600 Subject: [PATCH 0330/1137] Update suborg_review.html --- gsoc/templates/email/suborg_review.html | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/email/suborg_review.html b/gsoc/templates/email/suborg_review.html index 29ebe4c4..42f55070 100644 --- a/gsoc/templates/email/suborg_review.html +++ b/gsoc/templates/email/suborg_review.html @@ -1,3 +1,10 @@ -{{ gsoc_year }} -{{ suborg_name }} -{{ message }} \ No newline at end of file +You are recieving this email because you applied as {{ suborg_name }} for GSoC with Python Software Foundation for GSoC {{ gsoc_year }}.
    +
    +You application has been reviewed and some changes are needed.
    +
    +{{ message }}
    +
    +If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org
    +
    +Thank you
    +PSF GSoC Team!
    From d25fe2438c8bf727a50085979a5814c5d091a5af Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 27 Jun 2019 22:35:20 -0600 Subject: [PATCH 0331/1137] Update suborg_accept.html --- gsoc/templates/email/suborg_accept.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gsoc/templates/email/suborg_accept.html b/gsoc/templates/email/suborg_accept.html index 5f0213b6..6f85ab54 100644 --- a/gsoc/templates/email/suborg_accept.html +++ b/gsoc/templates/email/suborg_accept.html @@ -1,2 +1,8 @@ -{{ gsoc_year }} -{{ suborg_name }} \ No newline at end of file +You are recieving this email because you applied as {{ suborg_name }} for GSoC with Python Software Foundation for GSoC {{ gsoc_year }}.
    +
    +You application has been reviewed and has been accepted.
    +
    +If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org
    +
    +Thank you
    +PSF GSoC Team!
    From a154ce1ee024255c873b22d41a86b407f8f94b20 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 10:33:55 +0530 Subject: [PATCH 0332/1137] Send mail to admin on runcron exception --- gsoc/common/utils/build_tasks.py | 160 ++++++++++++++------------- gsoc/common/utils/commands.py | 28 +---- gsoc/common/utils/tools.py | 27 +++++ gsoc/management/commands/runcron.py | 49 ++++++-- gsoc/models.py | 3 +- gsoc/templates/email/cron_error.html | 2 + settings_local.py.template | 1 + 7 files changed, 160 insertions(+), 110 deletions(-) create mode 100644 gsoc/templates/email/cron_error.html diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 33b15362..177a6d80 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -7,86 +7,94 @@ def build_pre_blog_reminders(builder): - data = json.loads(builder.data) - due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) - gsoc_year = GsocYear.objects.first() - profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() - for profile in profiles: - if profile.current_blog_count is not 0 and not (profile.hidden and - profile.reminder_disabled): - template_data = { - 'current_blog_count': profile.current_blog_count, - 'due_date': due_date.date.strftime('%d %B %Y') - } - - scheduler_data = build_send_mail_json(profile.user.email, - template='pre_blog_reminder.html', - subject='Reminder for Weekly Blog Post', - template_data=template_data) - - s = Scheduler.objects.create(command='send_email', - data=scheduler_data) + try: + data = json.loads(builder.data) + due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) + gsoc_year = GsocYear.objects.first() + profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() + for profile in profiles: + if profile.current_blog_count is not 0 and not (profile.hidden and + profile.reminder_disabled): + template_data = { + 'current_blog_count': profile.current_blog_count, + 'due_date': due_date.date.strftime('%d %B %Y') + } + scheduler_data = build_send_mail_json(profile.user.email, + template='pre_blog_reminder.html', + subject='Reminder for Weekly Blog Post', + template_data=template_data) -def build_post_blog_reminders(builder): - data = json.loads(builder.data) - last_due_date = BlogPostDueDate.objects.last() - due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) - if due_date == last_due_date: - blogs_count = 0 - else: - blogs_count = 1 - - gsoc_year = GsocYear.objects.first() - profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() - for profile in profiles: - if profile.current_blog_count > blogs_count and not (profile.hidden and - profile.reminder_disabled): - suborg = profile.suborg_full_name - mentors = UserProfile.objects.filter(suborg_full_name=suborg, role=2) - suborg_admins = UserProfile.objects.filter(suborg_full_name=suborg, role=1) - - activation_date = builder.activation_date.date() - - if activation_date - due_date.date == timezone.timedelta(days=1): - student_template = 'first_post_blog_reminder_student.html' - - elif activation_date - due_date.date == timezone.timedelta(days=3): - student_template = 'second_post_blog_reminder_student.html' - - mentors_emails = ['gsoc-admins@python.org'] - mentors_emails.extend([_.user.email for _ in mentors]) - mentors_emails.extend([_.user.email for _ in suborg_admins]) - - mentors_template_data = { - 'student_username': profile.user.username, - 'student_email': profile.user.email, - 'suborg_name': profile.suborg_full_name.suborg_name, - 'due_date': due_date.date.strftime('%d %B %Y'), - 'current_blog_count': profile.current_blog_count - } + s = Scheduler.objects.create(command='send_email', + data=scheduler_data) + return None + except Exception as e: + return str(e) - scheduler_data_mentors = build_send_mail_json( - mentors_emails, - template='post_blog_reminder_mentors.html', - subject='Weekly Blog Post missed by a Student of your Sub-Org', - template_data=mentors_template_data + +def build_post_blog_reminders(builder): + try: + data = json.loads(builder.data) + last_due_date = BlogPostDueDate.objects.last() + due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) + if due_date == last_due_date: + blogs_count = 0 + else: + blogs_count = 1 + + gsoc_year = GsocYear.objects.first() + profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() + for profile in profiles: + if profile.current_blog_count > blogs_count and not (profile.hidden and + profile.reminder_disabled): + suborg = profile.suborg_full_name + mentors = UserProfile.objects.filter(suborg_full_name=suborg, role=2) + suborg_admins = UserProfile.objects.filter(suborg_full_name=suborg, role=1) + + activation_date = builder.activation_date.date() + + if activation_date - due_date.date == timezone.timedelta(days=1): + student_template = 'first_post_blog_reminder_student.html' + + elif activation_date - due_date.date == timezone.timedelta(days=3): + student_template = 'second_post_blog_reminder_student.html' + + mentors_emails = ['gsoc-admins@python.org'] + mentors_emails.extend([_.user.email for _ in mentors]) + mentors_emails.extend([_.user.email for _ in suborg_admins]) + + mentors_template_data = { + 'student_username': profile.user.username, + 'student_email': profile.user.email, + 'suborg_name': profile.suborg_full_name.suborg_name, + 'due_date': due_date.date.strftime('%d %B %Y'), + 'current_blog_count': profile.current_blog_count + } + + scheduler_data_mentors = build_send_mail_json( + mentors_emails, + template='post_blog_reminder_mentors.html', + subject='Weekly Blog Post missed by a Student of your Sub-Org', + template_data=mentors_template_data ) - Scheduler.objects.create(command='send_email', - data=scheduler_data_mentors) - - student_template_data = { - 'current_blog_count': profile.current_blog_count, - 'due_date': due_date.date.strftime('%d %B %Y') - } - - scheduler_data_student = build_send_mail_json( - profile.user.email, - template=student_template, - subject='Reminder for Weekly Blog Post', - template_data=student_template_data + Scheduler.objects.create(command='send_email', + data=scheduler_data_mentors) + + student_template_data = { + 'current_blog_count': profile.current_blog_count, + 'due_date': due_date.date.strftime('%d %B %Y') + } + + scheduler_data_student = build_send_mail_json( + profile.user.email, + template=student_template, + subject='Reminder for Weekly Blog Post', + template_data=student_template_data ) - Scheduler.objects.create(command='send_email', - data=scheduler_data_student) + Scheduler.objects.create(command='send_email', + data=scheduler_data_student) + return None + except Exception as e: + return str(e) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index dbd8c596..983ab62e 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -1,39 +1,21 @@ import json from smtplib import SMTPResponseException, SMTPSenderRefused -from django.core.mail import EmailMessage -from django.conf import settings - -from django.template.loader import get_template -from django.template import TemplateDoesNotExist -from django.template import Template from django.contrib.auth.models import User from .irc import send_message from gsoc.models import Scheduler, RegLink, GsocYear, UserProfile, Event +from .tools import send_mail def send_email(scheduler: Scheduler): data = json.loads(scheduler.data) try: - data['template'] = get_template(f'email/{data["template"]}') - except TemplateDoesNotExist: - data['template'] = Template(data['template']) - context_dict = {} if not data['template_data'] else data['template_data'] - content = data['template'].render(context_dict) - if isinstance(data['send_to'], str): - data['send_to'] = [data['send_to']] - try: - send_email = EmailMessage( - body=content, - subject=settings.EMAIL_SUBJECT_PREFIX + data['subject'], - from_email=settings.SERVER_EMAIL, - reply_to=settings.REPLY_EMAIL, - to=data['send_to'], - ) - send_email.content_subtype = "html" - send_email.send() + send_mail(data['send_to'], + data['subject'], + data['template'], + data['template_data']) except SMTPSenderRefused as e: last_error = json.dumps({ "message": str(e), diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 9d26ef40..d52e2e5e 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -1,6 +1,12 @@ import json from collections.abc import Sequence +from django.core.mail import EmailMessage +from django.conf import settings +from django.template.loader import get_template +from django.template import TemplateDoesNotExist +from django.template import Template + def build_send_mail_json(send_to, template: str, @@ -21,3 +27,24 @@ def build_send_reminder_json(send_to, raise TypeError('send_to must be a sequence of email addresses ' 'or one email address as str!') return json.dumps(locals()) + + +def send_mail(send_to, subject, template, context={}): + try: + template = get_template(f'email/{template}') + except TemplateDoesNotExist: + template = Template(template) + + content = template.render(context) + if isinstance(send_to, str): + send_to = [send_to] + + send_email = EmailMessage( + body=content, + subject=settings.EMAIL_SUBJECT_PREFIX + subject, + from_email=settings.SERVER_EMAIL, + reply_to=settings.REPLY_EMAIL, + to=send_to, + ) + send_email.content_subtype = "html" + send_email.send() diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index 2a0400a0..4f76627c 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -2,10 +2,11 @@ from django.utils import timezone from django.core.management.base import BaseCommand +from django.conf import settings -import gsoc.settings as config from gsoc.models import Scheduler, GsocYear, UserProfile, Builder from gsoc.common.utils import commands, build_tasks +from gsoc.common.utils.tools import send_mail class Command(BaseCommand): @@ -28,7 +29,7 @@ def add_arguments(self, parser): '-t', '--timeout', nargs='?', - default=config.RUNCRON_TIMEOUT, + default=settings.RUNCRON_TIMEOUT, type=int, help='Set timeout' ) @@ -36,7 +37,7 @@ def add_arguments(self, parser): '-n', '--num_workers', nargs='?', - default=config.RUNCRON_NUM_WORKERS, + default=settings.RUNCRON_NUM_WORKERS, type=int, help='Set number of workers' ) @@ -54,15 +55,36 @@ def build_items(self, options): for builder in builders: self.stdout.write('Running build task {}:{}' .format(builder.category, builder.pk), ending='\n') - getattr(build_tasks, builder.category)(builder) - self.stdout.write(self.style - .SUCCESS('Finished build task {}:{}' - .format(builder.category, builder.pk)), - ending='\n') - builder.built = True - builder.save() + err = getattr(build_tasks, builder.category)(builder) + if not err: + self.stdout.write(self.style + .SUCCESS('Finished build task {}:{}' + .format(builder.category, builder.pk)), + ending='\n') + builder.built = True + builder.save() + + else: + self.stdout.write( + self.style.ERROR( + 'Build task {}:{} failed with error: {}' .format( + builder.category, + builder.pk, + err)), + ending='\n') + builder.built = False + builder.last_error = err + builder.save() + send_mail(settings.ADMIN_EMAIL, + 'Exception on runcron build_items', + 'cron_error.html', + { + 'message': err, + 'time': today, + }) def handle_process(self, scheduler): + today = timezone.now() self.stdout.write('Running command {}:{}' .format(scheduler.command, scheduler.id), ending='\n') err = getattr(commands, scheduler.command)(scheduler) @@ -85,6 +107,13 @@ def handle_process(self, scheduler): scheduler.success = False scheduler.last_error = err scheduler.save() + send_mail(settings.ADMIN_EMAIL, + 'Exception on runcron process_items', + 'cron_error.html', + { + 'message': err, + 'time': today, + }) def process_items(self, options): # custom handlers diff --git a/gsoc/models.py b/gsoc/models.py index 06e66c85..b66c70e7 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -367,8 +367,9 @@ class Builder(models.Model): category = models.CharField(max_length=40, choices=categories) activation_date = models.DateTimeField(null=True, blank=True) - built = models.BooleanField(default=False) + built = models.BooleanField(default=None, null=True) data = models.TextField() + last_error = models.TextField(null=True, default=None, blank=True) def __str__(self): return self.category diff --git a/gsoc/templates/email/cron_error.html b/gsoc/templates/email/cron_error.html new file mode 100644 index 00000000..fdc2fd1b --- /dev/null +++ b/gsoc/templates/email/cron_error.html @@ -0,0 +1,2 @@ +{{ message }} +{{ time }} \ No newline at end of file diff --git a/settings_local.py.template b/settings_local.py.template index ea807678..885fe73a 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -47,6 +47,7 @@ EMAIL_PORT = 25 #EMAIL_HOST_USER = "" #EMAIL_HOST_PASSWORD = "" REPLY_EMAIL = 'gsoc-admins@python.org' +ADMIN_EMAIL = 'gsoc-admins@python.org' # reCAPTCHA settings # update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually From e8faf370fe0b7ee14b0d6d1ab8b9b2793de7029a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 10:34:18 +0530 Subject: [PATCH 0333/1137] Migrate db --- gsoc/migrations/0036_auto_20190628_0502.py | 23 +++++++++++++++++++++ project.db | Bin 1507328 -> 1507328 bytes 2 files changed, 23 insertions(+) create mode 100644 gsoc/migrations/0036_auto_20190628_0502.py diff --git a/gsoc/migrations/0036_auto_20190628_0502.py b/gsoc/migrations/0036_auto_20190628_0502.py new file mode 100644 index 00000000..4305bd55 --- /dev/null +++ b/gsoc/migrations/0036_auto_20190628_0502.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.9 on 2019-06-28 05:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0035_auto_20190626_0437'), + ] + + operations = [ + migrations.AddField( + model_name='builder', + name='last_error', + field=models.TextField(blank=True, default=None, null=True), + ), + migrations.AlterField( + model_name='builder', + name='built', + field=models.BooleanField(default=None, null=True), + ), + ] diff --git a/project.db b/project.db index dc9d49cf1ff22be7aef19d3309bb82d310fdc118..759859694f622ed62ca4dcc9d05d4f02635c9e0b 100644 GIT binary patch delta 434 zcmZo@h-qkunIJ8=hJk^x0EqJ$fCK{rXX8X2W7ahcdL{}RQx?cG+j9Jw%&t($!tsaW z&t}1d3JxLu#w2D{j`ZUEpFZylUUo)7pkxhGE^FXq!36p3o)?6tax*b%1Kp_3xSjp3@ZTb4NiNpu z>;WQ%OkAu$;nSS9R*hb)45HSKAjcaR7@Nf>mX_ql8yOf{8kiYb#2c6z7=bvt24=cO z777NYRt6?khK72^=0-*)#_di~B0wy<-6=}!5UUsm!zBhzF%Bd4ScXe%8(BZ|$MS~p zh5_BOWBa89u|!T5z5r%rptbVM+>K?u_3YxRs!Ywg({=6$ZztW>=_W;!N5s zn9#$qnNQPI}mdKF((jn0Wmia^8hg~5c2^s zKM)H5u^}hPLn4j>+@*U>W=bgdJ&Z7vFtYOM! z4V)~PAiv%7g78#sCI(j^$C0}+si%xxTve5+nRR+%uGj|Fq|(fslF5afqSFf|2?#K1 zZfCzM{I`f%g3D<-dw_@`&=*`>?M_i5KrFi5DN5`Rs|Y*8B?eA04kPwhhD&T4SwHgw xX`tb|wqHsROXOr>U|`h(YExs~9-A+=wt$N<2N+VRK%BH)P=N6b|1t+=0RRRUS}p(p From ae152ff61b32a88932ce3fda237ca93eeb77e630 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 10:37:07 +0530 Subject: [PATCH 0334/1137] Fix reminder disabled to stop notifications --- gsoc/common/utils/build_tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 177a6d80..81047772 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -13,7 +13,7 @@ def build_pre_blog_reminders(builder): gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() for profile in profiles: - if profile.current_blog_count is not 0 and not (profile.hidden and + if profile.current_blog_count is not 0 and not (profile.hidden or profile.reminder_disabled): template_data = { 'current_blog_count': profile.current_blog_count, @@ -45,7 +45,7 @@ def build_post_blog_reminders(builder): gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() for profile in profiles: - if profile.current_blog_count > blogs_count and not (profile.hidden and + if profile.current_blog_count > blogs_count and not (profile.hidden or profile.reminder_disabled): suborg = profile.suborg_full_name mentors = UserProfile.objects.filter(suborg_full_name=suborg, role=2) From 0bcb65eb9df7b50d99524a3e66652f6183de902b Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 10:45:11 +0530 Subject: [PATCH 0335/1137] Display calendar link on the detailed view of events --- gsoc/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index dd3d40e8..8daad387 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -409,7 +409,8 @@ def has_change_permission(self, request, obj=None): class EventAdmin(admin.ModelAdmin): - list_display = ('title', 'start_date', 'end_date', 'calendar_link') + list_display = ('title', 'start_date', 'end_date') + fields = ('title', 'start_date', 'end_date', 'timeline', 'calendar_link') def has_add_permission(self, request, obj=None): return False From 9faab1f8bbd7e3750dcdcb2b6997224be0fdb3ea Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 10:47:47 +0530 Subject: [PATCH 0336/1137] Fix pep8 warnings --- gsoc/common/utils/commands.py | 6 +++--- gsoc/common/utils/tools.py | 4 ++-- gsoc/management/commands/runcron.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 983ab62e..ba979bd7 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -13,9 +13,9 @@ def send_email(scheduler: Scheduler): data = json.loads(scheduler.data) try: send_mail(data['send_to'], - data['subject'], - data['template'], - data['template_data']) + data['subject'], + data['template'], + data['template_data']) except SMTPSenderRefused as e: last_error = json.dumps({ "message": str(e), diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index d52e2e5e..0bd19f8b 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -34,11 +34,11 @@ def send_mail(send_to, subject, template, context={}): template = get_template(f'email/{template}') except TemplateDoesNotExist: template = Template(template) - + content = template.render(context) if isinstance(send_to, str): send_to = [send_to] - + send_email = EmailMessage( body=content, subject=settings.EMAIL_SUBJECT_PREFIX + subject, diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index 4f76627c..74e0f2ab 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -63,7 +63,7 @@ def build_items(self, options): ending='\n') builder.built = True builder.save() - + else: self.stdout.write( self.style.ERROR( From fbbe6be8207977365fb816aa90b8bf34a68af109 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 28 Jun 2019 00:47:22 -0600 Subject: [PATCH 0337/1137] Update cron_error.html --- gsoc/templates/email/cron_error.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/email/cron_error.html b/gsoc/templates/email/cron_error.html index fdc2fd1b..3ea5b668 100644 --- a/gsoc/templates/email/cron_error.html +++ b/gsoc/templates/email/cron_error.html @@ -1,2 +1,5 @@ +Blog Error at {{ time }} + +Message: + {{ message }} -{{ time }} \ No newline at end of file From 37ad78a140653b588d1181938163382ce32addbb Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 12:21:41 +0530 Subject: [PATCH 0338/1137] Change builder admin --- gsoc/admin.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 8daad387..98cb79ff 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -359,7 +359,18 @@ def used_stat(self, obj): admin.site.register(AddUserLog, AddUserLogAdmin) -admin.site.register(Builder) + + +class BuilderAdmin(admin.ModelAdmin): + list_display = ('category', 'short_data', 'built', 'last_error') + list_filter = ('category', 'built') + sortable_by = ('last_error') + + def short_data(self, obj): + return '{}...'.format(obj.data[:50]) + + +admin.site.register(Builder, BuilderAdmin) class BlogPostDueDateInline(admin.TabularInline): From 2995ce7846f792ed8b1a30b4fc8012ae1cb3d63e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 28 Jun 2019 12:40:10 +0530 Subject: [PATCH 0339/1137] Fix typo --- gsoc/management/commands/runcron.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index 74e0f2ab..b5f3ee6e 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -45,8 +45,8 @@ def add_arguments(self, parser): def build_items(self, options): # build tasks today = timezone.now() - x = Builder.objects.filter(built=False, activation_date=None).all() - y = Builder.objects.filter(built=False, activation_date__lte=today).all() + x = Builder.objects.filter(built=None, activation_date=None).all() + y = Builder.objects.filter(built=None, activation_date__lte=today).all() builders = x | y if len(builders) is 0: From 489440704dca3df0a14d1225363434c8e03629e6 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 28 Jun 2019 01:12:35 -0600 Subject: [PATCH 0340/1137] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b0900404..ade6ee14 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # $GIT_DIR/info/exclude or the core.excludesFile configuration variable as # described in https://git-scm.com/docs/gitignore settings_local.py +credentials.json # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] From 35d4910f8d13b7d858ec1839c40127b4fba60daf Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 29 Jun 2019 03:49:04 +0530 Subject: [PATCH 0341/1137] Fix url to access feed --- blogs_list/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blogs_list/urls.py b/blogs_list/urls.py index 42a56471..b6ade03f 100644 --- a/blogs_list/urls.py +++ b/blogs_list/urls.py @@ -4,6 +4,6 @@ from .feeds import BlogsFeed urlpatterns = [ - url('^', list_blogs, name='list_blogs'), + url('^$', list_blogs, name='list_blogs'), url('feed/', BlogsFeed(), name='feed') ] From 761533f083078682ff62189e9b837e48931ff857 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 28 Jun 2019 01:12:35 -0600 Subject: [PATCH 0342/1137] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b0900404..ade6ee14 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # $GIT_DIR/info/exclude or the core.excludesFile configuration variable as # described in https://git-scm.com/docs/gitignore settings_local.py +credentials.json # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] From 64ad9e653a9b405b3f9fa67f1c89cecaa1cb50b6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 29 Jun 2019 03:49:04 +0530 Subject: [PATCH 0343/1137] Fix url to access feed --- blogs_list/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blogs_list/urls.py b/blogs_list/urls.py index 42a56471..b6ade03f 100644 --- a/blogs_list/urls.py +++ b/blogs_list/urls.py @@ -4,6 +4,6 @@ from .feeds import BlogsFeed urlpatterns = [ - url('^', list_blogs, name='list_blogs'), + url('^$', list_blogs, name='list_blogs'), url('feed/', BlogsFeed(), name='feed') ] From 1d9f94422593d7404b47fcc12f929b5a3005dd62 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 29 Jun 2019 20:18:57 +0530 Subject: [PATCH 0344/1137] Add util functions to render templates and push file to github repo --- gsoc/common/utils/tools.py | 19 +++++++++++++++++++ requirements.txt | 2 ++ settings_local.py.template | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 0bd19f8b..25555a15 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -7,6 +7,8 @@ from django.template import TemplateDoesNotExist from django.template import Template +from github import Github + def build_send_mail_json(send_to, template: str, @@ -48,3 +50,20 @@ def send_mail(send_to, subject, template, context={}): ) send_email.content_subtype = "html" send_email.send() + + +def render_site_template(template, context): + try: + template = get_template(f'site/{template}') + except TemplateDoesNotExist: + template = Template(template) + + return template.render(context) + + +def push_site_template(file_path, content): + content = content.encode() + g = Github(settings.GITHUB_ACCESS_TOKEN) + repo = g.get_repo(settings.STATIC_SITE_REPO) + f = repo.get_contents(file_path) + repo.update_file(f.path, f'Update {file_path}', content, f.sha) diff --git a/requirements.txt b/requirements.txt index 7b5b46a2..c768c0c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,5 @@ profanityfilter>=2.0.6 google-api-python-client google-auth-httplib2 google-auth-oauthlib + +PyGithub \ No newline at end of file diff --git a/settings_local.py.template b/settings_local.py.template index 885fe73a..50750980 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -70,3 +70,7 @@ GOOGLE_API_CLIENT_CONFIG = { "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"] } } + +# GITHUB SETTINGS +STATIC_SITE_REPO = 'python-gsoc/python-gsoc.github.io' +GITHUB_ACCESS_TOKEN = '' From 87741a9472eb42eefbf57fae17c897932e814a73 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 29 Jun 2019 22:26:47 +0530 Subject: [PATCH 0345/1137] Auto generate deadlines.html on timeline creation --- gsoc/common/utils/commands.py | 20 +++++- gsoc/common/utils/tools.py | 2 +- gsoc/models.py | 23 +++++++ gsoc/templates/site/deadlines.html | 104 +++++++++++++++++++++++++++++ settings_local.py.template | 3 + 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 gsoc/templates/site/deadlines.html diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index ba979bd7..af761125 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -2,11 +2,12 @@ from smtplib import SMTPResponseException, SMTPSenderRefused from django.contrib.auth.models import User +from django.conf import settings from .irc import send_message -from gsoc.models import Scheduler, RegLink, GsocYear, UserProfile, Event -from .tools import send_mail +from gsoc.models import Scheduler, RegLink, GsocYear, UserProfile, Event, BlogPostDueDate +from .tools import send_mail, render_site_template, push_site_template def send_email(scheduler: Scheduler): @@ -110,3 +111,18 @@ def add_calendar_event(scheduler: Scheduler): return None except Exception as e: return str(e) + + +def update_site_template(scheduler: Scheduler): + try: + template = json.loads(scheduler.data)['template'] + if template == 'deadlines.html': + gsoc_year = GsocYear.objects.first() + context = { + 'events': Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), + 'duedates': BlogPostDueDate.objects.filter(timeline__gsoc_year=gsoc_year).all(), + } + content = render_site_template(template, context) + push_site_template(settings.GITHUB_FILE_PATH[template], content) + except Exception as e: + return str(e) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 25555a15..734af2a2 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -57,7 +57,7 @@ def render_site_template(template, context): template = get_template(f'site/{template}') except TemplateDoesNotExist: template = Template(template) - + return template.render(context) diff --git a/gsoc/models.py b/gsoc/models.py index b66c70e7..0805c8a9 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -345,6 +345,7 @@ class Scheduler(models.Model): ('deactivate_user', 'deactivate_user'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), + ('update_site_template', 'update_site_template'), ) id = models.AutoField(primary_key=True) @@ -879,6 +880,17 @@ def event_add_to_calendar(sender, instance, **kwargs): instance.add_to_calendar() +# Publish the event to Github pages +@receiver(models.signals.post_save, sender=Event) +def event_publish_to_github_pages(sender, instance, **kwargs): + s = Scheduler.objects.filter(command='update_site_template', + data=json.dumps({'template': 'deadlines.html'}), + success=None).all() + if len(s) == 0: + Scheduler.objects.create(command='update_site_template', + data=json.dumps({'template': 'deadlines.html'})) + + # Delete Event from Calendar when obj is deleted @receiver(models.signals.pre_delete, sender=Event) def event_delete_from_calendar(sender, instance, **kwargs): @@ -900,6 +912,17 @@ def due_date_add_to_calendar(sender, instance, **kwargs): instance.add_to_calendar() +# Publish the duedate to Github pages +@receiver(models.signals.post_save, sender=BlogPostDueDate) +def duedate_publish_to_github_pages(sender, instance, **kwargs): + s = Scheduler.objects.filter(command='update_site_template', + data=json.dumps({'template': 'deadlines.html'}), + success=None).all() + if len(s) == 0: + Scheduler.objects.create(command='update_site_template', + data=json.dumps({'template': 'deadlines.html'})) + + # Delete BlogPostDueDate from Calendar when obj is deleted @receiver(models.signals.pre_delete, sender=BlogPostDueDate) def due_date_delete_from_calendar(sender, instance, **kwargs): diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html new file mode 100644 index 00000000..3d1d95b9 --- /dev/null +++ b/gsoc/templates/site/deadlines.html @@ -0,0 +1,104 @@ + + + + + + + + Python GSoC – Deadlines + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    +

    Dates and Deadlines

    +

    In general, Python will ask mentors to do things before the Google + deadline. This allows our admins + time to make sure that evaluations, etc. are complete and ready for Google when their deadline + comes. + (The whole organization gets penalized if anyone's late, so we make sure that doesn't happen + unfairly.) + Student deadlines are exactly as Google tells you, although getting things done earlier is never a + bad + idea!

    + +

    Mentor and Sub-Org admin deadlines

    +

    These are also listed on the calendar at the bottom of this page that you can subscribe to or add to your own device.

    +
      + {% for event in events %} + {% if event.start_date == event.end_date %} +
    • {{ event.start_date }} - {{ event.title }}
    • + {% else %} +
    • {{ event.start_date }} to {{ event.end_date }} - {{ event.title }}
    • + {% endif %} + {% endfor %} +
    + +

    Blogging schedule (Student Deadlines)

    +

    Every week, students are asked to post something about their project on their blogs. This helps the python community learn about the work students are doing and also helps the org admins make sure that students still on track to pass and don't need help. There are two types of things that students post: blog posts, which are longer descriptions of the work they're doing, and weekly check ins, which answer a few short questions as a sort of status report. These are due every Monday during the GSoC period, and the schedule is listed below as a list and as a calendar at the bottom of the page that you can export and add to your own calendar.

    +
      + {% for duedate in duedates %} +
    • {{ duedate.date }} - {{ duedate.title }}
    • + {% endfor %} +
    + + iCal Link +

    Please note Google's GSoC + dates + and deadlines.

    + +
    +
    +
    + + + + diff --git a/settings_local.py.template b/settings_local.py.template index 50750980..5ec3e941 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -74,3 +74,6 @@ GOOGLE_API_CLIENT_CONFIG = { # GITHUB SETTINGS STATIC_SITE_REPO = 'python-gsoc/python-gsoc.github.io' GITHUB_ACCESS_TOKEN = '' +GITHUB_FILE_PATH = { + 'deadlines.html': 'deadlines.html' +} From 6c334b43a4abde164e75fca0c5194ef0fccfc1d2 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 29 Jun 2019 22:27:58 +0530 Subject: [PATCH 0346/1137] Migrate db --- gsoc/migrations/0037_auto_20190629_1627.py | 18 ++++++++++++++++++ project.db | Bin 1507328 -> 1507328 bytes 2 files changed, 18 insertions(+) create mode 100644 gsoc/migrations/0037_auto_20190629_1627.py diff --git a/gsoc/migrations/0037_auto_20190629_1627.py b/gsoc/migrations/0037_auto_20190629_1627.py new file mode 100644 index 00000000..5ec2d23b --- /dev/null +++ b/gsoc/migrations/0037_auto_20190629_1627.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.9 on 2019-06-29 16:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0036_auto_20190628_0502'), + ] + + operations = [ + migrations.AlterField( + model_name='scheduler', + name='command', + field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template')], max_length=40), + ), + ] diff --git a/project.db b/project.db index 759859694f622ed62ca4dcc9d05d4f02635c9e0b..1eaa365a7aed83419ae63fc867744e190158c2f5 100644 GIT binary patch delta 366 zcmWm7J4?f06b9h$%caR@FKKL&p@IZlEGCkRrlun3&`AfU4$_in2dC0{MYnjtE>bKN z1O5OJheBx~Zf;W0sXssje}Zd4PlxA)GraE!f^ra)?`FgR}I6?d%Gvcb z)HMDUI;yYU`0|6VFbY8%N|jI)3w=y6m)RBy9~)8* a*TY#o^An!GrxG|9m1@e%;nO;up7;k}{ba=e delta 300 zcmXBOze>YU7zOZqlcu@1HUDgKK?MzTQH+Cj?Vv*j>Ee_@TDaKNVs&zi3OXs4Nb&|E z(xn86o4Y#Y0YvZ-TmyQ<4}NDn=ZoWh9QSXRoGBr63`Yb(2tS(y9Xg#!&$n)x5B!~P za+4phidDHIPg!2A-YK+~OxAPydJzU!VL(9v6>6YC9duy8p#e>p0|QL3zy=5Ap;d%| z`_fhy8d|3BxgG4i@j}~oe>~MDX{L`|pQec;hnBvBZ~yOn@w%IJ55*ddvgM7|SGGG6 zX7P0F_CD^nRr^wUsT4KANJ+S0Tl#>M^_li<4UEW$Q1)DuQVhAGwz0RO7STvNo)o@K LKdr=<{$Iy0OUzQ% From 0439ed8e31e5cd815ac8df31ff2babe2b8f0e5d0 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 30 Jun 2019 08:52:58 +0530 Subject: [PATCH 0347/1137] Auto generate index.html on timeline creation --- gsoc/common/utils/commands.py | 9 +- gsoc/models.py | 15 +- gsoc/templates/site/index.html | 568 +++++++++++++++++++++++++++++++++ 3 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 gsoc/templates/site/index.html diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index af761125..fffa387b 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -6,7 +6,8 @@ from .irc import send_message -from gsoc.models import Scheduler, RegLink, GsocYear, UserProfile, Event, BlogPostDueDate +from gsoc.models import (Scheduler, RegLink, GsocYear, UserProfile, Event, + BlogPostDueDate, SubOrgDetails) from .tools import send_mail, render_site_template, push_site_template @@ -116,12 +117,16 @@ def add_calendar_event(scheduler: Scheduler): def update_site_template(scheduler: Scheduler): try: template = json.loads(scheduler.data)['template'] + gsoc_year = GsocYear.objects.first() if template == 'deadlines.html': - gsoc_year = GsocYear.objects.first() context = { 'events': Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), 'duedates': BlogPostDueDate.objects.filter(timeline__gsoc_year=gsoc_year).all(), } + elif template == 'index.html': + context = { + 'suborgs': SubOrgDetails.objects.filter(gsoc_year=gsoc_year, accepted=True).all(), + } content = render_site_template(template, context) push_site_template(settings.GITHUB_FILE_PATH[template], content) except Exception as e: diff --git a/gsoc/models.py b/gsoc/models.py index 0805c8a9..0e102395 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -265,10 +265,19 @@ def accept(self): data=scheduler_data) RegLink.objects.create(user_role=1, - user_suborg=suborg, + user_suborg=self.suborg, user_gsoc_year=self.gsoc_year, email=self.suborg_admin_email) + s = Scheduler.objects.filter(command='update_site_template', + data=json.dumps({'template': 'index.html'}), + success=None).all() + if len(s) == 0: + time = timezone.now() + timezone.timedelta(minutes=5) + Scheduler.objects.create(command='update_site_template', + data=json.dumps({'template': 'index.html'}), + activation_date=time) + def send_review(self): self.accepted = False self.save() @@ -887,8 +896,10 @@ def event_publish_to_github_pages(sender, instance, **kwargs): data=json.dumps({'template': 'deadlines.html'}), success=None).all() if len(s) == 0: + time = timezone.now() + timezone.timedelta(minutes=5) Scheduler.objects.create(command='update_site_template', - data=json.dumps({'template': 'deadlines.html'})) + data=json.dumps({'template': 'deadlines.html'}), + activation_date=time) # Delete Event from Calendar when obj is deleted diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html new file mode 100644 index 00000000..5fe2a57a --- /dev/null +++ b/gsoc/templates/site/index.html @@ -0,0 +1,568 @@ + + + + + + + + Python GSoC – Splash + + + + + + + + + + + + + + + + + + + +
    + + +
    +
    +

    Python Summer of Code

    +

    + Students: get paid to work on open source projects! +

    +

    + Projects: find new contributors and mentor the next generation! +

    +
    +
    + +
    +
    +
    +

    What is it?

    +
    +
    +
    +

    + + Python +

    +

    + Python is a popular high-level programming language. It is a general-purpose language used + by + scientists, developers, and many others who want to work more quickly and integrate systems + more effectively. +

    +
    +
    +
    +

    + + Google Summer of Code +

    +

    + Google Summer of Code (GSoC) is a global program that offers post-secondary students an + opportunity to be paid for contributing to an open source project over a three month period. +

    +
    +
    +

    + The Python Software Foundation (PSF) is an organization devoted to advancing open source + technology related to the Python programming language. + Since 2005, the Python Software Foundation has participated in Google Summer of Code, serving + as an "umbrella organization" to a variety of Python-related projects, as well as sponsoring + projects related to the development of the Python language. Python provides mentors, Google + provides the program (and the money!), and students write code! +

    +
    +
    +
    +

    We're accepting a limited number of new sub-orgs through March 5th! Email + gsoc-admins(at)python.org if interested. +

    +
    +
    +
    +
    + + +
    +

    How do I get started?

    +
    +
    +

    + + Choose an organization. +

    +

    + There's hundreds of thousands of projects that use Python, and you + need to narrow + down the list before you can get help or do much that's useful. + See How + do I choose a project or sub-org? for ideas + on how to do that. +

    Any open source experience will help you prepare for GSoC, + so don't worry too much about what project you try first and don't be afraid + to change your mind! When we know which sub-orgs will be participating, + they'll be listed with the project ideas. +

    +
    +
    +
    +

    + + Set up your own development environment. +

    +

    + Document what you do so you can remember it later, and so you can + help others if they get stuck! And if you get stuck, don't be afraid to ask + for help. +

    +
    +
    +
    +
    +

    + + Start communicating with the developers. +

    +

    + Join the mailing list, IRC channel, or any other communication + channels the developers use. Listen, get to know the people involved, and ask + questions. +

      +
    • Read first to see if your question has already been answered. + We get a lot of repeat questions! +
    • +
    • Communicate in public (not in private). Most open source work is done in the open, + so + demonstrate that you can do that! +
    • +
    +

    +
    +
    +
    +

    + + Try to fix a bug. +

    +

    + Many projects have these tagged as "easy" "bite-size" or + "beginner-friendly" -- do a search to see what comes up. Competition for the easiest + ones can be fierce, so don't be afraid to try something harder if you think + you might know what to do. +

    +

    + Can't find a bug? Other ideas: find typos and fix them. Improve test coverage by + writing new tests. Improve documentation. Use a tool like Pylint or Bandit to see + if you can find new issues. +

    +
    +
    +
    +

    + + Find bugs and report them. +

    +

    + Hopefully you won't encounter too many, but it's always a good idea to get familiar with + your + project's bug reporting process. +

    +
    +
    +
    +
    +

    + + Help with documentation. +

    +

    + As a beginner in your project, you're going to see things that are confusing that more + experienced developers may not notice. Take advantage of your beginner mindset and make + sure to + document anything you think is missing! +

    +
    +
    +
    +

    + + Help others. +

    +

    + Most projects are looking for not just coders, but good community members who people like to + work with. Show your community skills by helping others and make a great impression come + selection time! +

    +
    +
    +
    + + +
    +
    +
    +

    How to apply

    +

    Short application checklist:

    +
      +
    1. Read the links and instructions given on this site -- All of it! we've + tried + to give you all + the information you need to be an awesome student applicant. +
    2. Choose a sub-org (check the list here). Applications + not + associated with a sub-org typically get rejected. +
    3. Talk with your prospective mentors about what they expect of student + applicants and get help from them to refine your project ideas. Listening to + your mentors' recommendations is very important at this stage! +
    4. +
    5. Prepare a patch for that sub-org. Usually we expect students to fix a bug + and + have made a pull + request (or equivalent). Your code doesn't have to be + accepted and merged, but it does have to be visible to the public and it does have to be + your + own work + (mentor help is ok, code you didn't write is not). +
    6. +
    7. + Write your application (with help from your mentors!) + The 2019 application template is available here. + All applications must go through Google's application system; we can't + accept + any application + unless it is submitted there. +
        +
      • Use a descriptive title and include your sub-org name in Google's system. Good + example: + "Mailman: + Improve + archive search" Bad example: "My gsoc project" +
      • Make it easy for your mentors to give you feedback. If you're using Google docs, + enable comments and submit a "draft" (we can't see the "final" versions until + applications close). + If you're using a format that doesn't accept comments, make sure your email is on + the + document and don't forget to check for + feedback! +
      • +
      +
    8. +
    9. Submit your application to Google before the deadline. We actually + recommend you submit a few days early in case you have internet problems or + the system is down. Google does not extend this deadline, so it's best to be + prepared early! You can edit your application up until the system + closes. +
    10. +
    +
    +

    + + Tip +

    +

    Communication is probably the most + important part of the application process. Talk to the mentors and other + developers, listen when they give you advice, + and demonstrate that you've understood by incorporating their feedback into + what you're proposing. We reject a lot of students who haven't listened to mentor + feedback. If your mentors tell you that a project idea won't work for them, you're + probably not going to get accepted unless you change it. +

    +
    +
    +

    + + What goes in an application? +

    + An ideal application will contain 5 things: +
      +
    1. A descriptive title including the name of the sub-org + you + want to work with + (if this is missing, your application may be rejected!) +
    2. +
    3. Information about you, including contact information.
    4. +
    5. Link to a code contribution you have made to your organization. + (Usually this is a link to a pull request.) +
    6. +
    7. Information about your proposed project. This should be fairly + detailed + and include + a timeline. +
    8. +
    9. Information about other commitments that might affect your ability to + work + during the GSoC period. + (exams, classes, holidays, other jobs, weddings, etc.) We can work around a lot of + things, + but + it helps + to know in advance. +
    10. +
    +
    +
    +
    +
    + + +
    +
    +

    Ideas

    +
    +
    +

    We're accepting a limited number of new sub-orgs through March 5th, so a few new names may + appear + here up until around March 7th. +

    +
    +
    + +
    + + + {% for suborg in suborgs %} +
    +
    + +
    +

    + {{ suborg.suborg.suborg_name }} +

    +
    +
    +
    {{ suborg.description }} +
    + {% if suborg.link %} +
    +

    Website

    +
    + {% endif %} + {% if suborg.mailing_list %} + + {% endif %} + {% if suborg.chat %} +
    +

    Contact

    +
    + {% endif %} + +
    +
    Status: Ideas ready
    +
    +
    + {% endfor %} + + +
    +
    +
    +

    Friends of the PSF

    +

    Here's some more interesting organizations that use Python!

    +
      +
    • OpenAstronomy - an umbrella + organisation that includes open source projects used by researchers and engineers around the + world to better understand the universe
    • +
    • GNU Mailman - + mailing list management software
    • +
    +
    +
    + + +
    +
    +
    +

    Getting in Touch

    +

    + Please note that Python has a Community + Code of Conduct and mentors and + students working with the PSF are asked to abide by it as members of the + Python community. +

    +
    +
    +
    +
    +

    + + Mailing Lists. +

    +

    Sign up to the gsoc-general(at)python.org + mailing list to get updates, reminders, and to discuss questions. Please join the list + before you send a message! +

    +

    The most common questions are answered here: +

    +

    +
    +
    +
    +

    + + IRC / Live chat +

    +

    + Our IRC channel is #python-gsoc + on + irc.freenode.net. (Don't know IRC? Learn more at + irchelp.org). +

    + +
    +
    +

    + + Specific sub-orgs +

    +

    To talk with people from a specific sub-org, check their ideas + page listing for their mailing lists, IRC, and other contact information. +

    +
    +
    +
    +

    + + Tips! +

    +
      +
    1. Read first. We've tried to answer the common questions on this site, and + we get asked things like "How do I get started?" and + "Where do I find easy bugs?" a lot. Check the + Frequently Asked Questions (FAQ) on the student page for + more! +
    2. +
    3. Be Patient! Our mentors typically have day jobs and can't always answer + right-away. If you can't hang out on IRC for an answer, send an email instead. +
    4. +
    5. Ask questions directly on IRC. You don't need to introduce + yourself or say hi first, just ask away! +
    6. +
    7. Communicate in public. That lets many mentors read your question so you + can usually get an answer faster. +
    8. +
    +

    For mentors: All the gsoc admins can be reached at + gsoc-admins(at)python(dot)org if you have questions about participating. + (Students should email gsoc-general(at)python.org with all of their + questions, unless they are of a sensitive personal nature.) +

    +
    +
    +

    + + Org admins +

    +

    The 2019 Python Software Foundation (PSF) org admin team:

    +
      +
    • Terri Oda (terri on IRC) - focus areas: figurehead, making final decisions, + website/documentation +
    • +
    • James Lopeman (meflin on IRC) - focus areas: IRC, ideas pages reviews, saying no
    • +
    • John Hawley (warthog9 on IRC) - focus areas: infrastructure, advice, emergency + mentoring/mentor + supervision. +
    • +
    • Matthew Lagoe (Botanic on IRC) - focus areas: student blogs, irc bot, marking sure things + happen on time +
    • +
    • Kushal Das (kushal on IRC) - focus areas: advice, time zone coverage
    • +
    +

    The org admins can be reached at gsoc-admins(at)python(dot)org (for mentors) + Students should almost always visit Getting Started first, and + email + gsoc-general(at)python.org only if you get stuck. +

    +

    We also have some "org admins emeritus" who may be able + to help you: +

    +
      +
    • Florian Fuchs (florianf on IRC)
    • +
    • Stephen Turnbull (yaseppochi on IRC)
    • +
    +
    +
    +
    + +
    +

    Other Stuff

    +
    +
    +
  • Found a typo? Want to improve this site? The + source code + is on GitHub and we welcome pull requests! +
  • +
  • Want to use some of the text of this site? It is now licensed + under CC-BY-4.0. +
  • +
  • This site was developed using purecss.io which is licensed + under + the BSD license +
  • + +
    +
    +
    + +
    + + + + From b1dc14db2a86659df00299afbcf463f5fd4c3b06 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 2 Jul 2019 10:22:23 +0530 Subject: [PATCH 0348/1137] Set update_site_template as custom handler --- gsoc/management/commands/runcron.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index b5f3ee6e..6d0f285c 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -116,8 +116,14 @@ def handle_process(self, scheduler): }) def process_items(self, options): + today = timezone.now() + # custom handlers - irc_schedulers = Scheduler.objects.filter(success=None, command='send_irc_msg') + irc_schedulers_1 = Scheduler.objects.filter(success=None, command='send_irc_msg', + activation_date=None) + irc_schedulers_2 = Scheduler.objects.filter(success=None, command='send_irc_msg', + activation_date__lte=today) + irc_schedulers = irc_schedulers_1 | irc_schedulers_2 if len(irc_schedulers) is 0: self.stdout.write(self.style.SUCCESS('No scheduled send_irc_msg tasks'), ending='\n') else: @@ -127,8 +133,21 @@ def process_items(self, options): self.stdout.write(self.style.SUCCESS('Sent {} irc message(s)' .format(len(irc_schedulers))), ending='\n') + template_schedulers_1 = Scheduler.objects.filter(success=None, + command='update_site_template', + activation_date=None).all() + template_schedulers_2 = Scheduler.objects.filter(success=None, + command='update_site_template', + activation_date__lte=today).all() + template_schedulers = template_schedulers_1 | template_schedulers_2 + if len(template_schedulers) is 0: + self.stdout.write(self.style.SUCCESS('No scheduled update_site_template tasks'), + ending='\n') + else: + for scheduler in template_schedulers: + self.handle_process(scheduler) + # generic handlers - today = timezone.now() x = Scheduler.objects.filter(success=None, activation_date=None).all() y = Scheduler.objects.filter(success=None, activation_date__lte=today).all() schedulers = x | y From 25124e029c43c5f7341ebd148df252bd5e9e4d92 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 2 Jul 2019 11:19:09 +0530 Subject: [PATCH 0349/1137] Fix suborg form validation condition --- gsoc/forms.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index 5e2284bc..43e5d00a 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,5 +1,5 @@ from .models import (UserDetails, UserProfile, RegLink, BlogPostDueDate, Event, - SubOrgDetails) + SubOrgDetails, SubOrg) from django import forms from django.core.exceptions import ValidationError @@ -84,9 +84,11 @@ def clean(self): raise ValidationError('Either suborg should be selected or ' 'the suborg name') - if not suborg and suborg_name == suborg.suborg_name: - raise ValidationError('Suborg with the entered name exists, ' - 'Please select from the list') + if not suborg: + _suborgs = SubOrg.objects.filter(suborg_name=suborg_name).all() + if len(_suborgs) > 0: + raise ValidationError('Suborg with the entered name exists, ' + 'Please select from the list') if suborg and suborg_name: raise ValidationError('Either suborg should be selected or ' From 8d17173feff3df10aa91f990ad6960c865fcea32 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 2 Jul 2019 09:03:39 +0530 Subject: [PATCH 0350/1137] Add scheduler to revoke student permissions related to articles --- gsoc/cms_toolbars.py | 7 +++++-- gsoc/cms_wizards.py | 8 +++++++- gsoc/common/utils/commands.py | 18 ++++++++++++------ gsoc/models.py | 2 +- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 50f9399d..2badc6bb 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -10,6 +10,7 @@ from cms.utils.conf import get_cms_setting from cms.utils.urlutils import admin_reverse +from django.contrib.auth.models import Permission from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language_from_request @@ -151,8 +152,10 @@ def populate(self): else: for profile in userprofiles: if profile.app_config == config: - change_article_perm = True - break + change_perm = Permission.objects.filter(codename='change_article').first() + if change_perm in user.user_permissions.all(): + change_article_perm = True + break add_article_perm = user.is_superuser if article else False delete_article_perm = user.is_superuser if article else False diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py index ceb2acce..bacb8478 100644 --- a/gsoc/cms_wizards.py +++ b/gsoc/cms_wizards.py @@ -2,6 +2,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import Permission from cms.api import add_plugin from cms.utils import permissions @@ -35,7 +36,12 @@ def user_has_add_permission(self, user, **kwargs): return False # Ensure user has permission to create articles. - if user.is_superuser or user.student_profile() is not None: + if user.student_profile() is not None: + add_perm = Permission.objects.filter(codename='add_article').first() + if add_perm in user.user_permissions.all(): + return True + + if user.is_superuser: return True # By default, no permission. diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index fffa387b..1452ea2e 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -1,7 +1,7 @@ import json from smtplib import SMTPResponseException, SMTPSenderRefused -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Permission from django.conf import settings from .irc import send_message @@ -50,14 +50,20 @@ def send_email(scheduler: Scheduler): return None -def deactivate_user(scheduler: Scheduler): +def revoke_student_permissions(scheduler: Scheduler): """ - makes a user inactive when scheduled + revoke article permissions from students when scheduled """ try: - u = User.objects.filter(pk=scheduler.data).first() - u.is_active = False - u.save() + u = User.objects.filter(pk=int(scheduler.data)).first() + + add_perm = Permission.objects.filter(codename='add_article').first() + change_perm = Permission.objects.filter(codename='change_article').first() + delete_perm = Permission.objects.filter(codename='delete_article').first() + view_perm = Permission.objects.filter(codename='view_article').first() + + u.user_permissions.remove(add_perm, change_perm, delete_perm, view_perm) + scheduler.success = True scheduler.save() return None diff --git a/gsoc/models.py b/gsoc/models.py index 0e102395..27924c0c 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -351,7 +351,7 @@ class Scheduler(models.Model): commands = ( ('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), - ('deactivate_user', 'deactivate_user'), + ('revoke_student_permissions', 'revoke_student_permissions'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template'), From fd021b591418f07f70075293a19f8472d22546c0 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 2 Jul 2019 09:03:59 +0530 Subject: [PATCH 0351/1137] Migrate db --- gsoc/migrations/0038_auto_20190702_0326.py | 18 ++++++++++++++++++ project.db | Bin 1507328 -> 1507328 bytes 2 files changed, 18 insertions(+) create mode 100644 gsoc/migrations/0038_auto_20190702_0326.py diff --git a/gsoc/migrations/0038_auto_20190702_0326.py b/gsoc/migrations/0038_auto_20190702_0326.py new file mode 100644 index 00000000..c0414857 --- /dev/null +++ b/gsoc/migrations/0038_auto_20190702_0326.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.9 on 2019-07-02 03:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0037_auto_20190629_1627'), + ] + + operations = [ + migrations.AlterField( + model_name='scheduler', + name='command', + field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('revoke_student_permissions', 'revoke_student_permissions'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template')], max_length=40), + ), + ] diff --git a/project.db b/project.db index 1eaa365a7aed83419ae63fc867744e190158c2f5..441b2b45d89327c472dcc50f62d097cb9f456546 100644 GIT binary patch delta 300 zcmZo@h-qkunIJ8=fq{Xs0EqJ$fCK{rXZu7QW5x{|6AZK&QzlQ;-pqKW*;S|ARfiFX znShuXh*^M`6^Pk@m>q~YfS41Axqz4(h-A*fLIWSg@9OiyQ_}Ker0B9 z&hF{#0V1YMoZXuR6MQ*str~q;8APoe(~I+y4GfGe;uA|t^5cyR3@r`J4UFOqjE&4d z99;u*T>~Qp17j;=V=Ge=J)p3GrQ!Ayks=n~mN_sBv?Yj6OAwP_;Rb@qf(hHUUrG>5 zSLU4lOv|d7&cu_SNQRC&s;HH7QO&RU7#~G8MlY!imfWzt{}kp MfPb<=f&$1$0H+vG?*IS* delta 224 zcmZo@h-qkunIJ8=j)8%(0EojFfCK{rXUjw#W5#tG6AZK&lP6Eq-pqKq*;S|ARfiFX znShuXh*^M`6^Pk@m>q~YfS41Axqz4(h-A*fLIWSg@9OiyQ_}Ker0AU z&J)ww14K+W3nuh%Za)zzV)1Ref&k+K{>cgn3T+9Z(-Ooan0PEE3nuK?eknmLk&}fl tfQc2Tnvsd6Nve-wI)Ap9CS(3|IbGq$(`9nRc(;e;imfVI=D;if!T{knKCl1) From f21a34783d833b7cb700691022c726410c9852a7 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 2 Jul 2019 09:53:52 +0530 Subject: [PATCH 0352/1137] Update local settings template --- settings_local.py.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/settings_local.py.template b/settings_local.py.template index 5ec3e941..3348326a 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -75,5 +75,6 @@ GOOGLE_API_CLIENT_CONFIG = { STATIC_SITE_REPO = 'python-gsoc/python-gsoc.github.io' GITHUB_ACCESS_TOKEN = '' GITHUB_FILE_PATH = { - 'deadlines.html': 'deadlines.html' + 'deadlines.html': 'deadlines.html', + 'index.html': 'index.html' } From 7eef7fdd205060eaab96527f817b060fee50275f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 3 Jul 2019 11:49:38 +0530 Subject: [PATCH 0353/1137] Add builders for revoking student blog permissions after gsoc ends --- gsoc/admin.py | 14 +++++++++++--- gsoc/common/utils/build_tasks.py | 11 +++++++++++ gsoc/forms.py | 8 +++++++- gsoc/models.py | 13 +++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 98cb79ff..7779cf88 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,6 +1,9 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, - BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails) -from .forms import UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm + BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, + GsocEndDate) +from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, + GsocEndDateForm) + from django.contrib.auth.models import User from django.contrib import admin @@ -383,10 +386,15 @@ class EventInline(admin.TabularInline): form = EventForm +class GsocEndDateInline(admin.TabularInline): + model = GsocEndDate + form = GsocEndDateForm + + class TimelineAdmin(admin.ModelAdmin): list_display = ('gsoc_year', ) exclude = ('calendar_id', ) - inlines = (BlogPostDueDateInline, EventInline) + inlines = (BlogPostDueDateInline, EventInline, GsocEndDateInline) admin.site.register(Timeline, TimelineAdmin) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 81047772..b01605fa 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -98,3 +98,14 @@ def build_post_blog_reminders(builder): return None except Exception as e: return str(e) + + +def build_revoke_student_perms(builder): + try: + gsoc_year = GsocYear.objects.first() + profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() + for profile in profiles: + Scheduler.objects.create(command='revoke_student_permissions', + data=profile.user.id) + except Exception as e: + return str(e) diff --git a/gsoc/forms.py b/gsoc/forms.py index 43e5d00a..ec702121 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,5 +1,5 @@ from .models import (UserDetails, UserProfile, RegLink, BlogPostDueDate, Event, - SubOrgDetails, SubOrg) + SubOrgDetails, SubOrg, GsocEndDate) from django import forms from django.core.exceptions import ValidationError @@ -51,6 +51,12 @@ class Meta: fields = ('title', 'start_date', 'end_date') +class GsocEndDateForm(forms.ModelForm): + class Meta: + model = GsocEndDate + fields = ('date', ) + + class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails diff --git a/gsoc/models.py b/gsoc/models.py index 27924c0c..c43230a3 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -373,6 +373,7 @@ class Builder(models.Model): categories = ( ('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), + ('build_revoke_student_perms', 'build_revoke_student_perms'), ) category = models.CharField(max_length=40, choices=categories) @@ -535,6 +536,11 @@ def create_builders(self): self.save() +class GsocEndDate(models.Model): + timeline = models.OneToOneField(Timeline, on_delete=models.CASCADE) + date = models.DateField() + + class PageNotification(models.Model): message = models.TextField(name='message') user = models.ForeignKey(User, name='user', @@ -923,6 +929,13 @@ def due_date_add_to_calendar(sender, instance, **kwargs): instance.add_to_calendar() +# Add new builder for GsocEndDate +@receiver(models.signals.post_save, sender=GsocEndDate) +def add_revoke_perms_builder(sender, instance, **kwargs): + Builder.objects.create(category='build_revoke_student_perms', + activation_date=instance.date) + + # Publish the duedate to Github pages @receiver(models.signals.post_save, sender=BlogPostDueDate) def duedate_publish_to_github_pages(sender, instance, **kwargs): From 9c4d266bda9e904c391b4c0f96ff6b17ef47d3f3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 3 Jul 2019 11:49:55 +0530 Subject: [PATCH 0354/1137] Migrate db --- gsoc/migrations/0039_gsocenddate.py | 22 +++++++++++++++++++++ gsoc/migrations/0040_auto_20190703_0617.py | 18 +++++++++++++++++ project.db | Bin 1507328 -> 1515520 bytes 3 files changed, 40 insertions(+) create mode 100644 gsoc/migrations/0039_gsocenddate.py create mode 100644 gsoc/migrations/0040_auto_20190703_0617.py diff --git a/gsoc/migrations/0039_gsocenddate.py b/gsoc/migrations/0039_gsocenddate.py new file mode 100644 index 00000000..f918b260 --- /dev/null +++ b/gsoc/migrations/0039_gsocenddate.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.9 on 2019-07-03 05:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0038_auto_20190702_0326'), + ] + + operations = [ + migrations.CreateModel( + name='GsocEndDate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('timeline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='gsoc.Timeline')), + ], + ), + ] diff --git a/gsoc/migrations/0040_auto_20190703_0617.py b/gsoc/migrations/0040_auto_20190703_0617.py new file mode 100644 index 00000000..eb4da56a --- /dev/null +++ b/gsoc/migrations/0040_auto_20190703_0617.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.9 on 2019-07-03 06:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0039_gsocenddate'), + ] + + operations = [ + migrations.AlterField( + model_name='builder', + name='category', + field=models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms')], max_length=40), + ), + ] diff --git a/project.db b/project.db index 441b2b45d89327c472dcc50f62d097cb9f456546..78752313634b8e404a2f8dfd64d0e9a640cd0712 100644 GIT binary patch delta 1120 zcmZvbUr19?9LMju|F6^CEqAX*YL_g?^yZygTQdSf40KC{RJfvS(=F57mNx%Dfo?*7 z2qYPLKg;q;LV1XY-pZIZrw|6 zA%FxZfCd@_9E->Nb(-CyxiyDhbJ^X-K-Ju;=1??Oz1>sqJb(6_=JeR@j>HuZ ztN+9fkAL409j0=+opnxkeZu#D%M8|SgLgAl3)&G=O2E3>K+(!m8YypRw0rzn17%qa zNVJ6-V3#p>Q~JQOKiN(8ExQcee_{GdXrz+*_+WTq|JKNNlty+^4~2)r@o-MYo6YoJ z4@L)aGc7WWs!2T<3grlzGt7jv_oK{2Y!HIX*KxhX2D`%i5_?Ui!mfgkMcqZ+DQn<$ zW+Nbn1T+u-b6mqL#g<%E@rYF@J!zcD{w4kP{24ToG2M_W5M9(osIWj)UE*g75QTn!#htbC2aP3;-HCj;hiB##`mXeGTu%xJZ}BW6yq0DHiuAU?jY#I z_$-aPBl0OUil-xT8`g#pjekeva#P>f;Bfqe(ie*jV<9TL@Onh9V5;Cn32^ER%JjzM z=j|5Oyh(_k#07CsbcvLC6LM?jshT91i&W16QyPO&#V|@SdRL%P#dt_s(hKGGi-&k+ w2(k|5OIos$gC;fx8>32gI#+Wh*$kR$cfP&G9^iOxab{s%9O~O>EomVhJr+-e3SN}-^}+WT9`8MPQ#hL}6ed46M}36wBFG^~ z26uxm658KmsyQfC>oE zfDY<_0qQ{*L;wXQh=K;;R-d?esafCUBo*t2^)Gd?`zE%sdFa<$$A9F+iHsjw4+kW( zbk?8<#fjIbG(!|l7UP%D-meN}EnfTGsG|zfnbt=#sG^pYzcdpbHz2&Ib1Ethd#B@T zc>fZ{JZr&nB!#i=^AGu0k2W*4{C+mK zw9a!EktY5#%i1_uU_JaU%S_xUtb!TFB9miZQ(B0Y5WS)$SiM6<={uV Date: Wed, 3 Jul 2019 11:52:25 +0530 Subject: [PATCH 0355/1137] Fix pep8 warnings --- gsoc/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 7779cf88..784b6265 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -3,7 +3,7 @@ GsocEndDate) from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, GsocEndDateForm) - + from django.contrib.auth.models import User from django.contrib import admin From 0ae39c2439a013e35bdb9d01940749cca8871195 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 3 Jul 2019 13:03:28 +0530 Subject: [PATCH 0356/1137] Send notification to admin when suborg application added/updated --- gsoc/models.py | 25 ++++++++++++++++++- .../suborg_application_notification.html | 1 + suborg/views.py | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 gsoc/templates/email/suborg_application_notification.html diff --git a/gsoc/models.py b/gsoc/models.py index c43230a3..df049076 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -278,13 +278,36 @@ def accept(self): data=json.dumps({'template': 'index.html'}), activation_date=time) + + def send_update_notification(self): + if self.suborg: + suborg_name = self.suborg.suborg_name + else: + suborg_name = self.suborg_name + + template_data = { + 'suborg_name': suborg_name + } + scheduler_data = build_send_mail_json(settings.ADMIN_EMAIL, + template='suborg_application_notification.html', + subject='Review new/updated SubOrg Application', + template_data=template_data) + Scheduler.objects.create(command='send_email', + data=scheduler_data) + + def send_review(self): self.accepted = False self.save() + if self.suborg: + suborg_name = self.suborg.suborg_name + else: + suborg_name = self.suborg_name + template_data = { 'gsoc_year': self.gsoc_year.gsoc_year, - 'suborg_name': self.suborg_name, + 'suborg_name': suborg_name, 'message': self.last_message, } scheduler_data = build_send_mail_json(self.suborg_admin_email, diff --git a/gsoc/templates/email/suborg_application_notification.html b/gsoc/templates/email/suborg_application_notification.html new file mode 100644 index 00000000..03f2d23a --- /dev/null +++ b/gsoc/templates/email/suborg_application_notification.html @@ -0,0 +1 @@ +{{ suborg_name }} \ No newline at end of file diff --git a/suborg/views.py b/suborg/views.py index 2b766c0a..d4c07378 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -47,6 +47,7 @@ def register_suborg(request): suborg_details = form.save() suborg_details.changed = True suborg_details.save() + suborg_details.send_update_notification() return redirect(reverse('suborg:post_register')) return render(request, 'register_suborg.html', { @@ -73,6 +74,7 @@ def update_application(request, application_id): suborg_details = form.save() suborg_details.changed = True suborg_details.save() + suborg_details.send_update_notification() return redirect(reverse('suborg:post_register')) return render(request, 'register_suborg.html', { From 140e20420829a015666ab9bf735db6551da3729c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 3 Jul 2019 13:06:38 +0530 Subject: [PATCH 0357/1137] Fix pep8 warnings --- gsoc/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index df049076..ea9a5607 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -278,7 +278,6 @@ def accept(self): data=json.dumps({'template': 'index.html'}), activation_date=time) - def send_update_notification(self): if self.suborg: suborg_name = self.suborg.suborg_name From ab1ac4134f668767bde87f6a2cf056123d9dafe1 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 3 Jul 2019 01:42:30 -0600 Subject: [PATCH 0358/1137] Update suborg_application_notification.html --- gsoc/templates/email/suborg_application_notification.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/suborg_application_notification.html b/gsoc/templates/email/suborg_application_notification.html index 03f2d23a..578d21ed 100644 --- a/gsoc/templates/email/suborg_application_notification.html +++ b/gsoc/templates/email/suborg_application_notification.html @@ -1 +1 @@ -{{ suborg_name }} \ No newline at end of file +{{ suborg_name }} has submitted a application. From 2c6f97990136ebd2f1b2945b3511084558d91501 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 01:50:44 -0600 Subject: [PATCH 0359/1137] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c768c0c0..5e418c71 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django>=2.1.7,<2.2 +django~>2.2.3 django-debug-toolbar>=1.11 django-cms>=3.6,<4 @@ -29,4 +29,4 @@ google-api-python-client google-auth-httplib2 google-auth-oauthlib -PyGithub \ No newline at end of file +PyGithub From 58b74b210ca32fb7124cc65476b77c2d4e0b3446 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 01:53:37 -0600 Subject: [PATCH 0360/1137] Update post_blog_reminder_mentors.html --- gsoc/templates/email/post_blog_reminder_mentors.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/post_blog_reminder_mentors.html b/gsoc/templates/email/post_blog_reminder_mentors.html index ba526109..7bc39b59 100644 --- a/gsoc/templates/email/post_blog_reminder_mentors.html +++ b/gsoc/templates/email/post_blog_reminder_mentors.html @@ -2,7 +2,7 @@
    It looks like they have {{ current_blog_count }} blog post(s) pending. Make sure to get your student to post as soon as possible.

    -Blogging is required for Python Software Foundation students, since that's how the org admin teams ensure that students are still active. Inactive students will be given failing grades.
    +Blogging is required for Python Software Foundation students, since that's how the org admin teams ensure that students are still active. Inactive students will be given failing grades.

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From fa0b6471b37456b7f6052f0d4165521626903011 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 01:54:25 -0600 Subject: [PATCH 0361/1137] Update second_post_blog_reminder_student.html --- gsoc/templates/email/second_post_blog_reminder_student.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/second_post_blog_reminder_student.html b/gsoc/templates/email/second_post_blog_reminder_student.html index bb571026..1f4c249d 100644 --- a/gsoc/templates/email/second_post_blog_reminder_student.html +++ b/gsoc/templates/email/second_post_blog_reminder_student.html @@ -2,7 +2,7 @@
    It looks like you have {{ current_blog_count }} blog post(s) pending. Make sure to get your posts in as soon as possible.

    -Blogging is required for Python Software Foundation students, since that's how the org admin teams ensure that students are still active. Inactive students will be given failing grades.
    +Blogging is required for Python Software Foundation students, since that's how the org admin teams ensure that students are still active. Inactive students will be given failing grades.

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From 5defc6d7a0bab0a05975efc38679b487ed981759 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 01:54:43 -0600 Subject: [PATCH 0362/1137] Update first_post_blog_reminder_student.html --- gsoc/templates/email/first_post_blog_reminder_student.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/first_post_blog_reminder_student.html b/gsoc/templates/email/first_post_blog_reminder_student.html index 4e9c2f41..e60b80f5 100644 --- a/gsoc/templates/email/first_post_blog_reminder_student.html +++ b/gsoc/templates/email/first_post_blog_reminder_student.html @@ -2,7 +2,7 @@
    It looks like you have {{ current_blog_count }} blog post(s) pending. Make sure to get your posts in as soon as possible.

    -Blogging is required for Python Software Foundation students, since that's how the org admin teams ensure that students are still active.
    +Blogging is required for Python Software Foundation students, since that's how the org admin teams ensure that students are still active.

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From 3a5cc913b52fff584e181f070b9d3388c1542806 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 20:11:28 -0600 Subject: [PATCH 0363/1137] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5e418c71..b85cb212 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django~>2.2.3 +django~=2.1 django-debug-toolbar>=1.11 django-cms>=3.6,<4 From 5d2384a58aeee247cc2285346b468ae9f020c528 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 20:13:29 -0600 Subject: [PATCH 0364/1137] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b85cb212..02fc955f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django~=2.1 +django>=2.1.10,<2.2 django-debug-toolbar>=1.11 django-cms>=3.6,<4 From d9ff81beaaf139e7c47309c95eec35c70df86165 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 20:46:11 -0600 Subject: [PATCH 0365/1137] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 02fc955f..978befa3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -django>=2.1.10,<2.2 +django~=2.1.10 django-debug-toolbar>=1.11 -django-cms>=3.6,<4 +django-cms~=3.6.0 djangocms-text-ckeditor>=3.7 djangocms-file>=2.2 djangocms-column>=1.9 From 07bd6d7c9617f8508b123c6917fc035ba03412e2 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 20:58:12 -0600 Subject: [PATCH 0366/1137] Update requirements.txt --- requirements.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 978befa3..bd52979d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,8 +25,9 @@ phonenumbers==8.10.6 pre-commit>=1.14.4 profanityfilter>=2.0.6 -google-api-python-client -google-auth-httplib2 -google-auth-oauthlib +google-api-python-client>=1.7.9 +google-auth-httplib2>=0.0.3 +google-auth-oauthlib>=0.3.0 + +PyGithub>=1.43.7 -PyGithub From e55f0bbab5bbb195342c6e0bdec605c1d9b1483d Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 21:04:02 -0600 Subject: [PATCH 0367/1137] Update requirements.txt --- requirements.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index bd52979d..25711e18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,18 +2,18 @@ django~=2.1.10 django-debug-toolbar>=1.11 django-cms~=3.6.0 -djangocms-text-ckeditor>=3.7 -djangocms-file>=2.2 -djangocms-column>=1.9 +djangocms-text-ckeditor>=3.7.0 +djangocms-file>=2.2.0 +djangocms-column>=1.9.0 djangocms-link>=2.3.1 djangocms-picture>=2.1.3 -djangocms-style>=2.1 -djangocms-snippet>=2.1 -djangocms-googlemap>=1.2 +djangocms-style>=2.1.0 +djangocms-snippet>=2.1.0 +djangocms-googlemap>=1.2.0 djangocms-video>=2.1.1 djangocms-audio>=1.1.0 -djangocms_history>=1 +djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 From d144e4eda715d449e940ef1858990a6752747618 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 21:05:57 -0600 Subject: [PATCH 0368/1137] Update requirements.txt --- requirements.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 25711e18..acb9302a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,10 @@ +#django django~=2.1.10 + +#django debug django-debug-toolbar>=1.11 +#django cms django-cms~=3.6.0 djangocms-text-ckeditor>=3.7.0 djangocms-file>=2.2.0 @@ -12,22 +16,22 @@ djangocms-snippet>=2.1.0 djangocms-googlemap>=1.2.0 djangocms-video>=2.1.1 djangocms-audio>=1.1.0 - djangocms_history>=1.0.0 - aldryn-newsblog>=2.2.1 +#gsoc pdf requirements fredirc==0.3.0 - pdfminer.six==20181108 chardet==3.0.4 phonenumbers==8.10.6 pre-commit>=1.14.4 profanityfilter>=2.0.6 +#google api google-api-python-client>=1.7.9 google-auth-httplib2>=0.0.3 google-auth-oauthlib>=0.3.0 +#github api PyGithub>=1.43.7 From 3a70f00ef29b4bceaaff489b84a467a3c6eb3f3b Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 4 Jul 2019 21:11:08 -0600 Subject: [PATCH 0369/1137] Update requirements.txt --- requirements.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index acb9302a..b94ff60e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,19 +19,25 @@ djangocms-audio>=1.1.0 djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 -#gsoc pdf requirements +#gsoc irc requirements fredirc==0.3.0 + +#gsoc pdf requirements pdfminer.six==20181108 chardet==3.0.4 phonenumbers==8.10.6 -pre-commit>=1.14.4 + +#gsoc comments requirements profanityfilter>=2.0.6 -#google api +#git pre-commit hook +pre-commit>=1.14.4 + +#google api for calander google-api-python-client>=1.7.9 google-auth-httplib2>=0.0.3 google-auth-oauthlib>=0.3.0 -#github api +#github api for pushing html pages PyGithub>=1.43.7 From 700b7e73700063ba19208f703b935f92d0e02761 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 5 Jul 2019 22:38:06 -0600 Subject: [PATCH 0370/1137] Update invite.html --- gsoc/templates/email/invite.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index de0de828..ae36af5a 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,5 +1,10 @@ Welcome to GSoC with the Python Software Foundation!

    +{% if role is not 3 %} +You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% endif %} with {suborg} for Google Summer of Code {% now 'Y' %} with the Python Software Foundation.
    +
    +You will need to register using the link below to be invited by Google.
    +{% else %} All students are required to post weekly, there are 2 types of posts students will have to make, the first is the weekly check-in. For a weekly check-in every student will have to answer these 3 questions in a post; with each answer being <100 words.

     1. What did you do this week?
    @@ -8,7 +13,8 @@
    The second post is a blog post, here a student will be required to go into some detail on what they are working on, what they struggle with, and what solutions they have come to. There is no formal structure to this and every student is welcome to use their own style but the above three questions should be answered in the blog post at some point.

    -This year we are introducing our new GSoC blogging platform, this platform will be worked on during GSoC so please report any bugs, issues, or suggestions to https://github.com/python-gsoc/python-blogs/issues or email gsoc-admins@python.org The site will be updated every Tuesday/Wednesday so if you don't get your blogs in on time you may encounter bugs and otherwise, so make sure you get your posts in on time!
    +{% endif %} +Blogs and suborgs are handled by our new GSoC blogging platform, so please report any bugs, issues, or suggestions to https://github.com/python-gsoc/python-blogs/issues or email gsoc-admins@python.org

    To register you will need to go to {{ register_link }}

    From 694118784ba6f4ea04a0a5e9ab110295d3971809 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 5 Jul 2019 22:39:12 -0600 Subject: [PATCH 0371/1137] Update invite.html --- gsoc/templates/email/invite.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index ae36af5a..5e603a3d 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,7 +1,7 @@ Welcome to GSoC with the Python Software Foundation!

    {% if role is not 3 %} -You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% endif %} with {suborg} for Google Summer of Code {% now 'Y' %} with the Python Software Foundation.
    +You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% endif %} with {suborg} for Google Summer of Code {gsoc_year} with the Python Software Foundation.

    You will need to register using the link below to be invited by Google.
    {% else %} From cac031194d5edab3761b2ec8af2719f5fb67d261 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 5 Jul 2019 22:40:05 -0600 Subject: [PATCH 0372/1137] Update invite.html --- gsoc/templates/email/invite.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index 5e603a3d..d8563e41 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,7 +1,7 @@ Welcome to GSoC with the Python Software Foundation!

    {% if role is not 3 %} -You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% endif %} with {suborg} for Google Summer of Code {gsoc_year} with the Python Software Foundation.
    +You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% else %}Other{% endif %} with {suborg} for Google Summer of Code {gsoc_year} with the Python Software Foundation.

    You will need to register using the link below to be invited by Google.
    {% else %} From 505be6d2975266f248a83da3fb716bca8c52189a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 4 Jul 2019 23:55:19 +0530 Subject: [PATCH 0373/1137] Add rerun builder/scheduler actions in respective admin pages --- gsoc/admin.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/gsoc/admin.py b/gsoc/admin.py index 784b6265..6d85961d 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -279,10 +279,20 @@ def get_readonly_fields(self, request, obj=None): admin.site.register(RegLink, RegLinkAdmin) +def rerun_scheduler(self, request, queryset): + for scheduler in queryset: + Scheduler.objects.create(command=scheduler.command, + data=scheduler.data) + + +rerun_scheduler.short_description = 'Rerun schedulers' + + class SchedulerAdmin(admin.ModelAdmin): list_display = ('command', 'short_data', 'success', 'last_error', 'created') list_filter = ('command', 'success') sortable_by = ('created', 'last_error') + actions = [rerun_scheduler] def short_data(self, obj): return '{}...'.format(obj.data[:50]) @@ -364,10 +374,20 @@ def used_stat(self, obj): admin.site.register(AddUserLog, AddUserLogAdmin) +def rerun_builder(self, request, queryset): + for builder in queryset: + Builder.objects.create(category=builder.category, + data=builder.data) + + +rerun_builder.short_description = 'Rerun builders' + + class BuilderAdmin(admin.ModelAdmin): list_display = ('category', 'short_data', 'built', 'last_error') list_filter = ('category', 'built') sortable_by = ('last_error') + actions = [rerun_builder] def short_data(self, obj): return '{}...'.format(obj.data[:50]) From adb287c5a0adb909571a7860dd1dbf83b9da9d72 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 5 Jul 2019 00:15:46 +0530 Subject: [PATCH 0374/1137] Fix updating suborg application --- gsoc/forms.py | 21 +++++++------- suborg/templates/update_suborg.html | 44 +++++++++++++++++++++++++++++ suborg/views.py | 5 ++-- 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 suborg/templates/update_suborg.html diff --git a/gsoc/forms.py b/gsoc/forms.py index ec702121..e215a81a 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -86,20 +86,19 @@ def clean(self): contact = list(filter(lambda a: a is not None, contact)) - if not (suborg or suborg_name): + if not suborg and suborg_name: + suborg = SubOrg.objects.filter(suborg_name=suborg_name) + if len(suborg) > 0: + cd['suborg'] = suborg.first() + elif suborg and not suborg_name: + cd['suborg_name'] = suborg.suborg_name + elif suborg and suborg_name: + if suborg.suborg_name != suborg_name: + raise ValidationError('Inconsistent suborg field values') + else: raise ValidationError('Either suborg should be selected or ' 'the suborg name') - if not suborg: - _suborgs = SubOrg.objects.filter(suborg_name=suborg_name).all() - if len(_suborgs) > 0: - raise ValidationError('Suborg with the entered name exists, ' - 'Please select from the list') - - if suborg and suborg_name: - raise ValidationError('Either suborg should be selected or ' - 'the suborg name, not both') - if len(contact) < 1: raise ValidationError('At least one out of the five contact ' 'details should be entered') diff --git a/suborg/templates/update_suborg.html b/suborg/templates/update_suborg.html new file mode 100644 index 00000000..d27035cf --- /dev/null +++ b/suborg/templates/update_suborg.html @@ -0,0 +1,44 @@ +{% extends CMS_TEMPLATE %} + +{% block head %} + +{% endblock %} + +{% block content %} +
    + {% if message %} +
    + Please review your application. {{ message }} +
    + {% endif %} +

    Apply for participating in GSoC@PSF as a SubOrg!

    + + {% csrf_token %} + {{ form }} + + +
    +{% endblock content %} + +{% block js %} + +{% endblock %} \ No newline at end of file diff --git a/suborg/views.py b/suborg/views.py index d4c07378..fce00978 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -77,9 +77,10 @@ def update_application(request, application_id): suborg_details.send_update_notification() return redirect(reverse('suborg:post_register')) - return render(request, 'register_suborg.html', { + return render(request, 'update_suborg.html', { 'form': form, - 'message': message + 'message': message, + 'id': application_id, }) From 889669abd838cef7de8fd26294600041aa9c65d4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 6 Jul 2019 09:15:05 +0530 Subject: [PATCH 0375/1137] Automatic register for existing users --- gsoc/views.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gsoc/views.py b/gsoc/views.py index 54521dbf..ff1f7d9c 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -11,7 +11,7 @@ import uuid from django.contrib import messages -from django.contrib.auth import decorators, password_validation, validators +from django.contrib.auth import decorators, password_validation, validators, logout from django.contrib.auth.models import User from django import shortcuts from django.http import JsonResponse @@ -145,6 +145,10 @@ def new_account_view(request): def register_view(request): + if request.user.is_authenticated: + messages.info(request, "You have been logged out.") + logout(request) + reglink_id = request.GET.get('reglink_id', request.POST.get('reglink_id', '')) try: reglink = RegLink.objects.get(reglink_id=reglink_id) @@ -160,6 +164,15 @@ def register_view(request): 'email': getattr(reglink, 'email', 'EMPTY') } if reglink_usable is False or request.method == 'GET': + user = User.objects.filter(email=context['email']).first() + if user: + reglink.create_user(username=user.username) + reglink.is_used = True + reglink.save() + messages.success(request, f'A user with {user.email} already exists in our database. '\ + f'A new profile has been created. Please login with your '\ + f'existing credentials.') + return shortcuts.redirect('/') if reglink_usable is False: context['can_register'] = False context['warning'] = 'Your registration link is invalid! Please check again!' From 6427a37ec7141a0f5af532da25f3b4283d6542a4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 6 Jul 2019 09:16:27 +0530 Subject: [PATCH 0376/1137] Fix pep8 warnings --- gsoc/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index ff1f7d9c..b2fbf981 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -169,8 +169,8 @@ def register_view(request): reglink.create_user(username=user.username) reglink.is_used = True reglink.save() - messages.success(request, f'A user with {user.email} already exists in our database. '\ - f'A new profile has been created. Please login with your '\ + messages.success(request, f'A user with {user.email} already exists in our database. ' + f'A new profile has been created. Please login with your ' f'existing credentials.') return shortcuts.redirect('/') if reglink_usable is False: From eef388e3f2407d728e3c3752b406d12090f4b631 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 6 Jul 2019 10:01:08 +0530 Subject: [PATCH 0377/1137] Change registration invite subject and content --- gsoc/models.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index ea9a5607..3b73f6b3 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -755,13 +755,23 @@ def create_user(self, *args, is_staff=True, **kwargs): def create_scheduler(self, trigger_time=timezone.now()): validate_email(self.email) + role = { + 0: 'Others', + 1: 'Suborg Admin', + 2: 'Mentor', + 3: 'Student', + } scheduler_data = build_send_mail_json(self.email, template='invite.html', - subject='Your GSoC 2019 invite', + subject=(f'You have been invited to join ' + f'{self.user_suborg.suborg_name.strip()}' + f' as a {role[self.user_role]} for GSoC ' + f'{self.user_gsoc_year.gsoc_year} with PSF'), template_data={ 'register_link': settings.INETLOCATION + - self.url}) + self.url, + 'role': self.user_role}) s = Scheduler.objects.create(command='send_email', activation_date=trigger_time, data=scheduler_data) From 6aacff861b1644434bf4b01874c912e1016bee87 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 6 Jul 2019 10:10:51 +0530 Subject: [PATCH 0378/1137] Add suborg name and gsoc year in invite template --- gsoc/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 3b73f6b3..e82783fb 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -771,7 +771,9 @@ def create_scheduler(self, trigger_time=timezone.now()): 'register_link': settings.INETLOCATION + self.url, - 'role': self.user_role}) + 'role': self.user_role, + 'gsoc_year': self.user_gsoc_role, + 'suborg':self.user_suborg.suborg_name.strip()}) s = Scheduler.objects.create(command='send_email', activation_date=trigger_time, data=scheduler_data) From d796742c8b60d6f2381a7ccb1936728fcc088afc Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 10:28:23 +0530 Subject: [PATCH 0379/1137] Rename suborg details field names --- gsoc/admin.py | 8 ++++---- gsoc/forms.py | 2 +- gsoc/models.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 6d85961d..28ba3d10 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -472,7 +472,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', - 'blog_url', 'link', 'accepted', 'changed', 'last_updated_at', 'last_updated_by', + 'blog_url', 'link', 'accepted', 'changed', 'last_reviewed_at', 'last_reviewed_by', ) fieldsets = ( ( @@ -491,7 +491,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', 'blog_url', 'link', 'changed', 'accepted', - 'last_updated_at', 'last_updated_by' + 'last_reviewed_at', 'last_reviewed_by' ) } ), @@ -501,8 +501,8 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.changed = False - obj.last_updated_at = timezone.now() - obj.last_updated_by = request.user + obj.last_reviewed_at = timezone.now() + obj.last_reviewed_by = request.user obj.send_review() super(SubOrgDetailsAdmin, self).save_model(request, obj, form, change) diff --git a/gsoc/forms.py b/gsoc/forms.py index e215a81a..566f784f 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -60,7 +60,7 @@ class Meta: class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails - exclude = ('accepted', 'last_message', 'changed', 'last_updated_at', 'last_updated_by') + exclude = ('accepted', 'last_message', 'changed', 'last_reviewed_at', 'last_reviewed_by') widgets = { 'suborg_admin_email': forms.HiddenInput(), 'gsoc_year': forms.HiddenInput(), diff --git a/gsoc/models.py b/gsoc/models.py index e82783fb..600f8168 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -237,8 +237,8 @@ class SubOrgDetails(models.Model): link = models.URLField(null=True, blank=True, verbose_name='Any other link') last_message = models.TextField(null=True, blank=True) - last_updated_at = models.DateTimeField(null=True, blank=True) - last_updated_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) + last_reviewed_at = models.DateTimeField(null=True, blank=True) + last_reviewed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) accepted = models.BooleanField(default=False) changed = models.BooleanField(default=None, null=True) From 1054e16189fa964a57eba66bb111a175a77cd58a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 10:28:30 +0530 Subject: [PATCH 0380/1137] Migrate db --- gsoc/migrations/0041_auto_20190707_0432.py | 23 +++++++++++++++++++++ project.db | Bin 1515520 -> 1515520 bytes 2 files changed, 23 insertions(+) create mode 100644 gsoc/migrations/0041_auto_20190707_0432.py diff --git a/gsoc/migrations/0041_auto_20190707_0432.py b/gsoc/migrations/0041_auto_20190707_0432.py new file mode 100644 index 00000000..f06e4a75 --- /dev/null +++ b/gsoc/migrations/0041_auto_20190707_0432.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.9 on 2019-07-07 04:32 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0040_auto_20190703_0617'), + ] + + operations = [ + migrations.RenameField( + model_name='suborgdetails', + old_name='last_updated_at', + new_name='last_reviewed_at', + ), + migrations.RenameField( + model_name='suborgdetails', + old_name='last_updated_by', + new_name='last_reviewed_by', + ), + ] diff --git a/project.db b/project.db index 78752313634b8e404a2f8dfd64d0e9a640cd0712..b4883f199be57463263191f6c4648d4d92c005ed 100644 GIT binary patch delta 787 zcmZ9~OK1~890u^2&2Dx#n`RR`nFQ=3M#U;=-EGs;~;#>u#tO-v>24xp!7Ej7}6BKDwm zD3O(fTQB(%8L6Yqj0(Wr-j$ii_G?;Dk0&Q{*?6O-w`wg~OI!;!HEta0wHCH~TClCT zrL9?S2(<=7%^~VNg}&k)?4M513DjZ73x}g3qdc)4lYNq4y=pxm%=3pVtK4(00dy9- zFm;}FuS5D^4ApS-*+tfcF3cba(l-<6I!AlwP$Q(q6haoo+-=u?&98(bGxyefy(xZf@Nh0}_Y;$q)^( z*18=;gc&vFxCJ8Yc2K#j6lBq(>)hgA`XPb4IkJ+%)#T?*CnnK?pwMtXz5}brQ&WSf z^QnP&GUxXVBy*|U;AqMhIdS-Kz~?7dcU7JszBDJ0Z#mS*%Xv^$?D8sRUBM=LVmfCY zAs-F5m(*min|w{?L1@@V470M=+$yFs^|- z074a;Ew7-M9R<7S8D>uolHVIeg;}IZT2oV~b&nn*dK$O=7ny0i1^ugU%)|!={5#Fs pfCY_<(1N?lI)U0qtniNA;-;3fb7 delta 797 zcmZ|MUq}=|7y$5@-C1|{cJH6JJ06sJDWO-VWM>E|A(kkl8KFX=-g>$IA?d_aQj_iq z5~?%h(($2!0_`o(E9WsE3Ze%ip&$zEwU;#NAxfh!f&!s|-!Odh%{TwzE3NUB)~Q|I zWrXkmXh6^*q+Xv6h4J#D<#Nm25UEX8q=($HS5c$dq_U-KrM=b#CAC%9gRQh3To>@_ zmFmz$bqE6ih=2eIWB?f`Km`t<0UbDj3%G#?ctIxc@#>KABwx%{{Y*Y-l*4{+6z)H5 ze53{HFmmHZNPnh%)yf^Wc6CsJN3Leybg1Z#ts7Y`UY z0-qW-rUl+OYWPH|&mjue-O>2OkfC7SJ#G+zxRR&{>IB)@OU4Wjj2K}|AioS^VSaqj zsK5(x!{S~K-f*V^mUp>QB!PK?1CI&b%3Y`wJ+mvBQZ{Z!E|2rFzO4^9+SRMdJ$WYg={J9FLu-(njDq0N=k%OIjiq6vv|Kniv{*=&f{YZlaj{YN9jW> z4)u4k&q1Hh)l^p(3gq>5oF9z*j;)Uolf6v4tJuwbz9x3}jwd@GQi6^!GIl%@N*qpJ zl(TwEgoMpjJ54|H#Hd7}O2?qx8HtWWdOIVB%F67nuF@!5w5gAMI!6osmoRCY{@r&A2!|3M=bNTK=dG*f!j~B+-`c5M|)@vCeG$R zANL^IO)+sbBEt4|(eLniSWSB^36n%_a-4O<=rP>KDs6KQi^pgte;cC(mG?J23oF$6 E1;ftfQvd(} From 065ed86bbd9a5ea7185801f7a3cd9637b0e02b9f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 10:40:44 +0530 Subject: [PATCH 0381/1137] Add created, updated fields in suborg details model --- gsoc/admin.py | 4 +++- gsoc/forms.py | 3 ++- gsoc/migrations/0042_auto_20190707_0505.py | 23 +++++++++++++++++++++ gsoc/models.py | 3 +++ project.db | Bin 1515520 -> 1519616 bytes suborg/views.py | 3 +++ 6 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 gsoc/migrations/0042_auto_20190707_0505.py diff --git a/gsoc/admin.py b/gsoc/admin.py index 28ba3d10..c653a911 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -473,6 +473,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', 'blog_url', 'link', 'accepted', 'changed', 'last_reviewed_at', 'last_reviewed_by', + 'created_at', 'updated_at', ) fieldsets = ( ( @@ -491,7 +492,8 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', 'blog_url', 'link', 'changed', 'accepted', - 'last_reviewed_at', 'last_reviewed_by' + 'last_reviewed_at', 'last_reviewed_by', + 'created_at', 'updated_at', ) } ), diff --git a/gsoc/forms.py b/gsoc/forms.py index 566f784f..7608d25d 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -60,7 +60,8 @@ class Meta: class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails - exclude = ('accepted', 'last_message', 'changed', 'last_reviewed_at', 'last_reviewed_by') + exclude = ('accepted', 'last_message', 'changed', 'last_reviewed_at', 'last_reviewed_by', + 'created_at', 'updated_at') widgets = { 'suborg_admin_email': forms.HiddenInput(), 'gsoc_year': forms.HiddenInput(), diff --git a/gsoc/migrations/0042_auto_20190707_0505.py b/gsoc/migrations/0042_auto_20190707_0505.py new file mode 100644 index 00000000..023a08d4 --- /dev/null +++ b/gsoc/migrations/0042_auto_20190707_0505.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.9 on 2019-07-07 05:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0041_auto_20190707_0432'), + ] + + operations = [ + migrations.AddField( + model_name='suborgdetails', + name='created_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='suborgdetails', + name='updated_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 600f8168..71489bda 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -240,6 +240,9 @@ class SubOrgDetails(models.Model): last_reviewed_at = models.DateTimeField(null=True, blank=True) last_reviewed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) + created_at = models.DateTimeField(null=True, blank=True) + updated_at = models.DateTimeField(null=True, blank=True) + accepted = models.BooleanField(default=False) changed = models.BooleanField(default=None, null=True) diff --git a/project.db b/project.db index b4883f199be57463263191f6c4648d4d92c005ed..50df89f7dc36a2307ff345ba118593ac439be485 100644 GIT binary patch delta 592 zcmb`^&1(}u6aet~*km`!=3}$jDMc`$BAT++Ntzgp1X@jQHRMtnu#&B58->!X)E-I^ z6FgJ|qoKlhs-m>utrB6R7yBm^4@wPU=^r3^@MdYBc=X!G{0{F8GjDj)+?j0dPd+MY8e5>L? z)|Ju5qFd)YlderI-E(Vcp1RC)JXhmcp8cC#;5k?cp3SFto@a-kdMTY*?Nwv4SGAHV z)oEF%j&)Z-h(`8Yy=dyE3tI#&Zt9e!y8!l`_Tvn#%+fi|UhP{=`j`ab<3s4aT zJQ8?(t!=1%sT6sBQCz=m^~nW?v>#<_>c@71e{`1Lvf+#U5xU&{Tt_ MNf_f0jAD=+1ECdo=Q9{&GLV?m?1WAX( zyGTQW(GU~{uTA+M)Y5cA{Ru5?K|RCgJ3Qaz`_#UQwSMt>B-}#?AAt&jKuD&pIfHwy zz0wNX(@*cMAYJ?j`TbC?U+r}dw4Y(T)7?&e`taL%&TQmN419nCJP?2%h(H1|P=E>o zU;qSx20|bV2Eh=^nfmJlHzs*h% Date: Sun, 7 Jul 2019 10:43:47 +0530 Subject: [PATCH 0382/1137] Fix pep8 warnings --- gsoc/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 71489bda..69e46a68 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -772,8 +772,8 @@ def create_scheduler(self, trigger_time=timezone.now()): f'{self.user_gsoc_year.gsoc_year} with PSF'), template_data={ 'register_link': - settings.INETLOCATION + - self.url, + (settings.INETLOCATION + + self.url), 'role': self.user_role, 'gsoc_year': self.user_gsoc_role, 'suborg':self.user_suborg.suborg_name.strip()}) From 56a5bd6f567cf238013c1f147e7a4cce123b37c6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 10:46:27 +0530 Subject: [PATCH 0383/1137] Fix pep8 warnings --- gsoc/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 69e46a68..30a77663 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -771,9 +771,8 @@ def create_scheduler(self, trigger_time=timezone.now()): f' as a {role[self.user_role]} for GSoC ' f'{self.user_gsoc_year.gsoc_year} with PSF'), template_data={ - 'register_link': - (settings.INETLOCATION + - self.url), + 'register_link': settings.INETLOCATION + + self.url, 'role': self.user_role, 'gsoc_year': self.user_gsoc_role, 'suborg':self.user_suborg.suborg_name.strip()}) From dbb3ff610f686dfb58c4b7efc959ec79a4a49365 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 10:47:30 +0530 Subject: [PATCH 0384/1137] Fix pep8 warnings --- gsoc/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 30a77663..2d5ed9e3 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -771,11 +771,10 @@ def create_scheduler(self, trigger_time=timezone.now()): f' as a {role[self.user_role]} for GSoC ' f'{self.user_gsoc_year.gsoc_year} with PSF'), template_data={ - 'register_link': settings.INETLOCATION + - self.url, + 'register_link': settings.INETLOCATION + self.url, 'role': self.user_role, 'gsoc_year': self.user_gsoc_role, - 'suborg':self.user_suborg.suborg_name.strip()}) + 'suborg': self.user_suborg.suborg_name.strip()}) s = Scheduler.objects.create(command='send_email', activation_date=trigger_time, data=scheduler_data) From d25abc7f3c73b022608d3a1f3a215cc8b8de4e86 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 12:50:26 +0530 Subject: [PATCH 0385/1137] Add category field in blogpostduedate --- .../0043_blogpostduedate_category.py | 18 ++++++++++++++++++ gsoc/models.py | 7 +++++++ project.db | Bin 1519616 -> 1519616 bytes 3 files changed, 25 insertions(+) create mode 100644 gsoc/migrations/0043_blogpostduedate_category.py diff --git a/gsoc/migrations/0043_blogpostduedate_category.py b/gsoc/migrations/0043_blogpostduedate_category.py new file mode 100644 index 00000000..c4987774 --- /dev/null +++ b/gsoc/migrations/0043_blogpostduedate_category.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.9 on 2019-07-07 07:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0042_auto_20190707_0505'), + ] + + operations = [ + migrations.AddField( + model_name='blogpostduedate', + name='category', + field=models.IntegerField(blank=True, choices=[(0, 'Weekly Check-In'), (1, 'Blog Post')], null=True), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 2d5ed9e3..c270a2a9 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -486,6 +486,11 @@ def save(self, *args, **kwargs): class BlogPostDueDate(models.Model): + categories = ( + (0, 'Weekly Check-In'), + (1, 'Blog Post'), + ) + class Meta: ordering = ['date'] title = models.CharField(max_length=100, default='Weekly Blog Post Due') @@ -499,6 +504,8 @@ class Meta: related_name='pre') post_blog_reminder_builder = models.ManyToManyField(Builder, blank=True) event_id = models.CharField(max_length=255, null=True, blank=True) + category = models.IntegerField(choices=categories, null=True, blank=True) + def add_to_calendar(self): with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: diff --git a/project.db b/project.db index 50df89f7dc36a2307ff345ba118593ac439be485..589939fcc68ed977b9725fcc6a34fc8aefebe28e 100644 GIT binary patch delta 664 zcmXxiPe>F|90&0C-puUG?C$LDv7|q0EgrNjL%~RLc2^VEZMSw; zP|0vAQ2H|{bC{b#P|+olg~mWoItwLq?67#~Ce+*H%t zc?Tg}`3Dh%#1HH7PQ0^aN?~G?S}E*AeEaCKaNOevdP(XKLjhYj7PI1#xKxwPI|32+ z-M!|}8Rxj|iBH@|nw59H#WXHq^)s{1L5#X<3rdvnuOnABWv%urc8}nLXQ}u~DvkjG z3}68Ve1Hc65P=^AKoCeE1i~N!WN->Jc&WIu+sf#|U6;S5T*ZPTZ;sV;bBDjyYYlUV z4)je|N;$*m>YT_HO4AQZmFnc&)MTbQHIajDx>R0B7#$bejc!=F(cP0UdW^(5ID?jz zjWNaB7^6A${Jsx-J)9xGvYIZs*dV}aIi zV_a|fKW2+QQ-p!1%6fWqXej1&Wz`5_1FOh@tK$Ky-9F806G>tXyYjr!=yG}bni;WG zcX5{f@cQnn0~{+Iq6?7j3R)Rk6Eq7a$y0g~kGk7=y6e3wsFyH|Nt$Z1*+|~}j(qNA gSp7ZwDX4qwp30k&314m$jDXvastDf8Hf@go3&|3`L;wH) delta 530 zcmZY6O=uHA6ae6v*`LhrW;YwNKh&ZP6rq8LL=9g2X<~w>Mh`vYP-L?;6ijN+R#}Qj zqCy1SV%)R8ZoEv1|D8igruwDMMP6 zccm@yvrr*9JcEKL>CLhg&t4y-y-C6=^Y#;2>>LeC?Bf;ZtyT-qQ@4Zj-$GrtmQd2_Sp4hE5**hcRDkI;SzO_%_DE!BxzDWm6gU?5e$oRgT}q|4h96nO>U~%=e!h&C{Fk>^ktl NMVPQe_nX~}{RIF4mLLEC From fcadd90e74cec03bc361b8988595def778cd5f3f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 12:54:44 +0530 Subject: [PATCH 0386/1137] Add category in admin timeline change form --- gsoc/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index 7608d25d..c8bcd173 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -42,7 +42,7 @@ class Meta: class BlogPostDueDateForm(forms.ModelForm): class Meta: model = BlogPostDueDate - fields = ('title', 'date') + fields = ('title', 'date', 'category') class EventForm(forms.ModelForm): From f43df5ab27f2f9e2a569d07b3372ef74227c756a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 7 Jul 2019 16:24:08 +0530 Subject: [PATCH 0387/1137] Add type of blog in reminder emails --- gsoc/common/utils/build_tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index b01605fa..6cb581b6 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -17,6 +17,7 @@ def build_pre_blog_reminders(builder): profile.reminder_disabled): template_data = { 'current_blog_count': profile.current_blog_count, + 'type': due_date.category, 'due_date': due_date.date.strftime('%d %B %Y') } From 7e9854d21b3adfd0e886d0b83689ecee65580f34 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 9 Jul 2019 07:19:09 +0530 Subject: [PATCH 0388/1137] Add link to blogs in userprofile admin --- gsoc/admin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index c653a911..1dd8dcd2 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -305,7 +305,7 @@ class HiddenUserProfileAdmin(admin.ModelAdmin): list_display = ('user', 'email', 'gsoc_year', 'suborg_full_name', 'proposal_confirmed', 'hidden', 'reminder_disabled', 'current_blog_count') list_filter = ('hidden', 'reminder_disabled') - readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'app_config', + readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'blog_link', 'proposal_confirmed', 'current_blog_count') fieldsets = ( ('Unhide', { @@ -313,10 +313,16 @@ class HiddenUserProfileAdmin(admin.ModelAdmin): }), ('User Profile Details', { 'fields': ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'proposal_confirmed', - 'app_config', 'current_blog_count') + 'blog_link', 'current_blog_count') }) ) + def blog_link(self, obj): + ns = obj.app_config.namespace + page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) + url = page.get_absolute_url() + return mark_safe(f'{ns}') + def email(self, obj): return obj.user.email From beb654b6a666bf58853808d1327844e298e1b745 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 8 Jul 2019 20:19:59 -0600 Subject: [PATCH 0389/1137] Update pre_blog_reminder.html --- gsoc/templates/email/pre_blog_reminder.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/templates/email/pre_blog_reminder.html b/gsoc/templates/email/pre_blog_reminder.html index c0303313..70c0cfa8 100644 --- a/gsoc/templates/email/pre_blog_reminder.html +++ b/gsoc/templates/email/pre_blog_reminder.html @@ -1,7 +1,7 @@ -This is a reminder for the blog post required on {{ due_date }}.
    +This is a reminder for the {% if role == 0 %}Blog Post{% else %}Weekly Check-In{% endif %} required on {{ due_date }}.

    {% if current_blog_count > 1 %} -It looks like you have {{ current_blog_count }} blog post(s) pending. Make sure to get your posts in as soon as possible.

    +It looks like you have {{ current_blog_count }} post(s) pending. Make sure to get your posts in as soon as possible.

    {% endif %} If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From e4ae69d59f137710de70b7ff5c0c06c6dd847a9a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 9 Jul 2019 07:53:39 +0530 Subject: [PATCH 0390/1137] Fix pep8 warnings --- gsoc/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index c270a2a9..4b382f31 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -490,7 +490,7 @@ class BlogPostDueDate(models.Model): (0, 'Weekly Check-In'), (1, 'Blog Post'), ) - + class Meta: ordering = ['date'] title = models.CharField(max_length=100, default='Weekly Blog Post Due') @@ -505,7 +505,7 @@ class Meta: post_blog_reminder_builder = models.ManyToManyField(Builder, blank=True) event_id = models.CharField(max_length=255, null=True, blank=True) category = models.IntegerField(choices=categories, null=True, blank=True) - + def add_to_calendar(self): with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: From 3ef56c616fbfa3c95b9128898b76db17293b1a4c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 9 Jul 2019 08:06:51 +0530 Subject: [PATCH 0391/1137] Fix typo --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 4b382f31..dda535b2 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -780,7 +780,7 @@ def create_scheduler(self, trigger_time=timezone.now()): template_data={ 'register_link': settings.INETLOCATION + self.url, 'role': self.user_role, - 'gsoc_year': self.user_gsoc_role, + 'gsoc_year': self.user_gsoc_year, 'suborg': self.user_suborg.suborg_name.strip()}) s = Scheduler.objects.create(command='send_email', activation_date=trigger_time, From c4c5f9bc92482dc034ef5e04fe66b9ec3657493f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 9 Jul 2019 08:06:51 +0530 Subject: [PATCH 0392/1137] Fix typo --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 4b382f31..26e17675 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -780,7 +780,7 @@ def create_scheduler(self, trigger_time=timezone.now()): template_data={ 'register_link': settings.INETLOCATION + self.url, 'role': self.user_role, - 'gsoc_year': self.user_gsoc_role, + 'gsoc_year': self.user_gsoc_year.gsoc_year, 'suborg': self.user_suborg.suborg_name.strip()}) s = Scheduler.objects.create(command='send_email', activation_date=trigger_time, From ef42a5f76f30f21a4e2da7e998a58a98d8337f22 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 9 Jul 2019 13:14:53 +0530 Subject: [PATCH 0393/1137] Add accepted blog in suborg details admin page --- gsoc/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 1dd8dcd2..d64aa90e 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -468,7 +468,7 @@ def has_change_permission(self, request, obj=None): class SubOrgDetailsAdmin(admin.ModelAdmin): - list_display = ('suborg_name', 'gsoc_year', 'changed') + list_display = ('suborg_name', 'gsoc_year', 'changed', 'accepted') list_filter = ('gsoc_year', 'changed') # fields = ('last_message', ) readonly_fields = ( From 09d5ffd6fdfe4d7cc442bf497c4e18804212d49e Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 22:52:17 -0600 Subject: [PATCH 0394/1137] Update requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index b94ff60e..e1de7a25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,3 +41,5 @@ google-auth-oauthlib>=0.3.0 #github api for pushing html pages PyGithub>=1.43.7 +#memcached +pylibmc>=1.6.0 From 48ed445c764d4a767098f63c05ee8f0ba2e417bc Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 22:53:45 -0600 Subject: [PATCH 0395/1137] Update settings_local.py.template --- settings_local.py.template | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/settings_local.py.template b/settings_local.py.template index 3348326a..05b3cdff 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -78,3 +78,13 @@ GITHUB_FILE_PATH = { 'deadlines.html': 'deadlines.html', 'index.html': 'index.html' } + +#memcached +if not DEBUG: + MIDDLEWARE = ('django.middleware.cache.UpdateCacheMiddleware',)+MIDDLEWARE+('django.middleware.cache.FetchFromCacheMiddleware',) + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', + 'LOCATION': '127.0.0.1:11211', + } + } From 107c6b6647fbff2017bd30211670f571eebba9e4 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:04:22 -0600 Subject: [PATCH 0396/1137] Update settings.py --- gsoc/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 832567b8..35fc0e37 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -96,6 +96,7 @@ def gettext(s): return s ] MIDDLEWARE = ( + 'django.middleware.cache.UpdateCacheMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware', 'cms.middleware.utils.ApphookReloadMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -109,7 +110,7 @@ def gettext(s): return s 'cms.middleware.page.CurrentPageMiddleware', 'cms.middleware.toolbar.ToolbarMiddleware', 'cms.middleware.language.LanguageCookieMiddleware', - + 'django.middleware.cache.FetchFromCacheMiddleware', ) INSTALLED_APPS = ( From 76d59c2b7527406718c33a51659f0f156c60ac14 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:04:46 -0600 Subject: [PATCH 0397/1137] Update settings_local.py.template --- settings_local.py.template | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/settings_local.py.template b/settings_local.py.template index 05b3cdff..c7f7d825 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -80,11 +80,9 @@ GITHUB_FILE_PATH = { } #memcached -if not DEBUG: - MIDDLEWARE = ('django.middleware.cache.UpdateCacheMiddleware',)+MIDDLEWARE+('django.middleware.cache.FetchFromCacheMiddleware',) - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', - 'LOCATION': '127.0.0.1:11211', - } +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', + 'LOCATION': '127.0.0.1:11211', } +} From 8e8e867cad0c9113bf0a0bf3a4cee3491078bb74 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:17:20 -0600 Subject: [PATCH 0398/1137] Update settings_local.py.template --- settings_local.py.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings_local.py.template b/settings_local.py.template index c7f7d825..ddf168ea 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -82,7 +82,7 @@ GITHUB_FILE_PATH = { #memcached CACHES = { 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 'LOCATION': '127.0.0.1:11211', } } From 9b368e72ffd5186a93966c52d6f13f0f4679c306 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:24:33 -0600 Subject: [PATCH 0399/1137] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e1de7a25..21e0da73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,4 +42,4 @@ google-auth-oauthlib>=0.3.0 PyGithub>=1.43.7 #memcached -pylibmc>=1.6.0 +#pylibmc>=1.6.0 From 2657658a93a74fc54d7e3d3bd027cec0c63aa1ba Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:25:00 -0600 Subject: [PATCH 0400/1137] Update settings_local.py.template --- settings_local.py.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings_local.py.template b/settings_local.py.template index ddf168ea..c7df19a8 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -79,7 +79,7 @@ GITHUB_FILE_PATH = { 'index.html': 'index.html' } -#memcached +#memcached use django.core.cache.backends.memcached.PyLibMCCache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', From 33b2755ca57d72eca3b5654498ad38f6db1e534d Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:38:59 -0600 Subject: [PATCH 0401/1137] Update base.html --- gsoc/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index ad2ca8d5..f80a165e 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -17,7 +17,7 @@ - + {% load static %} {% block head %}{% endblock %} From ff95f82d297f401f26a7430749489b0f4a2e9576 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:39:12 -0600 Subject: [PATCH 0402/1137] Update base.html --- gsoc/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index f80a165e..1d53c613 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -16,7 +16,7 @@ - + {% load static %} From 76b04227da18e4a82c859d6a920a60a144fe843a Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:42:06 -0600 Subject: [PATCH 0403/1137] Update base.html --- gsoc/templates/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 1d53c613..b9c99e7f 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -115,8 +115,8 @@
    {% render_block 'js' %} - - + + {% block js %}{% endblock %} From 3eda70ef6e8d6f7b3cd886cebba84ac1ae06f79c Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:49:39 -0600 Subject: [PATCH 0404/1137] Update base.html --- gsoc/templates/base.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index b9c99e7f..a4a0bd21 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -7,15 +7,15 @@ Python GSoC – Splash - + - + - - - + crossorigin="anonymous" media="print"> + + + {% load static %} From 82413590a8017fef360d96a09dc2d6e137aadd37 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 10 Jul 2019 23:52:11 -0600 Subject: [PATCH 0405/1137] Update base.html --- gsoc/templates/base.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index a4a0bd21..563928f3 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -7,15 +7,15 @@ Python GSoC – Splash - + - + - - - + crossorigin="anonymous" > + + + {% load static %} From a8e1e8e87021a040209d2ce5d0e81459120b3bb1 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 11 Jul 2019 10:42:02 +0530 Subject: [PATCH 0406/1137] Add comment admin --- gsoc/admin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index d64aa90e..1833d196 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,6 +1,6 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, - GsocEndDate) + GsocEndDate, Comment) from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, GsocEndDateForm) @@ -519,3 +519,13 @@ def has_add_permission(self, request, obj=None): admin.site.register(SubOrgDetails, SubOrgDetailsAdmin) + + +class CommentAdmin(admin.ModelAdmin): + list_display = ('username', 'article', 'content') + + def has_add_permission(self, request, obj=None): + return False + + +admin.site.register(Comment, CommentAdmin) From 9b0dc31ee5172225570f5bc94496ae034d04327e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 11 Jul 2019 11:29:04 +0530 Subject: [PATCH 0407/1137] Add mentor for specific suborg application --- suborg/templates/application_list.html | 17 ++++++++++++----- suborg/urls.py | 2 +- suborg/views.py | 25 +++++++++++++++++-------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/suborg/templates/application_list.html b/suborg/templates/application_list.html index e3f5a21b..3b36b2c4 100644 --- a/suborg/templates/application_list.html +++ b/suborg/templates/application_list.html @@ -5,13 +5,20 @@

    Select any of the applications to change

    diff --git a/suborg/urls.py b/suborg/urls.py index 6564c44e..a79ae9b9 100644 --- a/suborg/urls.py +++ b/suborg/urls.py @@ -16,6 +16,6 @@ # name='reject_application'), ])), url('^mentor/', include([ - url('^add/', views.add_mentor, name='add_mentor') + url('^add/(?P[0-9]+)/', views.add_mentor, name='add_mentor') ])), ] diff --git a/suborg/views.py b/suborg/views.py index 5153640a..7a4ebd54 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -19,7 +19,7 @@ def is_suborg_admin(user): def home(request): - return redirect(reverse('suborg:register_suborg')) + return redirect(reverse('suborg:application_list')) @decorators.login_required @@ -108,9 +108,18 @@ def accept_application(request, application_id): # return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) -@decorators.user_passes_test(is_suborg_admin) -def add_mentor(request): - profile = request.user.suborg_admin_profile() +@decorators.login_required +def add_mentor(request, application_id): + application = SubOrgDetails.objects.get(id=application_id) + + if not application.accepted: + messages.error(request, 'Application not accepted yet! Can not add mentors.') + return redirect(reverse('suborg:application_list')) + + if application.suborg_admin_email != request.user.email: + messages.error(request, 'You are not authorized to add mentors for this suborg.') + return redirect(reverse('suborg:application_list')) + MentorFormSet = modelformset_factory(RegLink, fields=('email', ), extra=4) if request.method == 'POST': @@ -118,8 +127,8 @@ def add_mentor(request): if formset.is_valid(): instances = formset.save(commit=False) for instance in instances: - instance.user_suborg = profile.suborg_full_name - instance.user_gsoc_year = profile.gsoc_year + instance.user_suborg = application.suborg + instance.user_gsoc_year = application.gsoc_year instance.user_role = 2 instance.save() else: @@ -127,8 +136,8 @@ def add_mentor(request): 'formset': formset, }) - formset = MentorFormSet(queryset=RegLink.objects.filter(user_gsoc_year=profile.gsoc_year, - user_suborg=profile.suborg_full_name, + formset = MentorFormSet(queryset=RegLink.objects.filter(user_gsoc_year=application.gsoc_year, + user_suborg=application.suborg, user_role=2)) return render(request, 'add_mentor.html', { From a1056c94bc7afef6deb4532534414b769b358781 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 11 Jul 2019 11:31:29 +0530 Subject: [PATCH 0408/1137] Migrate db --- project.db | Bin 1519616 -> 1523712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 589939fcc68ed977b9725fcc6a34fc8aefebe28e..38941d70de6dcb7f3026e5c10c05f0703ba94780 100644 GIT binary patch delta 1179 zcmZ`(eP~-%6o2=9-NjrlhxItju~$Tj`=YzzjN0}K*_!k{r23>JgK;PtQ@UYy;El?8f{F#l-!-9!pU;Rt_@ z8)L_ri}WJEW7-<00SWrwu*N?C6yDt-b$$c7Ao_V7-qa`7UKmcpo57{%5)Gb#bcHdZDeYXf4~_@*&x=!B>E zHQvSL+3SA~9MuYF(+GVXuU2WsY#JfvaY9%0hb~JxOXJEX@X7l?4=+le4eLTX$5?bR zRCZfDFMy4$rI<65W%MZJ&i#MN!pQ;>bWQ=26t3;#GYy)$gq@ZFVSu9jfY3>Khfc(c`HLDn4(p zA(HQSpptT*QoSb-!vxP=CHudm)S$;7R3Fbjji#OyY#!DB52A{RO5ndKs5k|`Kj07L zpF(R9L3a6KnRK={)|1Yp?ymli(x9q-#W%EnU%9@2pKYA05kUtb9yZ@FckwOk4Q7eH zOtqTA#)RRxkR@A%?Vtlceg}FfZum-!`L6kDr&4VWQ5~XblMuP}`T%3ZKZN4`%yPS!Df=v7_v1E3fC~B_}}WE9oq9ElQQBWkDK7pWKmH zG`0?F(4PyE8#(8tO?>Cxy|v~pYB!c{q3HYptsbpjkyc5~k3Rlfxl$t1NNG!E|ZK2SCXPI|q z-WlGR?dp1?>wfKEL3j!w-2N{_5Yk64#7g~BsHDu1&3kCGnfx^MBoBwL?nxHe9-*czpl5ev`rhMSNjh-!AU1=tVk% zN+PPA@kn_4U2sW|CQ{!<#SYhauCCuzobCe8Yco-_ULsCGO~K2&*a1Dny($=1~I zx7*TuaM{`eBvluUKX8E{Kcy}XT3=;hO zf92-fs1XuT$6kQqwc*OBAxy_!f`D$^KyExrMn+?2qFGv={l%Ar!dkzpm_)evk@C2RbAtjcGaw zsXIk{)q06iqZjRhFvdrhtB< v>_j+`Pp&P7a;`+3o77buL8qCN2xEAq1ULe!z+qU~u5IjiP$r}Vq9XD)B+n83 From 3d72f83750321783718a2bdf836168b8c42c862f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 15 Jul 2019 11:01:15 +0530 Subject: [PATCH 0409/1137] Reminder subject according to category of blog post --- gsoc/common/utils/build_tasks.py | 27 +++++++++---- gsoc/models.py | 66 ++++++++++++++++---------------- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 6cb581b6..33a44d18 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -12,6 +12,11 @@ def build_pre_blog_reminders(builder): due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() + categories = ( + (0, 'Weekly Check-In'), + (1, 'Blog Post'), + ) + category = categories[due_date.category][1] for profile in profiles: if profile.current_blog_count is not 0 and not (profile.hidden or profile.reminder_disabled): @@ -19,11 +24,11 @@ def build_pre_blog_reminders(builder): 'current_blog_count': profile.current_blog_count, 'type': due_date.category, 'due_date': due_date.date.strftime('%d %B %Y') - } + } scheduler_data = build_send_mail_json(profile.user.email, template='pre_blog_reminder.html', - subject='Reminder for Weekly Blog Post', + subject=f'Reminder for {category}', template_data=template_data) s = Scheduler.objects.create(command='send_email', @@ -43,6 +48,12 @@ def build_post_blog_reminders(builder): else: blogs_count = 1 + categories = ( + (0, 'Weekly Check-In'), + (1, 'Blog Post'), + ) + category = categories[due_date.category][1] + gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() for profile in profiles: @@ -70,14 +81,14 @@ def build_post_blog_reminders(builder): 'suborg_name': profile.suborg_full_name.suborg_name, 'due_date': due_date.date.strftime('%d %B %Y'), 'current_blog_count': profile.current_blog_count - } + } scheduler_data_mentors = build_send_mail_json( mentors_emails, template='post_blog_reminder_mentors.html', - subject='Weekly Blog Post missed by a Student of your Sub-Org', + subject=f'{category} missed by a Student of your Sub-Org', template_data=mentors_template_data - ) + ) Scheduler.objects.create(command='send_email', data=scheduler_data_mentors) @@ -85,14 +96,14 @@ def build_post_blog_reminders(builder): student_template_data = { 'current_blog_count': profile.current_blog_count, 'due_date': due_date.date.strftime('%d %B %Y') - } + } scheduler_data_student = build_send_mail_json( profile.user.email, template=student_template, - subject='Reminder for Weekly Blog Post', + subject=f'Reminder for {category}', template_data=student_template_data - ) + ) Scheduler.objects.create(command='send_email', data=scheduler_data_student) diff --git a/gsoc/models.py b/gsoc/models.py index 26e17675..46d7bfaa 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -118,7 +118,7 @@ def is_unclean(self): '', '<', '>', - ) + ) for _ in unclean_texts: if _ in self.lead_in: return True @@ -165,36 +165,36 @@ class SubOrgDetails(models.Model): GsocYear, on_delete=models.CASCADE, related_name='suborg_details' - ) + ) reason_for_participation = models.TextField( verbose_name='Why does your org want to participate in Google Summer of Code?' - ) + ) suborg_admin_email = models.EmailField( verbose_name='Suborg admin email' - ) + ) mentors_student_engagement = models.TextField( verbose_name='How will you keep mentors engaged with their students?' - ) + ) students_on_schedule = models.TextField( verbose_name='How will you help your students stay ' 'on schedule to complete their projects?' - ) + ) students_involvement_gsoc = models.TextField( verbose_name='How will you get your students involved in your community during GSoC?' - ) + ) students_involvement_after = models.TextField( verbose_name='How will you keep students involved with your community after GSoC?' - ) + ) past_gsoc_experience = models.BooleanField( verbose_name='Has your org been accepted as a mentor org ' 'in Google Summer of Code before?' - ) + ) past_years = models.ManyToManyField( GsocYear, blank=True, verbose_name='Which years did your org participate in GSoC?' - ) + ) suborg_in_past = models.BooleanField(verbose_name='Was this as a Suborg?') applied_but_not_selected = models.ManyToManyField( @@ -203,19 +203,19 @@ class SubOrgDetails(models.Model): related_name='applied_not_selected', verbose_name='If your org has applied for GSoC ' 'before but not been accepted, select the years' - ) + ) year_of_start = models.IntegerField(verbose_name='What year was your project started?') source_code = models.URLField(verbose_name='Where does your source code live?') docs = models.URLField( verbose_name='Please provide the URL that points to the repository, ' 'GitHub organization, or a web page that describes how to' ' get your source code' - ) + ) anything_else = models.TextField( null=True, blank=True, verbose_name='Anything else we should know (optional)' - ) + ) suborg = models.ForeignKey(SubOrg, null=True, blank=True, on_delete=models.CASCADE, verbose_name='Select your suborg, if ' @@ -258,7 +258,7 @@ def accept(self): template_data = { 'gsoc_year': self.gsoc_year.gsoc_year, 'suborg_name': self.suborg.suborg_name, - } + } scheduler_data = build_send_mail_json(self.suborg_admin_email, template='suborg_accept.html', subject='Acceptance for GSoC@PSF {}'. @@ -289,7 +289,7 @@ def send_update_notification(self): template_data = { 'suborg_name': suborg_name - } + } scheduler_data = build_send_mail_json(settings.ADMIN_EMAIL, template='suborg_application_notification.html', subject='Review new/updated SubOrg Application', @@ -297,7 +297,6 @@ def send_update_notification(self): Scheduler.objects.create(command='send_email', data=scheduler_data) - def send_review(self): self.accepted = False self.save() @@ -311,7 +310,7 @@ def send_review(self): 'gsoc_year': self.gsoc_year.gsoc_year, 'suborg_name': suborg_name, 'message': self.last_message, - } + } scheduler_data = build_send_mail_json(self.suborg_admin_email, template='suborg_review.html', subject='Review your SubOrg Application' @@ -399,7 +398,7 @@ class Builder(models.Model): ('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), - ) + ) category = models.CharField(max_length=40, choices=categories) activation_date = models.DateTimeField(null=True, blank=True) @@ -423,7 +422,7 @@ def add_calendar(self): calendar = { 'summary': 'GSoC @ PSF Calendar', 'timezone': 'UTC', - } + } calendar = service.calendars().insert(body=calendar).execute() self.calendar_id = calendar.get('id') self.save() @@ -456,11 +455,11 @@ def add_to_calendar(self): 'summary': self.title, 'start': { 'date': self.start_date.strftime('%Y-%m-%d') - }, + }, 'end': { 'date': self.end_date.strftime('%Y-%m-%d') - }, - } + }, + } calendar_id = self.timeline.calendar_id if self.timeline else 'primary' if not self.event_id: event = service.events().insert(calendarId=calendar_id, body=event).execute() @@ -489,14 +488,14 @@ class BlogPostDueDate(models.Model): categories = ( (0, 'Weekly Check-In'), (1, 'Blog Post'), - ) + ) class Meta: ordering = ['date'] title = models.CharField(max_length=100, default='Weekly Blog Post Due') date = models.DateField() timeline = models.ForeignKey(Timeline, on_delete=models.CASCADE, null=True, - blank=True) + blank=True) add_counter_scheduler = models.ForeignKey(Scheduler, on_delete=models.CASCADE, null=True, blank=True) pre_blog_reminder_builder = models.ForeignKey(Builder, on_delete=models.CASCADE, @@ -506,7 +505,6 @@ class Meta: event_id = models.CharField(max_length=255, null=True, blank=True) category = models.IntegerField(choices=categories, null=True, blank=True) - def add_to_calendar(self): with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: creds = pickle.load(token) @@ -515,11 +513,11 @@ def add_to_calendar(self): 'summary': self.title, 'start': { 'date': self.date.strftime('%Y-%m-%d') - }, + }, 'end': { 'date': self.date.strftime('%Y-%m-%d') - }, - } + }, + } calendar_id = self.timeline.calendar_id if self.timeline else 'primary' if not self.event_id: event = service.events().insert(calendarId=calendar_id, body=event).execute() @@ -548,7 +546,7 @@ def create_scheduler(self): def create_builders(self): builder_data = json.dumps({ 'due_date_pk': self.pk - }) + }) s = Builder.objects.create(category='build_pre_blog_reminders', activation_date=self.date + datetime.timedelta(days=-3), @@ -725,7 +723,7 @@ def create_user(self, *args, is_staff=True, **kwargs): namespace = str(uuid.uuid4()) email = kwargs.get('email', self.email) user, status = User.objects.get_or_create(*args, is_staff=is_staff, - email=email, **kwargs) + email=email, **kwargs) role = {k: v for v, k in UserProfile.ROLES} profile = UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, @@ -770,7 +768,7 @@ def create_scheduler(self, trigger_time=timezone.now()): 1: 'Suborg Admin', 2: 'Mentor', 3: 'Student', - } + } scheduler_data = build_send_mail_json(self.email, template='invite.html', subject=(f'You have been invited to join ' @@ -832,7 +830,7 @@ def send_notifications(self): 'created_at': self.created_at.strftime('%I:%M %p, %d %B %Y'), 'username': self.username, 'link': comment_link, - } + } scheduler_data = build_send_mail_json(self.article.owner.email, template='comment_notification.html', subject='{} commented on your article'. @@ -856,8 +854,8 @@ class ArticleReview(models.Model): last_reviewed_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, limit_choices_to={ - 'is_superuser': True, - }) + 'is_superuser': True, + }) is_reviewed = models.BooleanField(default=False) def save(self, *args, **kwargs): From f6ab6f43b0510a9133a7bffc1b86a50d7cf47d31 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 15 Jul 2019 17:36:50 +0530 Subject: [PATCH 0410/1137] Fix Page Notifications --- gsoc/admin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gsoc/admin.py b/gsoc/admin.py index 1833d196..5e65d580 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -337,6 +337,10 @@ class PageNotificationAdmin(admin.ModelAdmin): list_display = ('message', 'user', 'page') list_filter = ('user', 'page') + def save_model(self, request, obj, form, change): + obj.user = request.user + super(PageNotificationAdmin, self).save_model(request, obj, form, change) + def get_fieldsets(self, request, obj=None): if request.user.is_superuser: fieldsets = ( From faf5904af9f18501587a30b55cc0b48df1950b09 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 15 Jul 2019 18:48:46 +0530 Subject: [PATCH 0411/1137] New student can edit their add/edit page's notification --- gsoc/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 46d7bfaa..a84abb81 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -749,13 +749,19 @@ def create_user(self, *args, is_staff=True, **kwargs): apphook_namespace=namespace, parent=blog_list_page) su = User.objects.filter(is_superuser=True).first() - api.publish_page(page, su, 'en') + page = api.publish_page(page, su, 'en') + + PagePermission.objects.create(user=user, page=page) permissions = list() permissions.append(Permission.objects.filter(codename='add_article').first()) permissions.append(Permission.objects.filter(codename='change_article').first()) permissions.append(Permission.objects.filter(codename='delete_article').first()) permissions.append(Permission.objects.filter(codename='view_article').first()) + permissions.append(Permission.objects.filter(codename='add_pagenotification').first()) + permissions.append(Permission.objects.filter(codename='change_pagenotification').first()) + permissions.append(Permission.objects.filter(codename='delete_pagenotification').first()) + permissions.append(Permission.objects.filter(codename='view_pagenotification').first()) user.user_permissions.set(permissions) mark_urlconf_as_changed() From 7d2224850aa9c336086121bb28d871c0a1967d5f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 16 Jul 2019 12:30:13 +0530 Subject: [PATCH 0412/1137] Add scheduler for archiving current gsoc site --- gsoc/common/utils/commands.py | 13 +++++++++--- gsoc/common/utils/tools.py | 38 +++++++++++++++++++++++++++++++++++ gsoc/models.py | 1 + 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 1452ea2e..8ccf1046 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -8,7 +8,8 @@ from gsoc.models import (Scheduler, RegLink, GsocYear, UserProfile, Event, BlogPostDueDate, SubOrgDetails) -from .tools import send_mail, render_site_template, push_site_template +from .tools import (send_mail, render_site_template, push_site_template, + archive_current_gsoc_files) def send_email(scheduler: Scheduler): @@ -128,12 +129,18 @@ def update_site_template(scheduler: Scheduler): context = { 'events': Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), 'duedates': BlogPostDueDate.objects.filter(timeline__gsoc_year=gsoc_year).all(), - } + } elif template == 'index.html': context = { 'suborgs': SubOrgDetails.objects.filter(gsoc_year=gsoc_year, accepted=True).all(), - } + } content = render_site_template(template, context) push_site_template(settings.GITHUB_FILE_PATH[template], content) except Exception as e: return str(e) + + +def archive_gsoc_pages(scheduler: Scheduler): + try: + gsoc_year = GsocYear.objects.first() + archive_current_gsoc_files(gsoc_year.gsoc_year) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 734af2a2..191e826b 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -67,3 +67,41 @@ def push_site_template(file_path, content): repo = g.get_repo(settings.STATIC_SITE_REPO) f = repo.get_contents(file_path) repo.update_file(f.path, f'Update {file_path}', content, f.sha) + + +def is_year(file_name): + try: + year = int(file_name) + if year >= 2000 and year <= 2100: + return True + return False + except: + return False + + +def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md']): + contents = repo.get_contents('') + files = [] + while contents: + file_content = contents.pop(0) + if not (file_content.path in except_files or is_year(file_content.path)): + if file_content.type == "dir": + contents.extend(repo.get_contents(file_content.path)) + else: + files.append(file_content) + return files + + +def archive_current_gsoc_files(current_year): + g = Github(settings.GITHUB_ACCESS_TOKEN) + repo = g.get_repo(settings.STATIC_SITE_REPO) + files = get_files(repo) + for file in files: + try: + repo.create_file(f'{current_year}/{file.path}', + f'Archive GSoC {current_year} files', file.decoded_content) + except Exception as e1: + repo.update_file(f'{current_year}/{file.path}', + f'Archive GSoC {current_year} files', + file.content, + file.sha) diff --git a/gsoc/models.py b/gsoc/models.py index a84abb81..6eae91f6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -379,6 +379,7 @@ class Scheduler(models.Model): ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template'), + ('archive_gsoc_pages', 'archive_gsoc_pages') ) id = models.AutoField(primary_key=True) From 88447ddbcb3e436c39a8eb34692bef947ef66785 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 16 Jul 2019 12:30:37 +0530 Subject: [PATCH 0413/1137] Migrate db --- gsoc/migrations/0044_auto_20190716_0700.py | 18 ++++++++++++++++++ project.db | Bin 1523712 -> 1523712 bytes 2 files changed, 18 insertions(+) create mode 100644 gsoc/migrations/0044_auto_20190716_0700.py diff --git a/gsoc/migrations/0044_auto_20190716_0700.py b/gsoc/migrations/0044_auto_20190716_0700.py new file mode 100644 index 00000000..4eba5614 --- /dev/null +++ b/gsoc/migrations/0044_auto_20190716_0700.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.10 on 2019-07-16 07:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0043_blogpostduedate_category'), + ] + + operations = [ + migrations.AlterField( + model_name='scheduler', + name='command', + field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('revoke_student_permissions', 'revoke_student_permissions'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template'), ('archive_gsoc_pages', 'archive_gsoc_pages')], max_length=40), + ), + ] diff --git a/project.db b/project.db index 38941d70de6dcb7f3026e5c10c05f0703ba94780..7484c5d807d2013a370f8e7671b39c5186a46835 100644 GIT binary patch delta 469 zcmZo@h;3+yoggiEjDdl%1c<{JfCK{r=dFo4#*D`{CP*o8as1&-;;iMo#Cd76U_vAZ z7v~uUd7z3j43ZNaMJNB!xGc`bz#!O|&8*3hUYwsCU!0tgno^pRS~U5Y=4vMHzR8od zev9x-Vh{lujRGbw(q6=Pt=Uzl-BpJXh?#(x8Hibcm=%cGfS4VKIe?fGh`E568;E&; zm=}oofS4bM1%Ox(h=qVyc)P2P$bMyJRd$By>;WR4jO;*1uV=HhY7Ap#5Vdv$xzNDC z#3Vkkv?M>?$iUFjz}(O*-oV_z0L0NXFxNFSQ!p^MGBB_*GSf3PvoN+a+in{!a`?w~ z1p&qf{F4z$$vC1PZvxO6rcP|b2Sssq{)-Dev1e;rZ8)Aq!;HW#}_AOq^6YSq!vwH zq`ipoYO||OyQ>Z(5HkTWGZ3=?F)I+W0Wmuea{w_X5OV=BHxTmxF)tAF0Wm)i3jnbo z5DNjZ@OD=nk^RcdD(tq?*#ks8rz-{U%5GL@xWKmEHeBTJk7W+b0@D&iS(Nx0Lm2pr z_}=m*aX7KPWxc?1h}n Date: Tue, 16 Jul 2019 12:33:25 +0530 Subject: [PATCH 0414/1137] Add archive_gsoc_pages on gsoc end date --- gsoc/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gsoc/models.py b/gsoc/models.py index 6eae91f6..4240ceb2 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -983,6 +983,14 @@ def add_revoke_perms_builder(sender, instance, **kwargs): activation_date=instance.date) +# Add new builder for GsocEndDate +@receiver(models.signals.post_save, sender=GsocEndDate) +def add_revoke_perms_builder(sender, instance, **kwargs): + Scheduler.objects.create(command='archive_gsoc_pages', + activation_date=instance.date, + data="{}") + + # Publish the duedate to Github pages @receiver(models.signals.post_save, sender=BlogPostDueDate) def duedate_publish_to_github_pages(sender, instance, **kwargs): From dab075457a782c8f9ab31ed823d52c494215c7ac Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 18 Jul 2019 11:13:13 +0530 Subject: [PATCH 0415/1137] Add codesnippet for ckeditor --- gsoc/common/utils/tools.py | 4 +- gsoc/models.py | 8 +- gsoc/settings.py | 4 +- .../codesnippet/dialogs/codesnippet.js | 83 ++ .../plugins/codesnippet/icons/codesnippet.png | Bin 0 -> 532 bytes .../codesnippet/icons/hidpi/codesnippet.png | Bin 0 -> 1046 bytes .../ckeditor/plugins/codesnippet/lang/ar.js | 13 + .../ckeditor/plugins/codesnippet/lang/az.js | 13 + .../ckeditor/plugins/codesnippet/lang/bg.js | 13 + .../ckeditor/plugins/codesnippet/lang/ca.js | 13 + .../ckeditor/plugins/codesnippet/lang/cs.js | 13 + .../ckeditor/plugins/codesnippet/lang/da.js | 13 + .../plugins/codesnippet/lang/de-ch.js | 13 + .../ckeditor/plugins/codesnippet/lang/de.js | 13 + .../ckeditor/plugins/codesnippet/lang/el.js | 13 + .../plugins/codesnippet/lang/en-au.js | 13 + .../plugins/codesnippet/lang/en-gb.js | 13 + .../ckeditor/plugins/codesnippet/lang/en.js | 13 + .../ckeditor/plugins/codesnippet/lang/eo.js | 13 + .../plugins/codesnippet/lang/es-mx.js | 13 + .../ckeditor/plugins/codesnippet/lang/es.js | 13 + .../ckeditor/plugins/codesnippet/lang/et.js | 13 + .../ckeditor/plugins/codesnippet/lang/eu.js | 13 + .../ckeditor/plugins/codesnippet/lang/fa.js | 13 + .../ckeditor/plugins/codesnippet/lang/fi.js | 13 + .../plugins/codesnippet/lang/fr-ca.js | 13 + .../ckeditor/plugins/codesnippet/lang/fr.js | 13 + .../ckeditor/plugins/codesnippet/lang/gl.js | 13 + .../ckeditor/plugins/codesnippet/lang/he.js | 13 + .../ckeditor/plugins/codesnippet/lang/hr.js | 13 + .../ckeditor/plugins/codesnippet/lang/hu.js | 13 + .../ckeditor/plugins/codesnippet/lang/id.js | 13 + .../ckeditor/plugins/codesnippet/lang/it.js | 13 + .../ckeditor/plugins/codesnippet/lang/ja.js | 13 + .../ckeditor/plugins/codesnippet/lang/km.js | 13 + .../ckeditor/plugins/codesnippet/lang/ko.js | 13 + .../ckeditor/plugins/codesnippet/lang/ku.js | 13 + .../ckeditor/plugins/codesnippet/lang/lt.js | 13 + .../ckeditor/plugins/codesnippet/lang/lv.js | 13 + .../ckeditor/plugins/codesnippet/lang/nb.js | 13 + .../ckeditor/plugins/codesnippet/lang/nl.js | 13 + .../ckeditor/plugins/codesnippet/lang/no.js | 13 + .../ckeditor/plugins/codesnippet/lang/oc.js | 13 + .../ckeditor/plugins/codesnippet/lang/pl.js | 13 + .../plugins/codesnippet/lang/pt-br.js | 13 + .../ckeditor/plugins/codesnippet/lang/pt.js | 13 + .../ckeditor/plugins/codesnippet/lang/ro.js | 13 + .../ckeditor/plugins/codesnippet/lang/ru.js | 13 + .../ckeditor/plugins/codesnippet/lang/sk.js | 13 + .../ckeditor/plugins/codesnippet/lang/sl.js | 13 + .../ckeditor/plugins/codesnippet/lang/sq.js | 13 + .../plugins/codesnippet/lang/sr-latn.js | 13 + .../ckeditor/plugins/codesnippet/lang/sr.js | 13 + .../ckeditor/plugins/codesnippet/lang/sv.js | 13 + .../ckeditor/plugins/codesnippet/lang/th.js | 13 + .../ckeditor/plugins/codesnippet/lang/tr.js | 13 + .../ckeditor/plugins/codesnippet/lang/tt.js | 13 + .../ckeditor/plugins/codesnippet/lang/ug.js | 13 + .../ckeditor/plugins/codesnippet/lang/uk.js | 13 + .../ckeditor/plugins/codesnippet/lang/vi.js | 13 + .../plugins/codesnippet/lang/zh-cn.js | 13 + .../ckeditor/plugins/codesnippet/lang/zh.js | 13 + .../codesnippet/lib/highlight/CHANGES.md | 827 ++++++++++++++++++ .../plugins/codesnippet/lib/highlight/LICENSE | 24 + .../codesnippet/lib/highlight/README.md | 167 ++++ .../codesnippet/lib/highlight/README.ru.md | 171 ++++ .../lib/highlight/highlight.pack.js | 2 + .../codesnippet/lib/highlight/styles/arta.css | 160 ++++ .../lib/highlight/styles/ascetic.css | 50 ++ .../highlight/styles/atelier-dune.dark.css | 93 ++ .../highlight/styles/atelier-dune.light.css | 93 ++ .../highlight/styles/atelier-forest.dark.css | 93 ++ .../highlight/styles/atelier-forest.light.css | 93 ++ .../highlight/styles/atelier-heath.dark.css | 93 ++ .../highlight/styles/atelier-heath.light.css | 93 ++ .../styles/atelier-lakeside.dark.css | 93 ++ .../styles/atelier-lakeside.light.css | 93 ++ .../highlight/styles/atelier-seaside.dark.css | 93 ++ .../styles/atelier-seaside.light.css | 93 ++ .../lib/highlight/styles/brown_paper.css | 105 +++ .../lib/highlight/styles/brown_papersq.png | Bin 0 -> 18198 bytes .../codesnippet/lib/highlight/styles/dark.css | 105 +++ .../lib/highlight/styles/default.css | 153 ++++ .../lib/highlight/styles/docco.css | 132 +++ .../codesnippet/lib/highlight/styles/far.css | 113 +++ .../lib/highlight/styles/foundation.css | 133 +++ .../lib/highlight/styles/github.css | 125 +++ .../lib/highlight/styles/googlecode.css | 147 ++++ .../codesnippet/lib/highlight/styles/idea.css | 122 +++ .../lib/highlight/styles/ir_black.css | 105 +++ .../lib/highlight/styles/magula.css | 123 +++ .../lib/highlight/styles/mono-blue.css | 62 ++ .../lib/highlight/styles/monokai.css | 127 +++ .../lib/highlight/styles/monokai_sublime.css | 149 ++++ .../lib/highlight/styles/obsidian.css | 154 ++++ .../lib/highlight/styles/paraiso.dark.css | 93 ++ .../lib/highlight/styles/paraiso.light.css | 93 ++ .../lib/highlight/styles/pojoaque.css | 106 +++ .../lib/highlight/styles/pojoaque.jpg | Bin 0 -> 1186 bytes .../lib/highlight/styles/railscasts.css | 182 ++++ .../lib/highlight/styles/rainbow.css | 112 +++ .../lib/highlight/styles/school_book.css | 113 +++ .../lib/highlight/styles/school_book.png | Bin 0 -> 486 bytes .../lib/highlight/styles/solarized_dark.css | 107 +++ .../lib/highlight/styles/solarized_light.css | 107 +++ .../lib/highlight/styles/sunburst.css | 160 ++++ .../highlight/styles/tomorrow-night-blue.css | 93 ++ .../styles/tomorrow-night-bright.css | 92 ++ .../styles/tomorrow-night-eighties.css | 92 ++ .../lib/highlight/styles/tomorrow-night.css | 93 ++ .../lib/highlight/styles/tomorrow.css | 90 ++ .../codesnippet/lib/highlight/styles/vs.css | 89 ++ .../lib/highlight/styles/xcode.css | 158 ++++ .../lib/highlight/styles/zenburn.css | 117 +++ .../ckeditor/plugins/codesnippet/plugin.js | 484 ++++++++++ .../codesnippet/samples/codesnippet.html | 239 +++++ 116 files changed, 7625 insertions(+), 8 deletions(-) create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/dialogs/codesnippet.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/icons/codesnippet.png create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/icons/hidpi/codesnippet.png create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ar.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/az.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/bg.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ca.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/cs.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/da.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de-ch.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/el.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-au.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-gb.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eo.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es-mx.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/et.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eu.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fa.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fi.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr-ca.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/gl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/he.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hu.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/id.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/it.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ja.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/km.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ko.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ku.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lt.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lv.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nb.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/no.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/oc.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt-br.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ro.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ru.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sk.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sq.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr-latn.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sv.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/th.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tt.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ug.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/uk.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/vi.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh-cn.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/CHANGES.md create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/LICENSE create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.md create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.ru.md create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/arta.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ascetic.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_paper.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_papersq.png create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/default.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/docco.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/far.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/foundation.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/github.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/googlecode.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/idea.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ir_black.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/magula.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/mono-blue.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai_sublime.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.jpg create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/railscasts.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/rainbow.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.png create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_dark.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_light.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/sunburst.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-blue.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-bright.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-eighties.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/vs.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/xcode.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/zenburn.css create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/plugin.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/samples/codesnippet.html diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 191e826b..92e06cfd 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -75,7 +75,7 @@ def is_year(file_name): if year >= 2000 and year <= 2100: return True return False - except: + except Exception as e: return False @@ -100,7 +100,7 @@ def archive_current_gsoc_files(current_year): try: repo.create_file(f'{current_year}/{file.path}', f'Archive GSoC {current_year} files', file.decoded_content) - except Exception as e1: + except Exception as e: repo.update_file(f'{current_year}/{file.path}', f'Archive GSoC {current_year} files', file.content, diff --git a/gsoc/models.py b/gsoc/models.py index 4240ceb2..a66be31d 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1041,10 +1041,10 @@ def decrease_blog_counter(sender, instance, **kwargs): # Clean lead_in HTML when new Article is created -@receiver(models.signals.post_save, sender=Article) -def clean_html(sender, instance, **kwargs): - if instance.is_unclean(): - instance.clean_article_html() +# @receiver(models.signals.post_save, sender=Article) +# def clean_html(sender, instance, **kwargs): +# if instance.is_unclean(): +# instance.clean_article_html() # Add ArticleReveiw object when new Article is created diff --git a/gsoc/settings.py b/gsoc/settings.py index 35fc0e37..baabe69a 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -341,7 +341,7 @@ def gettext(s): return s CKEDITOR_SETTINGS = { 'disableNativeSpellChecker': False, 'language': '{{ language }}', - 'extraPlugins': 'button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube', + 'extraPlugins': 'button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube,codesnippet', 'toolbar': [ {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']}, @@ -357,7 +357,7 @@ def gettext(s): return s 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']}, {'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']}, {'name': 'insert', 'items': ['Table', 'HorizontalRule', - 'Smiley', 'SpecialChar', 'PageBreak', 'Image', 'Youtube']}, + 'Smiley', 'SpecialChar', 'PageBreak', 'Image', 'Youtube', 'CodeSnippet']}, '/', {'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']}, {'name': 'colors', 'items': ['TextColor', 'BGColor']}, diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/dialogs/codesnippet.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/dialogs/codesnippet.js new file mode 100644 index 00000000..516295f6 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/dialogs/codesnippet.js @@ -0,0 +1,83 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +'use strict'; + +( function() { + CKEDITOR.dialog.add( 'codeSnippet', function( editor ) { + var snippetLangs = editor._.codesnippet.langs, + lang = editor.lang.codesnippet, + clientHeight = document.documentElement.clientHeight, + langSelectItems = [], + snippetLangId; + + langSelectItems.push( [ editor.lang.common.notSet, '' ] ); + + for ( snippetLangId in snippetLangs ) + langSelectItems.push( [ snippetLangs[ snippetLangId ], snippetLangId ] ); + + // Size adjustments. + var size = CKEDITOR.document.getWindow().getViewPaneSize(), + // Make it maximum 800px wide, but still fully visible in the viewport. + width = Math.min( size.width - 70, 800 ), + // Make it use 2/3 of the viewport height. + height = size.height / 1.5; + + // Low resolution settings. + if ( clientHeight < 650 ) { + height = clientHeight - 220; + } + + return { + title: lang.title, + minHeight: 200, + resizable: CKEDITOR.DIALOG_RESIZE_NONE, + contents: [ + { + id: 'info', + elements: [ + { + id: 'lang', + type: 'select', + label: lang.language, + items: langSelectItems, + setup: function( widget ) { + if ( widget.ready && widget.data.lang ) + this.setValue( widget.data.lang ); + + // The only way to have an empty select value in Firefox is + // to set a negative selectedIndex. + if ( CKEDITOR.env.gecko && ( !widget.data.lang || !widget.ready ) ) + this.getInputElement().$.selectedIndex = -1; + }, + commit: function( widget ) { + widget.setData( 'lang', this.getValue() ); + } + }, + { + id: 'code', + type: 'textarea', + label: lang.codeContents, + setup: function( widget ) { + this.setValue( widget.data.code ); + }, + commit: function( widget ) { + widget.setData( 'code', this.getValue() ); + }, + required: true, + validate: CKEDITOR.dialog.validate.notEmpty( lang.emptySnippetError ), + inputStyle: 'cursor:auto;' + + 'width:' + width + 'px;' + + 'height:' + height + 'px;' + + 'tab-size:4;' + + 'text-align:left;', + 'class': 'cke_source' + } + ] + } + ] + }; + } ); +}() ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/icons/codesnippet.png b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/icons/codesnippet.png new file mode 100644 index 0000000000000000000000000000000000000000..c71851067105640817b7a7360126b1127c60683e GIT binary patch literal 532 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CDO3=9p;3=BX21L>Cx45bDP46hOx7_4S6Fo@?*ia+WGRLhp+?e4#&2s zoSU2d*c3S$7o5_HRqr==aro7r_8s9X{g!wxbh7L7RMtGynR;9@qHO2g*>i6vM}~_2 zV`g4pSa(92p^u&a=h+iSSU=m&c(pUq;E~ckHr?#$T+>cgD=@^IOGw(AWq3?J>;IAi zmfv?Y2{3v3)rBR9$A0a8SYxN`Da6?DJ&wUaD6MTam&(EF^&4N`S@FL5PKW|$n%bm{ zS#1mf(^AzqBd4ugmtB-Ae&$W+rQIq#hY!3h>lQflXO6_<+pqLItR~hdcAEIIChUt5 zIXb!c-IiV4lN4SiZ@-=U;cVC8hYxnI`>nzm{rh`nLGJBif9u{eDxClP{P9PLWq-ua zx0D=moc2Kl7(%Kgt`Q|Ei6yC4$wjF^iowXh&`j6BT-VSt#L(Ev*ucufNZY{3%D_Nk hLU{#>hTQy=%(P0}8ZytGKLXUi;OXk;vd$@?2>?^$!l3{F literal 0 HcmV?d00001 diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/icons/hidpi/codesnippet.png b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/icons/hidpi/codesnippet.png new file mode 100644 index 0000000000000000000000000000000000000000..2de477f6b1345fd235c347deff599628924022d4 GIT binary patch literal 1046 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh?!xdN1Q+aGJ{c&& zUgGKN%Km_xiC2-KneQA20|T>^r;B4q#NngiwizOhBLD8qPTd)!*IwncVgD(kh-IQd zTur@GS-7G&1t%8%0ENt@KfxE>c}I{mvPi?mM^7oICq&uIGiu zhtJ-gG5=h*?|WLGnVH!FUl#@jmnl{Il}Yq8~aX z?r(NYIX&yzUtKu{BL)%OHEcSH?$YzP)_WDO{7d(BQBXQjAf&(5C+XU?T!CFSrt3bo zv>0hSIJ$&XDgU_|SDrAVNVDct)mI1AWe1;$0@utrQQVM;V zTAv>~_2NM!pAMH@@6~(1kNxzL?U*I>@QHKNU0=z^bD9m^f_}GgspwDK#jToF8}zyD zwE8^%B8S2mmxH=qm@73Isx8fUy2nfG%28{(6?*$$KYR6Im4h<-E54{Ls$4No_dKb7 zu!eoznl&+3Ufp{ARnKIt|6cZxY|h_b4$Y7}E?s6d_gsO+o$Eo?5!0hKgr!LayGWfpt`;FVTf1f=w-I;moXA`w4Hzpj?Q#!fvn8o?eTMr*T zJl~MB^q%GS?8?fYSve2xcv-CPl-s61`?y{qHF zUUuX63=!E|qg2;j%{mx$o7EuTPl{3H`7`-fnWZkzyy4GmbGfslti1f;oZZ3u-p+7z z(GPKaU%PUPA=|MT<_ll%{r=r;ao|^9iRhV0yH5SBlXndaIg!2g?};lh{{C;O+|;q)_S-Be8J3)S-UpQnKEyu1b6Vku{hZ>vWxHdSJ)CKj zAa&VWX_K&zE!VO4OsBW6-CCRMb8`D8J{5}%6MS4we3m)4Gu!3MLdW~(uig5pb6RVO z{bcq-UDHw}8Sa=Jv+=$%O_#y^@Zp(~XJYZfY z+I4br9s4^^1j{n-SX%thTl=)plgd8(D>c&ubLOd=0yBRdP`(kYX@0 zFf`LOG}JXP3NbXcGB&m{FwizI1d`Vo*Stm1kei>9nO2Eg!?GBWK%fQ&Pgg&ebxsLQ E0M3NQ?EnA( literal 0 HcmV?d00001 diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ar.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ar.js new file mode 100644 index 00000000..1f574f0e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ar.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ar', { + button: 'أدمج قصاصة الشيفرة', + codeContents: 'محتوى الشيفرة', + emptySnippetError: 'قصاصة الشيفرة لايمكن أن تكون فارغة.', + language: 'لغة', + title: 'قصاصة الشيفرة', + pathName: 'قصاصة الشيفرة' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/az.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/az.js new file mode 100644 index 00000000..e1003a9b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/az.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'az', { + button: 'Kodun parçasını əlavə et', + codeContents: 'Kod', + emptySnippetError: 'Kodun parçasını boş ola bilməz', + language: 'Programlaşdırma dili', + title: 'Kodun parçasını', + pathName: 'kodun parçasını' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/bg.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/bg.js new file mode 100644 index 00000000..883576eb --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/bg.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'bg', { + button: 'Въвеждане на блок с код', + codeContents: 'Съдържание на кода', + emptySnippetError: 'Блока с код не може да бъде празен.', + language: 'Език', + title: 'Блок с код', + pathName: 'блок с код' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ca.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ca.js new file mode 100644 index 00000000..ef8b4617 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ca.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ca', { + button: 'Insereix el fragment de codi', + codeContents: 'Contingut del codi', + emptySnippetError: 'El fragment de codi no pot estar buit.', + language: 'Idioma', + title: 'Fragment de codi', + pathName: 'fragment de codi' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/cs.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/cs.js new file mode 100644 index 00000000..4de5c620 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/cs.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'cs', { + button: 'Vložit úryvek kódu', + codeContents: 'Obsah kódu', + emptySnippetError: 'Úryvek kódu nemůže být prázdný.', + language: 'Jazyk', + title: 'Úryvek kódu', + pathName: 'úryvek kódu' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/da.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/da.js new file mode 100644 index 00000000..684da94f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/da.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'da', { + button: 'Indsæt kodestykket her', + codeContents: 'Koden', + emptySnippetError: 'Kodestykket kan ikke være tomt.', + language: 'Sprog', + title: 'Kodestykke', + pathName: 'kodestykke' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de-ch.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de-ch.js new file mode 100644 index 00000000..050688f6 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de-ch.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'de-ch', { + button: 'Codeschnipsel einfügen', + codeContents: 'Codeinhalt', + emptySnippetError: 'Ein Codeschnipsel darf nicht leer sein.', + language: 'Sprache', + title: 'Codeschnipsel', + pathName: 'Codeschnipsel' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de.js new file mode 100644 index 00000000..335611a1 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/de.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'de', { + button: 'Codeschnipsel einfügen', + codeContents: 'Codeinhalt', + emptySnippetError: 'Ein Codeschnipsel darf nicht leer sein.', + language: 'Sprache', + title: 'Codeschnipsel', + pathName: 'Codeschnipsel' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/el.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/el.js new file mode 100644 index 00000000..65b9b10e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/el.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'el', { + button: 'Εισαγωγή Αποσπάσματος Κώδικα', + codeContents: 'Περιεχόμενο κώδικα', + emptySnippetError: 'Δεν γίνεται να είναι κενά τα αποσπάσματα κώδικα.', + language: 'Γλώσσα', + title: 'Απόσπασμα κώδικα', + pathName: 'απόσπασμα κώδικα' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-au.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-au.js new file mode 100644 index 00000000..72ed7468 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-au.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'en-au', { + button: 'Insert Code Snippet', + codeContents: 'Code content', + emptySnippetError: 'A code snippet cannot be empty.', + language: 'Language', + title: 'Code snippet', + pathName: 'code snippet' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-gb.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-gb.js new file mode 100644 index 00000000..272d21c7 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en-gb.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'en-gb', { + button: 'Insert Code Snippet', + codeContents: 'Code content', + emptySnippetError: 'A code snippet cannot be empty.', + language: 'Language', + title: 'Code snippet', + pathName: 'code snippet' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en.js new file mode 100644 index 00000000..34f1ec3e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/en.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'en', { + button: 'Insert Code Snippet', + codeContents: 'Code content', + emptySnippetError: 'A code snippet cannot be empty.', + language: 'Language', + title: 'Code snippet', + pathName: 'code snippet' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eo.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eo.js new file mode 100644 index 00000000..7528063e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eo.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'eo', { + button: 'Enmeti kodaĵeron', + codeContents: 'Kodenhavo', + emptySnippetError: 'Kodaĵero ne povas esti malplena.', + language: 'Lingvo', + title: 'Kodaĵero', + pathName: 'kodaĵero' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es-mx.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es-mx.js new file mode 100644 index 00000000..b0db9c2b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es-mx.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'es-mx', { + button: 'Insertar fragmento de código', + codeContents: 'Contenido del código', + emptySnippetError: 'Un fragmento de código no puede estar vacio.', + language: 'Idioma', + title: 'Fragmento de código', + pathName: 'fragmento de código' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es.js new file mode 100644 index 00000000..235df92a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/es.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'es', { + button: 'Insertar fragmento de código', + codeContents: 'Contenido del código', + emptySnippetError: 'Un fragmento de código no puede estar vacío.', + language: 'Lenguaje', + title: 'Fragmento de código', + pathName: 'fragmento de código' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/et.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/et.js new file mode 100644 index 00000000..a77c2f48 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/et.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'et', { + button: 'Koodilõigu sisestamine', + codeContents: 'Koodi sisu', + emptySnippetError: 'A code snippet cannot be empty.', // MISSING + language: 'Keel', + title: 'Koodi vidin', + pathName: 'code snippet' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eu.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eu.js new file mode 100644 index 00000000..98b31e2f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/eu.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'eu', { + button: 'Txertatu kode zatia', + codeContents: 'Kode edukia', + emptySnippetError: 'Kode zatiak ezin du hutsik egon.', + language: 'Lengoaia', + title: 'Kode zatia', + pathName: 'kode zatia' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fa.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fa.js new file mode 100644 index 00000000..1a4c4cae --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fa.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'fa', { + button: 'قرار دادن کد قطعه', + codeContents: 'محتوای کد', + emptySnippetError: 'کد نمی تواند خالی باشد.', + language: 'زبان', + title: 'کد قطعه', + pathName: 'کد قطعه' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fi.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fi.js new file mode 100644 index 00000000..5dd20033 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fi.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'fi', { + button: 'Lisää koodileike', + codeContents: 'Koodisisältö', + emptySnippetError: 'Koodileike ei voi olla tyhjä.', + language: 'Kieli', + title: 'Koodileike', + pathName: 'koodileike' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr-ca.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr-ca.js new file mode 100644 index 00000000..ab067800 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr-ca.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'fr-ca', { + button: 'Insérer du code', + codeContents: 'Code content', // MISSING + emptySnippetError: 'A code snippet cannot be empty.', // MISSING + language: 'Language', // MISSING + title: 'Code snippet', // MISSING + pathName: 'code snippet' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr.js new file mode 100644 index 00000000..54323bee --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/fr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'fr', { + button: 'Insérer un extrait de code', + codeContents: 'Code', + emptySnippetError: 'Un extrait de code ne peut pas être vide.', + language: 'Langue', + title: 'Extrait de code', + pathName: 'extrait de code' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/gl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/gl.js new file mode 100644 index 00000000..bf79b5ea --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/gl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'gl', { + button: 'Inserir fragmento de código', + codeContents: 'Contido do código', + emptySnippetError: 'Un fragmento de código non pode estar baleiro.', + language: 'Linguaxe', + title: 'Fragmento de código', + pathName: 'fragmento de código' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/he.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/he.js new file mode 100644 index 00000000..c0efa956 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/he.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'he', { + button: 'הכנס קטע קוד', + codeContents: 'תוכן קוד', + emptySnippetError: 'קטע קוד לא יכול להיות ריק.', + language: 'שפה', + title: 'קטע קוד', + pathName: 'code snippet' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hr.js new file mode 100644 index 00000000..31256764 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'hr', { + button: 'Ubaci isječak kôda', + codeContents: 'Sadržaj kôda', + emptySnippetError: 'Isječak kôda ne može biti prazan.', + language: 'Jezik', + title: 'Isječak kôda', + pathName: 'isječak kôda' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hu.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hu.js new file mode 100644 index 00000000..224abe03 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/hu.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'hu', { + button: 'Illeszd be a kódtöredéket', + codeContents: 'Kód tartalom', + emptySnippetError: 'A kódtöredék nem lehet üres.', + language: 'Nyelv', + title: 'Kódtöredék', + pathName: 'kódtöredék' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/id.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/id.js new file mode 100644 index 00000000..1633785e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/id.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'id', { + button: 'Masukkan potongan kode', + codeContents: 'Konten kode', + emptySnippetError: 'Potongan kode tidak boleh kosong', + language: 'Bahasa', + title: 'Potongan kode', + pathName: 'potongan kode' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/it.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/it.js new file mode 100644 index 00000000..b1754a55 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/it.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'it', { + button: 'Inserisci frammento di codice', + codeContents: 'Contenuto del codice', + emptySnippetError: 'Un frammento di codice non può essere vuoto.', + language: 'Lingua', + title: 'Frammento di codice', + pathName: 'frammento di codice' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ja.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ja.js new file mode 100644 index 00000000..025e3e32 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ja.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ja', { + button: 'コードスニペットを挿入', + codeContents: 'コード内容', + emptySnippetError: 'コードスニペットを入力してください。', + language: '言語', + title: 'コードスニペット', + pathName: 'コードスニペット' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/km.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/km.js new file mode 100644 index 00000000..14e6167a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/km.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'km', { + button: 'Insert Code Snippet', // MISSING + codeContents: 'មាតិកាកូដ', + emptySnippetError: 'A code snippet cannot be empty.', // MISSING + language: 'ភាសា', + title: 'Code snippet', // MISSING + pathName: 'code snippet' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ko.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ko.js new file mode 100644 index 00000000..58628b26 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ko.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ko', { + button: '코드 스니펫 삽입', + codeContents: '코드 본문', + emptySnippetError: '코드 스니펫은 빈칸일 수 없습니다.', + language: '언어', + title: '코드 스니펫', + pathName: '코드 스니펫' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ku.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ku.js new file mode 100644 index 00000000..9e02c0a8 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ku.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ku', { + button: 'تێخستنی تیتکی کۆد', + codeContents: 'ناوەڕۆکی کۆد', + emptySnippetError: 'تیتکی کۆد نابێت بەتاڵ بێت.', + language: 'زمان', + title: 'تیتکی کۆد', + pathName: 'تیتکی کۆد' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lt.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lt.js new file mode 100644 index 00000000..818a3ba1 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lt.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'lt', { + button: 'Įterpkite kodo gabaliuką', + codeContents: 'Kodo turinys', + emptySnippetError: 'Kodo fragmentas negali būti tusčias.', + language: 'Kalba', + title: 'Kodo fragmentas', + pathName: 'kodo fragmentas' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lv.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lv.js new file mode 100644 index 00000000..8515409d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/lv.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'lv', { + button: 'Ievietot koda fragmentu', + codeContents: 'Koda saturs', + emptySnippetError: 'Koda fragments nevar būt tukšs.', + language: 'Valoda', + title: 'Koda fragments', + pathName: 'koda fragments' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nb.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nb.js new file mode 100644 index 00000000..6234453b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nb.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'nb', { + button: 'Sett inn kodesnutt', + codeContents: 'Kodeinnhold', + emptySnippetError: 'En kodesnutt kan ikke være tom.', + language: 'Språk', + title: 'Kodesnutt', + pathName: 'kodesnutt' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nl.js new file mode 100644 index 00000000..68d08497 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/nl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'nl', { + button: 'Stuk code invoegen', + codeContents: 'Code', + emptySnippetError: 'Een stuk code kan niet leeg zijn.', + language: 'Taal', + title: 'Stuk code', + pathName: 'stuk code' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/no.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/no.js new file mode 100644 index 00000000..72c6bad3 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/no.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'no', { + button: 'Sett inn kodesnutt', + codeContents: 'Code content', // MISSING + emptySnippetError: 'A code snippet cannot be empty.', // MISSING + language: 'Language', // MISSING + title: 'Code snippet', // MISSING + pathName: 'code snippet' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/oc.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/oc.js new file mode 100644 index 00000000..cf7b211d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/oc.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'oc', { + button: 'Inserir un extrait de còdi', + codeContents: 'Còdi', + emptySnippetError: 'Un extrait de còdi pòt pas èsser void.', + language: 'Lenga', + title: 'Extrait de còdi', + pathName: 'extrait de còdi' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pl.js new file mode 100644 index 00000000..a0045d87 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'pl', { + button: 'Wstaw fragment kodu', + codeContents: 'Treść kodu', + emptySnippetError: 'Kod nie może być pusty.', + language: 'Język', + title: 'Fragment kodu', + pathName: 'fragment kodu' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt-br.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt-br.js new file mode 100644 index 00000000..038b10bd --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt-br.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'pt-br', { + button: 'Inserir fragmento de código', + codeContents: 'Conteúdo do código', + emptySnippetError: 'Um fragmento de código não pode ser vazio', + language: 'Idioma', + title: 'Fragmento de código', + pathName: 'fragmento de código' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt.js new file mode 100644 index 00000000..9ef47461 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/pt.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'pt', { + button: 'Inserir fragmento de código', + codeContents: 'Conteúdo do código', + emptySnippetError: 'A code snippet cannot be empty.', // MISSING + language: 'Idioma', + title: 'Segmento de código', + pathName: 'Fragmento de código' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ro.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ro.js new file mode 100644 index 00000000..a436a33a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ro.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ro', { + button: 'Adaugă segment de cod', + codeContents: 'Conținutul codului', + emptySnippetError: 'Un segment de cod nu poate fi gol.', + language: 'Limba', + title: 'Segment de cod', + pathName: 'segment de cod' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ru.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ru.js new file mode 100644 index 00000000..0ce9962f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ru.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ru', { + button: 'Вставить сниппет', + codeContents: 'Содержимое кода', + emptySnippetError: 'Сниппет не может быть пустым', + language: 'Язык', + title: 'Сниппет', + pathName: 'сниппет' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sk.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sk.js new file mode 100644 index 00000000..5e8a65d8 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sk.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'sk', { + button: 'Vložte ukážku programového kódu', + codeContents: 'Obsah kódu', + emptySnippetError: 'Ukážka kódu nesmie byť prázdna.', + language: 'Jazyk', + title: 'Ukážka programového kódu', + pathName: 'ukážka programového kódu' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sl.js new file mode 100644 index 00000000..c6d9bcea --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'sl', { + button: 'Vstavi odsek kode', + codeContents: 'Vsebina kode', + emptySnippetError: 'Odsek kode ne more biti prazen.', + language: 'Jezik', + title: 'Odsek kode', + pathName: 'odsek kode' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sq.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sq.js new file mode 100644 index 00000000..da6bfe5a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sq.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'sq', { + button: 'Shto kod copëze', + codeContents: 'Përmbajtja e kodit', + emptySnippetError: 'Copëza e kodit nuk mund të jetë e zbrazët.', + language: 'Gjuha', + title: 'Copëza e kodit', + pathName: 'copëza e kodit' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr-latn.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr-latn.js new file mode 100644 index 00000000..dceacaf2 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr-latn.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'sr-latn', { + button: 'Nalepi delić koda', + codeContents: 'Sadržaj koda', + emptySnippetError: 'Delić koda ne može biti prazan', + language: 'Jezik', + title: 'Delić koda', + pathName: 'Delić koda' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr.js new file mode 100644 index 00000000..9bfc702a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'sr', { + button: 'Налепи делић кода', + codeContents: 'Садржај кода', + emptySnippetError: 'Делић кода не може бити празан', + language: 'Језик', + title: 'Делић кода', + pathName: 'Делић кода' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sv.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sv.js new file mode 100644 index 00000000..07e54a0c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/sv.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'sv', { + button: 'Infoga kodsnutt', + codeContents: 'Kodinnehålll', + emptySnippetError: 'Innehåll krävs för kodsnutt', + language: 'Språk', + title: 'Kodsnutt', + pathName: 'kodsnutt' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/th.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/th.js new file mode 100644 index 00000000..ef92ad35 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/th.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'th', { + button: 'แทรกชิ้นส่วนของรหัสหรือโค้ด', + codeContents: 'Code content', // MISSING + emptySnippetError: 'A code snippet cannot be empty.', // MISSING + language: 'Language', // MISSING + title: 'Code snippet', // MISSING + pathName: 'code snippet' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tr.js new file mode 100644 index 00000000..79e44d0c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'tr', { + button: 'Kod parçacığı ekle', + codeContents: 'Kod', + emptySnippetError: 'Kod parçacığı boş bırakılamaz', + language: 'Dil', + title: 'Kod parçacığı', + pathName: 'kod parçacığı' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tt.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tt.js new file mode 100644 index 00000000..2f9e055e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/tt.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'tt', { + button: 'Код өзеген өстәү', + codeContents: 'Код эчтәлеге', + emptySnippetError: 'Код өзеге буш булмаска тиеш.', + language: 'Тел', + title: 'Код өзеге', + pathName: 'код өзеге' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ug.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ug.js new file mode 100644 index 00000000..e2ca7ce8 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/ug.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'ug', { + button: 'كود پارچىسى قىستۇرۇش', + codeContents: 'كود مەزمۇنى', + emptySnippetError: 'كود پارچىسى بوش قالمايدۇ', + language: 'تىل', + title: 'كود پارچىسى', + pathName: 'كود پارچىسى' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/uk.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/uk.js new file mode 100644 index 00000000..9ee4f394 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/uk.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'uk', { + button: 'Вставити фрагмент коду', + codeContents: 'Код', + emptySnippetError: 'Фрагмент коду не можи бути порожнім.', + language: 'Мова', + title: 'Фрагмент коду', + pathName: 'фрагмент коду' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/vi.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/vi.js new file mode 100644 index 00000000..9ea21539 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/vi.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'vi', { + button: 'Chèn đoạn mã', + codeContents: 'Nội dung mã', + emptySnippetError: 'Một đoạn mã không thể để trống.', + language: 'Ngôn ngữ', + title: 'Đoạn mã', + pathName: 'mã dính' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh-cn.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh-cn.js new file mode 100644 index 00000000..1aaed204 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh-cn.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'zh-cn', { + button: '插入代码段', + codeContents: '代码内容', + emptySnippetError: '插入的代码不能为空。', + language: '代码语言', + title: '代码段', + pathName: '代码段' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh.js new file mode 100644 index 00000000..62513ea9 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lang/zh.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'codesnippet', 'zh', { + button: '插入程式碼片段', + codeContents: '程式碼內容', + emptySnippetError: '程式碼片段不可為空白。', + language: '語言', + title: '程式碼片段', + pathName: '程式碼片段' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/CHANGES.md b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/CHANGES.md new file mode 100644 index 00000000..f878062b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/CHANGES.md @@ -0,0 +1,827 @@ +## Version 8.0 beta + +This new major release is quite a big overhaul bringing both new features and +some backwards incompatible changes. However, chances are that the majority of +users won't be affected by the latter: the basic scenario described in the +README is left intact. + +Here's what did change in an incompatible way: + +- We're now prefixing all classes located in [CSS classes reference][cr] with + `hljs-`, by default, because some class names would collide with other + people's stylesheets. If you were using an older version, you might still want + the previous behavior, but still want to upgrade. To suppress this new + behavior, you would initialize like so: + + ```html + + ``` + +- `tabReplace` and `useBR` that were used in different places are also unified + into the global options object and are to be set using `configure(options)`. + This function is documented in our [API docs][]. Also note that these + parameters are gone from `highlightBlock` and `fixMarkup` which are now also + rely on `configure`. + +- We removed public-facing (though undocumented) object `hljs.LANGUAGES` which + was used to register languages with the library in favor of two new methods: + `registerLanguage` and `getLanguage`. Both are documented in our [API docs][]. + +- Result returned from `highlight` and `highlightAuto` no longer contains two + separate attributes contributing to relevance score, `relevance` and + `keyword_count`. They are now unified in `relevance`. + +Another technically compatible change that nonetheless might need attention: + +- The structure of the NPM package was refactored, so if you had installed it + locally, you'll have to update your paths. The usual `require('highlight.js')` + works as before. This is contributed by [Dmitry Smolin][]. + +New features: + +- Languages now can be recognized by multiple names like "js" for JavaScript or + "html" for, well, HTML (which earlier insisted on calling it "xml"). These + aliases can be specified in the class attribute of the code container in your + HTML as well as in various API calls. For now there are only a few very common + aliases but we'll expand it in the future. All of them are listed in the + [class reference][]. + +- Language detection can now be restricted to a subset of languages relevant in + a given context — a web page or even a single highlighting call. This is + especially useful for node.js build that includes all the known languages. + Another example is a StackOverflow-style site where users specify languages + as tags rather than in the markdown-formatted code snippets. This is + documented in the [API reference][] (see methods `highlightAuto` and + `configure`). + +- Language definition syntax streamlined with [variants][] and + [beginKeywords][]. + +New languages and styles: + +- *Oxygene* by [Carlo Kok][] +- *Mathematica* by [Daniel Kvasnička][] +- *Autohotkey* by [Seongwon Lee][] +- *Atelier* family of styles in 10 variants by [Bram de Haan][] +- *Paraíso* styles by [Jan T. Sott][] + +Miscelleanous improvements: + +- Highlighting `=>` prompts in Clojure. +- [Jeremy Hull][] fixed a lot of styles for consistency. +- Finally, highlighting PHP and HTML [mixed in peculiar ways][php-html]. +- Objective C and C# now properly highlight titles in method definition. +- Big overhaul of relevance counting for a number of languages. Please do report + bugs about mis-detection of non-trivial code snippets! + +[cr]: http://highlightjs.readthedocs.org/en/latest/css-classes-reference.html +[api docs]: http://highlightjs.readthedocs.org/en/latest/api.html +[variants]: https://groups.google.com/d/topic/highlightjs/VoGC9-1p5vk/discussion +[beginKeywords]: https://github.com/isagalaev/highlight.js/commit/6c7fdea002eb3949577a85b3f7930137c7c3038d +[php-html]: https://twitter.com/highlightjs/status/408890903017689088 + +[Carlo Kok]: https://github.com/carlokok +[Bram de Haan]: https://github.com/atelierbram +[Daniel Kvasnička]: https://github.com/dkvasnicka +[Dmitry Smolin]: https://github.com/dimsmol +[Jeremy Hull]: https://github.com/sourrust +[Seongwon Lee]: https://github.com/dlimpid +[Jan T. Sott]: https://github.com/idleberg + + +## Version 7.5 + +A catch-up release dealing with some of the accumulated contributions. This one +is probably will be the last before the 8.0 which will be slightly backwards +incompatible regarding some advanced use-cases. + +One outstanding change in this version is the addition of 6 languages to the +[hosted script][d]: Markdown, ObjectiveC, CoffeeScript, Apache, Nginx and +Makefile. It now weighs about 6K more but we're going to keep it under 30K. + +New languages: + +- OCaml by [Mehdi Dogguy][mehdid] and [Nicolas Braud-Santoni][nbraud] +- [LiveCode Server][lcs] by [Ralf Bitter][revig] +- Scilab by [Sylvestre Ledru][sylvestre] +- basic support for Makefile by [Ivan Sagalaev][isagalaev] + +Improvements: + +- Ruby's got support for characters like `?A`, `?1`, `?\012` etc. and `%r{..}` + regexps. +- Clojure now allows a function call in the beginning of s-expressions + `(($filter "myCount") (arr 1 2 3 4 5))`. +- Haskell's got new keywords and now recognizes more things like pragmas, + preprocessors, modules, containers, FFIs etc. Thanks to [Zena Treep][treep] + for the implementation and to [Jeremy Hull][sourrust] for guiding it. +- Miscelleanous fixes in PHP, Brainfuck, SCSS, Asciidoc, CMake, Python and F#. + +[mehdid]: https://github.com/mehdid +[nbraud]: https://github.com/nbraud +[revig]: https://github.com/revig +[lcs]: http://livecode.com/developers/guides/server/ +[sylvestre]: https://github.com/sylvestre +[isagalaev]: https://github.com/isagalaev +[treep]: https://github.com/treep +[sourrust]: https://github.com/sourrust +[d]: http://highlightjs.org/download/ + + +## New core developers + +The latest long period of almost complete inactivity in the project coincided +with growing interest to it led to a decision that now seems completely obvious: +we need more core developers. + +So without further ado let me welcome to the core team two long-time +contributors: [Jeremy Hull][] and [Oleg +Efimov][]. + +Hope now we'll be able to work through stuff faster! + +P.S. The historical commit is [here][1] for the record. + +[Jeremy Hull]: https://github.com/sourrust +[Oleg Efimov]: https://github.com/sannis +[1]: https://github.com/isagalaev/highlight.js/commit/f3056941bda56d2b72276b97bc0dd5f230f2473f + + +## Version 7.4 + +This long overdue version is a snapshot of the current source tree with all the +changes that happened during the past year. Sorry for taking so long! + +Along with the changes in code highlight.js has finally got its new home at +, moving from its craddle on Software Maniacs which it +outgrew a long time ago. Be sure to report any bugs about the site to +. + +On to what's new… + +New languages: + +- Handlebars templates by [Robin Ward][] +- Oracle Rules Language by [Jason Jacobson][] +- F# by [Joans Follesø][] +- AsciiDoc and Haml by [Dan Allen][] +- Lasso by [Eric Knibbe][] +- SCSS by [Kurt Emch][] +- VB.NET by [Poren Chiang][] +- Mizar by [Kelley van Evert][] + +[Robin Ward]: https://github.com/eviltrout +[Jason Jacobson]: https://github.com/jayce7 +[Joans Follesø]: https://github.com/follesoe +[Dan Allen]: https://github.com/mojavelinux +[Eric Knibbe]: https://github.com/EricFromCanada +[Kurt Emch]: https://github.com/kemch +[Poren Chiang]: https://github.com/rschiang +[Kelley van Evert]: https://github.com/kelleyvanevert + +New style themes: + +- Monokai Sublime by [noformnocontent][] +- Railscasts by [Damien White][] +- Obsidian by [Alexander Marenin][] +- Docco by [Simon Madine][] +- Mono Blue by [Ivan Sagalaev][] (uses a single color hue for everything) +- Foundation by [Dan Allen][] + +[noformnocontent]: http://nn.mit-license.org/ +[Damien White]: https://github.com/visoft +[Alexander Marenin]: https://github.com/ioncreature +[Simon Madine]: https://github.com/thingsinjars +[Ivan Sagalaev]: https://github.com/isagalaev + +Other notable changes: + +- Corrected many corner cases in CSS. +- Dropped Python 2 version of the build tool. +- Implemented building for the AMD format. +- Updated Rust keywords (thanks to [Dmitry Medvinsky][]). +- Literal regexes can now be used in language definitions. +- CoffeeScript highlighting is now significantly more robust and rich due to + input from [Cédric Néhémie][]. + +[Dmitry Medvinsky]: https://github.com/dmedvinsky +[Cédric Néhémie]: https://github.com/abe33 + + +## Version 7.3 + +- Since this version highlight.js no longer works in IE version 8 and older. + It's made it possible to reduce the library size and dramatically improve code + readability and made it easier to maintain. Time to go forward! + +- New languages: AppleScript (by [Nathan Grigg][ng] and [Dr. Drang][dd]) and + Brainfuck (by [Evgeny Stepanischev][bolk]). + +- Improvements to existing languages: + + - interpreter prompt in Python (`>>>` and `...`) + - @-properties and classes in CoffeeScript + - E4X in JavaScript (by [Oleg Efimov][oe]) + - new keywords in Perl (by [Kirk Kimmel][kk]) + - big Ruby syntax update (by [Vasily Polovnyov][vast]) + - small fixes in Bash + +- Also Oleg Efimov did a great job of moving all the docs for language and style + developers and contributors from the old wiki under the source code in the + "docs" directory. Now these docs are nicely presented at + . + +[ng]: https://github.com/nathan11g +[dd]: https://github.com/drdrang +[bolk]: https://github.com/bolknote +[oe]: https://github.com/Sannis +[kk]: https://github.com/kimmel +[vast]: https://github.com/vast + + +## Version 7.2 + +A regular bug-fix release without any significant new features. Enjoy! + + +## Version 7.1 + +A Summer crop: + +- [Marc Fornos][mf] made the definition for Clojure along with the matching + style Rainbow (which, of course, works for other languages too). +- CoffeeScript support continues to improve getting support for regular + expressions. +- Yoshihide Jimbo ported to highlight.js [five Tomorrow styles][tm] from the + [project by Chris Kempson][tm0]. +- Thanks to [Casey Duncun][cd] the library can now be built in the popular + [AMD format][amd]. +- And last but not least, we've got a fair number of correctness and consistency + fixes, including a pretty significant refactoring of Ruby. + +[mf]: https://github.com/mfornos +[tm]: http://jmblog.github.com/color-themes-for-highlightjs/ +[tm0]: https://github.com/ChrisKempson/Tomorrow-Theme +[cd]: https://github.com/caseman +[amd]: http://requirejs.org/docs/whyamd.html + + +## Version 7.0 + +The reason for the new major version update is a global change of keyword syntax +which resulted in the library getting smaller once again. For example, the +hosted build is 2K less than at the previous version while supporting two new +languages. + +Notable changes: + +- The library now works not only in a browser but also with [node.js][]. It is + installable with `npm install highlight.js`. [API][] docs are available on our + wiki. + +- The new unique feature (apparently) among syntax highlighters is highlighting + *HTTP* headers and an arbitrary language in the request body. The most useful + languages here are *XML* and *JSON* both of which highlight.js does support. + Here's [the detailed post][p] about the feature. + +- Two new style themes: a dark "south" *[Pojoaque][]* by Jason Tate and an + emulation of*XCode* IDE by [Angel Olloqui][ao]. + +- Three new languages: *D* by [Aleksandar Ružičić][ar], *R* by [Joe Cheng][jc] + and *GLSL* by [Sergey Tikhomirov][st]. + +- *Nginx* syntax has become a million times smaller and more universal thanks to + remaking it in a more generic manner that doesn't require listing all the + directives in the known universe. + +- Function titles are now highlighted in *PHP*. + +- *Haskell* and *VHDL* were significantly reworked to be more rich and correct + by their respective maintainers [Jeremy Hull][sr] and [Igor Kalnitsky][ik]. + +And last but not least, many bugs have been fixed around correctness and +language detection. + +Overall highlight.js currently supports 51 languages and 20 style themes. + +[node.js]: http://nodejs.org/ +[api]: http://softwaremaniacs.org/wiki/doku.php/highlight.js:api +[p]: http://softwaremaniacs.org/blog/2012/05/10/http-and-json-in-highlight-js/en/ +[pojoaque]: http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html +[ao]: https://github.com/angelolloqui +[ar]: https://github.com/raleksandar +[jc]: https://github.com/jcheng5 +[st]: https://github.com/tikhomirov +[sr]: https://github.com/sourrust +[ik]: https://github.com/ikalnitsky + + +## Version 6.2 + +A lot of things happened in highlight.js since the last version! We've got nine +new contributors, the discussion group came alive, and the main branch on GitHub +now counts more than 350 followers. Here are most significant results coming +from all this activity: + +- 5 (five!) new languages: Rust, ActionScript, CoffeeScript, MatLab and + experimental support for markdown. Thanks go to [Andrey Vlasovskikh][av], + [Alexander Myadzel][am], [Dmytrii Nagirniak][dn], [Oleg Efimov][oe], [Denis + Bardadym][db] and [John Crepezzi][jc]. + +- 2 new style themes: Monokai by [Luigi Maselli][lm] and stylistic imitation of + another well-known highlighter Google Code Prettify by [Aahan Krish][ak]. + +- A vast number of [correctness fixes and code refactorings][log], mostly made + by [Oleg Efimov][oe] and [Evgeny Stepanischev][es]. + +[av]: https://github.com/vlasovskikh +[am]: https://github.com/myadzel +[dn]: https://github.com/dnagir +[oe]: https://github.com/Sannis +[db]: https://github.com/btd +[jc]: https://github.com/seejohnrun +[lm]: http://grigio.org/ +[ak]: https://github.com/geekpanth3r +[es]: https://github.com/bolknote +[log]: https://github.com/isagalaev/highlight.js/commits/ + + +## Version 6.1 — Solarized + +[Jeremy Hull][jh] has implemented my dream feature — a port of [Solarized][] +style theme famous for being based on the intricate color theory to achieve +correct contrast and color perception. It is now available for highlight.js in +both variants — light and dark. + +This version also adds a new original style Arta. Its author pumbur maintains a +[heavily modified fork of highlight.js][pb] on GitHub. + +[jh]: https://github.com/sourrust +[solarized]: http://ethanschoonover.com/solarized +[pb]: https://github.com/pumbur/highlight.js + + +## Version 6.0 + +New major version of the highlighter has been built on a significantly +refactored syntax. Due to this it's even smaller than the previous one while +supporting more languages! + +New languages are: + +- Haskell by [Jeremy Hull][sourrust] +- Erlang in two varieties — module and REPL — made collectively by [Nikolay + Zakharov][desh], [Dmitry Kovega][arhibot] and [Sergey Ignatov][ignatov] +- Objective C by [Valerii Hiora][vhbit] +- Vala by [Antono Vasiljev][antono] +- Go by [Stephan Kountso][steplg] + +[sourrust]: https://github.com/sourrust +[desh]: http://desh.su/ +[arhibot]: https://github.com/arhibot +[ignatov]: https://github.com/ignatov +[vhbit]: https://github.com/vhbit +[antono]: https://github.com/antono +[steplg]: https://github.com/steplg + +Also this version is marginally faster and fixes a number of small long-standing +bugs. + +Developer overview of the new language syntax is available in a [blog post about +recent beta release][beta]. + +[beta]: http://softwaremaniacs.org/blog/2011/04/25/highlight-js-60-beta/en/ + +P.S. New version is not yet available on a Yandex' CDN, so for now you have to +download [your own copy][d]. + +[d]: /soft/highlight/en/download/ + + +## Version 5.14 + +Fixed bugs in HTML/XML detection and relevance introduced in previous +refactoring. + +Also test.html now shows the second best result of language detection by +relevance. + + +## Version 5.13 + +Past weekend began with a couple of simple additions for existing languages but +ended up in a big code refactoring bringing along nice improvements for language +developers. + +### For users + +- Description of C++ has got new keywords from the upcoming [C++ 0x][] standard. +- Description of HTML has got new tags from [HTML 5][]. +- CSS-styles have been unified to use consistent padding and also have lost + pop-outs with names of detected languages. +- [Igor Kalnitsky][ik] has sent two new language descriptions: CMake и VHDL. + +This makes total number of languages supported by highlight.js to reach 35. + +Bug fixes: + +- Custom classes on `

    ` tags are not being overridden anymore
    +- More correct highlighting of code blocks inside non-`
    ` containers:
    +  highlighter now doesn't insist on replacing them with its own container and
    +  just replaces the contents.
    +- Small fixes in browser compatibility and heuristics.
    +
    +[c++ 0x]: http://ru.wikipedia.org/wiki/C%2B%2B0x
    +[html 5]: http://en.wikipedia.org/wiki/HTML5
    +[ik]: http://kalnitsky.org.ua/
    +
    +### For developers
    +
    +The most significant change is the ability to include language submodes right
    +under `contains` instead of defining explicit named submodes in the main array:
    +
    +    contains: [
    +      'string',
    +      'number',
    +      {begin: '\\n', end: hljs.IMMEDIATE_RE}
    +    ]
    +
    +This is useful for auxiliary modes needed only in one place to define parsing.
    +Note that such modes often don't have `className` and hence won't generate a
    +separate `` in the resulting markup. This is similar in effect to
    +`noMarkup: true`. All existing languages have been refactored accordingly.
    +
    +Test file test.html has at last become a real test. Now it not only puts the
    +detected language name under the code snippet but also tests if it matches the
    +expected one. Test summary is displayed right above all language snippets.
    +
    +
    +## CDN
    +
    +Fine people at [Yandex][] agreed to host highlight.js on their big fast servers.
    +[Link up][l]!
    +
    +[yandex]: http://yandex.com/
    +[l]: http://softwaremaniacs.org/soft/highlight/en/download/
    +
    +
    +## Version 5.10 — "Paris".
    +
    +Though I'm on a vacation in Paris, I decided to release a new version with a
    +couple of small fixes:
    +
    +- Tomas Vitvar discovered that TAB replacement doesn't always work when used
    +  with custom markup in code
    +- SQL parsing is even more rigid now and doesn't step over SmallTalk in tests
    +
    +
    +## Version 5.9
    +
    +A long-awaited version is finally released.
    +
    +New languages:
    +
    +- Andrew Fedorov made a definition for Lua
    +- a long-time highlight.js contributor [Peter Leonov][pl] made a definition for
    +  Nginx config
    +- [Vladimir Moskva][vm] made a definition for TeX
    +
    +[pl]: http://kung-fu-tzu.ru/
    +[vm]: http://fulc.ru/
    +
    +Fixes for existing languages:
    +
    +- [Loren Segal][ls] reworked the Ruby definition and added highlighting for
    +  [YARD][] inline documentation
    +- the definition of SQL has become more solid and now it shouldn't be overly
    +  greedy when it comes to language detection
    +
    +[ls]: http://gnuu.org/
    +[yard]: http://yardoc.org/
    +
    +The highlighter has become more usable as a library allowing to do highlighting
    +from initialization code of JS frameworks and in ajax methods (see.
    +readme.eng.txt).
    +
    +Also this version drops support for the [WordPress][wp] plugin. Everyone is
    +welcome to [pick up its maintenance][p] if needed.
    +
    +[wp]: http://wordpress.org/
    +[p]: http://bazaar.launchpad.net/~isagalaev/+junk/highlight/annotate/342/src/wp_highlight.js.php
    +
    +
    +## Version 5.8
    +
    +- Jan Berkel has contributed a definition for Scala. +1 to hotness!
    +- All CSS-styles are rewritten to work only inside `
    ` tags to avoid
    +  conflicts with host site styles.
    +
    +
    +## Version 5.7.
    +
    +Fixed escaping of quotes in VBScript strings.
    +
    +
    +## Version 5.5
    +
    +This version brings a small change: now .ini-files allow digits, underscores and
    +square brackets in key names.
    +
    +
    +## Version 5.4
    +
    +Fixed small but upsetting bug in the packer which caused incorrect highlighting
    +of explicitly specified languages. Thanks to Andrew Fedorov for precise
    +diagnostics!
    +
    +
    +## Version 5.3
    +
    +The version to fulfil old promises.
    +
    +The most significant change is that highlight.js now preserves custom user
    +markup in code along with its own highlighting markup. This means that now it's
    +possible to use, say, links in code. Thanks to [Vladimir Dolzhenko][vd] for the
    +[initial proposal][1] and for making a proof-of-concept patch.
    +
    +Also in this version:
    +
    +- [Vasily Polovnyov][vp] has sent a GitHub-like style and has implemented
    +  support for CSS @-rules and Ruby symbols.
    +- Yura Zaripov has sent two styles: Brown Paper and School Book.
    +- Oleg Volchkov has sent a definition for [Parser 3][p3].
    +
    +[1]: http://softwaremaniacs.org/forum/highlightjs/6612/
    +[p3]: http://www.parser.ru/
    +[vp]: http://vasily.polovnyov.ru/
    +[vd]: http://dolzhenko.blogspot.com/
    +
    +
    +## Version 5.2
    +
    +- at last it's possible to replace indentation TABs with something sensible (e.g. 2 or 4 spaces)
    +- new keywords and built-ins for 1C by Sergey Baranov
    +- a couple of small fixes to Apache highlighting
    +
    +
    +## Version 5.1
    +
    +This is one of those nice version consisting entirely of new and shiny
    +contributions!
    +
    +- [Vladimir Ermakov][vooon] created highlighting for AVR Assembler
    +- [Ruslan Keba][rukeba] created highlighting for Apache config file. Also his
    +  original visual style for it is now available for all highlight.js languages
    +  under the name "Magula".
    +- [Shuen-Huei Guan][drake] (aka Drake) sent new keywords for RenderMan
    +  languages. Also thanks go to [Konstantin Evdokimenko][ke] for his advice on
    +  the matter.
    +
    +[vooon]: http://vehq.ru/about/
    +[rukeba]: http://rukeba.com/
    +[drake]: http://drakeguan.org/
    +[ke]: http://k-evdokimenko.moikrug.ru/
    +
    +
    +## Version 5.0
    +
    +The main change in the new major version of highlight.js is a mechanism for
    +packing several languages along with the library itself into a single compressed
    +file. Now sites using several languages will load considerably faster because
    +the library won't dynamically include additional files while loading.
    +
    +Also this version fixes a long-standing bug with Javascript highlighting that
    +couldn't distinguish between regular expressions and division operations.
    +
    +And as usually there were a couple of minor correctness fixes.
    +
    +Great thanks to all contributors! Keep using highlight.js.
    +
    +
    +## Version 4.3
    +
    +This version comes with two contributions from [Jason Diamond][jd]:
    +
    +- language definition for C# (yes! it was a long-missed thing!)
    +- Visual Studio-like highlighting style
    +
    +Plus there are a couple of minor bug fixes for parsing HTML and XML attributes.
    +
    +[jd]: http://jason.diamond.name/weblog/
    +
    +
    +## Version 4.2
    +
    +The biggest news is highlighting for Lisp, courtesy of Vasily Polovnyov. It's
    +somewhat experimental meaning that for highlighting "keywords" it doesn't use
    +any pre-defined set of a Lisp dialect. Instead it tries to highlight first word
    +in parentheses wherever it makes sense. I'd like to ask people programming in
    +Lisp to confirm if it's a good idea and send feedback to [the forum][f].
    +
    +Other changes:
    +
    +- Smalltalk was excluded from DEFAULT_LANGUAGES to save traffic
    +- [Vladimir Epifanov][voldmar] has implemented javascript style switcher for
    +  test.html
    +- comments now allowed inside Ruby function definition
    +- [MEL][] language from [Shuen-Huei Guan][drake]
    +- whitespace now allowed between `
    ` and ``
    +- better auto-detection of C++ and PHP
    +- HTML allows embedded VBScript (`<% .. %>`)
    +
    +[f]: http://softwaremaniacs.org/forum/highlightjs/
    +[voldmar]: http://voldmar.ya.ru/
    +[mel]: http://en.wikipedia.org/wiki/Maya_Embedded_Language
    +[drake]: http://drakeguan.org/
    +
    +
    +## Version 4.1
    +
    +Languages:
    +
    +- Bash from Vah
    +- DOS bat-files from Alexander Makarov (Sam)
    +- Diff files from Vasily Polovnyov
    +- Ini files from myself though initial idea was from Sam
    +
    +Styles:
    +
    +- Zenburn from Vladimir Epifanov, this is an imitation of a
    +  [well-known theme for Vim][zenburn].
    +- Ascetic from myself, as a realization of ideals of non-flashy highlighting:
    +  just one color in only three gradations :-)
    +
    +In other news. [One small bug][bug] was fixed, built-in keywords were added for
    +Python and C++ which improved auto-detection for the latter (it was shame that
    +[my wife's blog][alenacpp] had issues with it from time to time). And lastly
    +thanks go to Sam for getting rid of my stylistic comments in code that were
    +getting in the way of [JSMin][].
    +
    +[zenburn]: http://en.wikipedia.org/wiki/Zenburn
    +[alenacpp]: http://alenacpp.blogspot.com/
    +[bug]: http://softwaremaniacs.org/forum/viewtopic.php?id=1823
    +[jsmin]: http://code.google.com/p/jsmin-php/
    +
    +
    +## Version 4.0
    +
    +New major version is a result of vast refactoring and of many contributions.
    +
    +Visible new features:
    +
    +- Highlighting of embedded languages. Currently is implemented highlighting of
    +  Javascript and CSS inside HTML.
    +- Bundled 5 ready-made style themes!
    +
    +Invisible new features:
    +
    +- Highlight.js no longer pollutes global namespace. Only one object and one
    +  function for backward compatibility.
    +- Performance is further increased by about 15%.
    +
    +Changing of a major version number caused by a new format of language definition
    +files. If you use some third-party language files they should be updated.
    +
    +
    +## Version 3.5
    +
    +A very nice version in my opinion fixing a number of small bugs and slightly
    +increased speed in a couple of corner cases. Thanks to everybody who reports
    +bugs in he [forum][f] and by email!
    +
    +There is also a new language — XML. A custom XML formerly was detected as HTML
    +and didn't highlight custom tags. In this version I tried to make custom XML to
    +be detected and highlighted by its own rules. Which by the way include such
    +things as CDATA sections and processing instructions (``).
    +
    +[f]: http://softwaremaniacs.org/forum/viewforum.php?id=6
    +
    +
    +## Version 3.3
    +
    +[Vladimir Gubarkov][xonix] has provided an interesting and useful addition.
    +File export.html contains a little program that shows and allows to copy and
    +paste an HTML code generated by the highlighter for any code snippet. This can
    +be useful in situations when one can't use the script itself on a site.
    +
    +
    +[xonix]: http://xonixx.blogspot.com/
    +
    +
    +## Version 3.2 consists completely of contributions:
    +
    +- Vladimir Gubarkov has described SmallTalk
    +- Yuri Ivanov has described 1C
    +- Peter Leonov has packaged the highlighter as a Firefox extension
    +- Vladimir Ermakov has compiled a mod for phpBB
    +
    +Many thanks to you all!
    +
    +
    +## Version 3.1
    +
    +Three new languages are available: Django templates, SQL and Axapta. The latter
    +two are sent by [Dmitri Roudakov][1]. However I've almost entirely rewrote an
    +SQL definition but I'd never started it be it from the ground up :-)
    +
    +The engine itself has got a long awaited feature of grouping keywords
    +("keyword", "built-in function", "literal"). No more hacks!
    +
    +[1]: http://roudakov.ru/
    +
    +
    +## Version 3.0
    +
    +It is major mainly because now highlight.js has grown large and has become
    +modular. Now when you pass it a list of languages to highlight it will
    +dynamically load into a browser only those languages.
    +
    +Also:
    +
    +- Konstantin Evdokimenko of [RibKit][] project has created a highlighting for
    +  RenderMan Shading Language and RenderMan Interface Bytestream. Yay for more
    +  languages!
    +- Heuristics for C++ and HTML got better.
    +- I've implemented (at last) a correct handling of backslash escapes in C-like
    +  languages.
    +
    +There is also a small backwards incompatible change in the new version. The
    +function initHighlighting that was used to initialize highlighting instead of
    +initHighlightingOnLoad a long time ago no longer works. If you by chance still
    +use it — replace it with the new one.
    +
    +[RibKit]: http://ribkit.sourceforge.net/
    +
    +
    +## Version 2.9
    +
    +Highlight.js is a parser, not just a couple of regular expressions. That said
    +I'm glad to announce that in the new version 2.9 has support for:
    +
    +- in-string substitutions for Ruby -- `#{...}`
    +- strings from from numeric symbol codes (like #XX) for Delphi
    +
    +
    +## Version 2.8
    +
    +A maintenance release with more tuned heuristics. Fully backwards compatible.
    +
    +
    +## Version 2.7
    +
    +- Nikita Ledyaev presents highlighting for VBScript, yay!
    +- A couple of bugs with escaping in strings were fixed thanks to Mickle
    +- Ongoing tuning of heuristics
    +
    +Fixed bugs were rather unpleasant so I encourage everyone to upgrade!
    +
    +
    +## Version 2.4
    +
    +- Peter Leonov provides another improved highlighting for Perl
    +- Javascript gets a new kind of keywords — "literals". These are the words
    +  "true", "false" and "null"
    +
    +Also highlight.js homepage now lists sites that use the library. Feel free to
    +add your site by [dropping me a message][mail] until I find the time to build a
    +submit form.
    +
    +[mail]: mailto:Maniac@SoftwareManiacs.Org
    +
    +
    +## Version 2.3
    +
    +This version fixes IE breakage in previous version. My apologies to all who have
    +already downloaded that one!
    +
    +
    +## Version 2.2
    +
    +- added highlighting for Javascript
    +- at last fixed parsing of Delphi's escaped apostrophes in strings
    +- in Ruby fixed highlighting of keywords 'def' and 'class', same for 'sub' in
    +  Perl
    +
    +
    +## Version 2.0
    +
    +- Ruby support by [Anton Kovalyov][ak]
    +- speed increased by orders of magnitude due to new way of parsing
    +- this same way allows now correct highlighting of keywords in some tricky
    +  places (like keyword "End" at the end of Delphi classes)
    +
    +[ak]: http://anton.kovalyov.net/
    +
    +
    +## Version 1.0
    +
    +Version 1.0 of javascript syntax highlighter is released!
    +
    +It's the first version available with English description. Feel free to post
    +your comments and question to [highlight.js forum][forum]. And don't be afraid
    +if you find there some fancy Cyrillic letters -- it's for Russian users too :-)
    +
    +[forum]: http://softwaremaniacs.org/forum/viewforum.php?id=6
    diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/LICENSE b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/LICENSE
    new file mode 100644
    index 00000000..422deb73
    --- /dev/null
    +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/LICENSE
    @@ -0,0 +1,24 @@
    +Copyright (c) 2006, Ivan Sagalaev
    +All rights reserved.
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +    * Redistributions of source code must retain the above copyright
    +      notice, this list of conditions and the following disclaimer.
    +    * Redistributions in binary form must reproduce the above copyright
    +      notice, this list of conditions and the following disclaimer in the
    +      documentation and/or other materials provided with the distribution.
    +    * Neither the name of highlight.js nor the names of its contributors 
    +      may be used to endorse or promote products derived from this software 
    +      without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
    +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
    +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.md b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.md
    new file mode 100644
    index 00000000..0ee96377
    --- /dev/null
    +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.md
    @@ -0,0 +1,167 @@
    +# Highlight.js
    +
    +Highlight.js highlights syntax in code examples on blogs, forums and,
    +in fact, on any web page. It's very easy to use because it works
    +automatically: finds blocks of code, detects a language, highlights it.
    +
    +Autodetection can be fine tuned when it fails by itself (see "Heuristics").
    +
    +
    +## Basic usage
    +
    +Link the library and a stylesheet from your page and hook highlighting to
    +the page load event:
    +
    +```html
    +
    +
    +
    +```
    +
    +This will highlight all code on the page marked up as `
     .. 
    `. +If you use different markup or need to apply highlighting dynamically, read +"Custom initialization" below. + +- You can download your own customized version of "highlight.pack.js" or + use the hosted one as described on the download page: + + +- Style themes are available in the download package or as hosted files. + To create a custom style for your site see the class reference in the file + [CSS classes reference][cr] from the downloaded package. + +[cr]: http://highlightjs.readthedocs.org/en/latest/css-classes-reference.html + + +## node.js + +Highlight.js can be used under node.js. The package with all supported languages is +installable from NPM: + + npm install highlight.js + +Alternatively, you can build it from the source with only languages you need: + + python3 tools/build.py -tnode lang1 lang2 .. + +Using the library: + +```javascript +var hljs = require('highlight.js'); + +// If you know the language +hljs.highlight(lang, code).value; + +// Automatic language detection +hljs.highlightAuto(code).value; +``` + + +## AMD + +Highlight.js can be used with an AMD loader. You will need to build it from +source in order to do so: + +```bash +$ python3 tools/build.py -tamd lang1 lang2 .. +``` + +Which will generate a `build/highlight.pack.js` which will load as an AMD +module with support for the built languages and can be used like so: + +```javascript +require(["highlight.js/build/highlight.pack"], function(hljs){ + + // If you know the language + hljs.highlight(lang, code).value; + + // Automatic language detection + hljs.highlightAuto(code).value; +}); +``` + + +## Tab replacement + +You can replace TAB ('\x09') characters used for indentation in your code +with some fixed number of spaces or with a `` to give them special +styling: + +```html + +``` + +## Custom initialization + +If you use different markup for code blocks you can initialize them manually +with `highlightBlock(code)` function. It takes a DOM element containing the +code to highlight and optionally a string with which to replace TAB +characters. + +Initialization using, for example, jQuery might look like this: + +```javascript +$(document).ready(function() { + $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); +}); +``` + +You can use `highlightBlock` to highlight blocks dynamically inserted into +the page. Just make sure you don't do it twice for already highlighted +blocks. + +If your code container relies on `
    ` tags instead of line breaks (i.e. if +it's not `
    `) set the `useBR` option to `true`:
    +
    +```javascript
    +hljs.configure({useBR: true});
    +$('div.code').each(function(i, e) {hljs.highlightBlock(e)});
    +```
    +
    +
    +## Heuristics
    +
    +Autodetection of a code's language is done using a simple heuristic:
    +the program tries to highlight a fragment with all available languages and
    +counts all syntactic structures that it finds along the way. The language
    +with greatest count wins.
    +
    +This means that in short fragments the probability of an error is high
    +(and it really happens sometimes). In this cases you can set the fragment's
    +language explicitly by assigning a class to the `` element:
    +
    +```html
    +
    ...
    +``` + +You can use class names recommended in HTML5: "language-html", +"language-php". Classes also can be assigned to the `
    ` element.
    +
    +To disable highlighting of a fragment altogether use "no-highlight" class:
    +
    +```html
    +
    ...
    +``` + + +## Export + +File export.html contains a little program that allows you to paste in a code +snippet and then copy and paste the resulting HTML code generated by the +highlighter. This is useful in situations when you can't use the script itself +on a site. + + +## Meta + +- Version: 8.0 +- URL: http://highlightjs.org/ + +For the license terms see LICENSE files. +For authors and contributors see AUTHORS.en.txt file. diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.ru.md b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.ru.md new file mode 100644 index 00000000..0d0e0fea --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/README.ru.md @@ -0,0 +1,171 @@ +# Highlight.js + +Highlight.js нужен для подсветки синтаксиса в примерах кода в блогах, +форумах и вообще на любых веб-страницах. Пользоваться им очень просто, +потому что работает он автоматически: сам находит блоки кода, сам +определяет язык, сам подсвечивает. + +Автоопределением языка можно управлять, когда оно не справляется само (см. +дальше "Эвристика"). + + +## Простое использование + +Подключите библиотеку и стиль на страницу и повесть вызов подсветки на +загрузку страницы: + +```html + + + +``` + +Весь код на странице, обрамлённый в теги `
     .. 
    ` +будет автоматически подсвечен. Если вы используете другие теги или хотите +подсвечивать блоки кода динамически, читайте "Инициализацию вручную" ниже. + +- Вы можете скачать собственную версию "highlight.pack.js" или сослаться + на захостенный файл, как описано на странице загрузки: + + +- Стилевые темы можно найти в загруженном архиве или также использовать + захостенные. Чтобы сделать собственный стиль для своего сайта, вам + будет полезен [CSS classes reference][cr], который тоже есть в архиве. + +[cr]: http://highlightjs.readthedocs.org/en/latest/css-classes-reference.html + + +## node.js + +Highlight.js можно использовать в node.js. Библиотеку со всеми возможными языками можно +установить с NPM: + + npm install highlight.js + +Также её можно собрать из исходников с только теми языками, которые нужны: + + python3 tools/build.py -tnode lang1 lang2 .. + +Использование библиотеки: + +```javascript +var hljs = require('highlight.js'); + +// Если вы знаете язык +hljs.highlight(lang, code).value; + +// Автоопределение языка +hljs.highlightAuto(code).value; +``` + + +## AMD + +Highlight.js можно использовать с загрузчиком AMD-модулей. Для этого его +нужно собрать из исходников следующей командой: + +```bash +$ python3 tools/build.py -tamd lang1 lang2 .. +``` + +Она создаст файл `build/highlight.pack.js`, который является загружаемым +AMD-модулем и содержит все выбранные при сборке языки. Используется он так: + +```javascript +require(["highlight.js/build/highlight.pack"], function(hljs){ + + // Если вы знаете язык + hljs.highlight(lang, code).value; + + // Автоопределение языка + hljs.highlightAuto(code).value; +}); +``` + + +## Замена TABов + +Также вы можете заменить символы TAB ('\x09'), используемые для отступов, на +фиксированное количество пробелов или на отдельный ``, чтобы задать ему +какой-нибудь специальный стиль: + +```html + +``` + + +## Инициализация вручную + +Если вы используете другие теги для блоков кода, вы можете инициализировать их +явно с помощью функции `highlightBlock(code)`. Она принимает DOM-элемент с +текстом расцвечиваемого кода и опционально - строчку для замены символов TAB. + +Например с использованием jQuery код инициализации может выглядеть так: + +```javascript +$(document).ready(function() { + $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); +}); +``` + +`highlightBlock` можно также использовать, чтобы подсветить блоки кода, +добавленные на страницу динамически. Только убедитесь, что вы не делаете этого +повторно для уже раскрашенных блоков. + +Если ваш блок кода использует `
    ` вместо переводов строки (т.е. если это не +`
    `), включите опцию `useBR`:
    +
    +```javascript
    +hljs.configure({useBR: true});
    +$('div.code').each(function(i, e) {hljs.highlightBlock(e)});
    +```
    +
    +
    +## Эвристика
    +
    +Определение языка, на котором написан фрагмент, делается с помощью
    +довольно простой эвристики: программа пытается расцветить фрагмент всеми
    +языками подряд, и для каждого языка считает количество подошедших
    +синтаксически конструкций и ключевых слов. Для какого языка нашлось больше,
    +тот и выбирается.
    +
    +Это означает, что в коротких фрагментах высока вероятность ошибки, что
    +периодически и случается. Чтобы указать язык фрагмента явно, надо написать
    +его название в виде класса к элементу ``:
    +
    +```html
    +
    ...
    +``` + +Можно использовать рекомендованные в HTML5 названия классов: +"language-html", "language-php". Также можно назначать классы на элемент +`
    `.
    +
    +Чтобы запретить расцветку фрагмента вообще, используется класс "no-highlight":
    +
    +```html
    +
    ...
    +``` + + +## Экспорт + +В файле export.html находится небольшая программка, которая показывает и дает +скопировать непосредственно HTML-код подсветки для любого заданного фрагмента кода. +Это может понадобится например на сайте, на котором нельзя подключить сам скрипт +highlight.js. + + +## Координаты + +- Версия: 8.0 +- URL: http://highlightjs.org/ + +Лицензионное соглашение читайте в файле LICENSE. +Список авторов и соавторов читайте в файле AUTHORS.ru.txt diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js new file mode 100644 index 00000000..2f0a664d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js @@ -0,0 +1,2 @@ +// %LEAVE_UNMINIFIED% %REMOVE_LINE% +var hljs=new function(){function k(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function i(w,x){var v=w&&w.exec(x);return v&&v.index==0}function d(v){return Array.prototype.map.call(v.childNodes,function(w){if(w.nodeType==3){return b.useBR?w.nodeValue.replace(/\n/g,""):w.nodeValue}if(t(w)=="br"){return"\n"}return d(w)}).join("")}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^language-/,"")});return v.filter(function(x){return j(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=k(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+k(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};function E(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})}if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b=D.bK.split(" ").join("|")}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?\\b("+F.b+")\\b\\.?":F.b}).concat([D.tE]).concat([D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T";U+=Z+'">';return U+X+Y}function N(){var U=k(C);if(!I.k){return U}var T="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(U);while(V){T+=U.substr(X,V.index-X);var W=E(I,V);if(W){H+=W[1];T+=w(W[0],V[0])}else{T+=V[0]}X=I.lR.lastIndex;V=I.lR.exec(U)}return T+U.substr(X)}function F(){if(I.sL&&!f[I.sL]){return k(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):g(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=k(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=k(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"")+'"')}C+=X;return X.length||1}var M=j(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:k(L)}}else{throw O}}}function g(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:k(y)};var w=v;x.forEach(function(z){if(!j(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function h(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"
    ")}return v}function p(z){var y=d(z);var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):g(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=h(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function e(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function j(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=g;this.fixMarkup=h;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=e;this.getLanguage=j;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("cs",function(b){var a="abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async await ascending descending from get group into join let orderby partial select set value var where yield";return{k:a,c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",b:"///|"},{cN:"xmlDocTag",b:""}]},b.CLCM,b.CBLCLM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},b.ASM,b.QSM,b.CNM,{bK:"protected public private internal",e:/[{;=]/,k:a,c:[{bK:"class namespace interface",starts:{c:[b.TM]}},{b:b.IR+"\\s*\\(",rB:true,c:[b.TM]}]}]}});hljs.registerLanguage("ruby",function(e){var h="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var g="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor";var a={cN:"yardoctag",b:"@[A-Za-z]+"};var i={cN:"comment",v:[{b:"#",e:"$",c:[a]},{b:"^\\=begin",e:"^\\=end",c:[a],r:10},{b:"^__END__",e:"\\n$"}]};var c={cN:"subst",b:"#\\{",e:"}",k:g};var d={cN:"string",c:[e.BE,c],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:"%[qw]?\\(",e:"\\)"},{b:"%[qw]?\\[",e:"\\]"},{b:"%[qw]?{",e:"}"},{b:"%[qw]?<",e:">",r:10},{b:"%[qw]?/",e:"/",r:10},{b:"%[qw]?%",e:"%",r:10},{b:"%[qw]?-",e:"-",r:10},{b:"%[qw]?\\|",e:"\\|",r:10},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]};var b={cN:"params",b:"\\(",e:"\\)",k:g};var f=[d,i,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]},i]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[e.inherit(e.TM,{b:h}),b,i]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[d,{b:h}],r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[i,{cN:"regexp",c:[e.BE,c],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];c.c=f;b.c=f;return{k:g,c:f}});hljs.registerLanguage("diff",function(a){return{c:[{cN:"chunk",r:10,v:[{b:/^\@\@ +\-\d+,\d+ +\+\d+,\d+ +\@\@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("javascript",function(a){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,a.REGEXP_MODE,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBLCLM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/]+/}]}]}]};return{aliases:["html"],cI:true,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true,}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("css",function(a){var b="[a-zA-Z-][a-zA-Z0-9_-]*";var c={cN:"function",b:b+"\\(",e:"\\)",c:["self",a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:true,eE:true,r:0,c:[c,a.ASM,a.QSM,a.NM]}]},{cN:"tag",b:b,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[c,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});hljs.registerLanguage("http",function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}});hljs.registerLanguage("java",function(b){var a="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws";return{k:a,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}],r:10},b.CLCM,b.CBLCLM,b.ASM,b.QSM,{bK:"protected public private",e:/[{;=]/,k:a,c:[{cN:"class",bK:"class interface",eW:true,i:/[:"<>]/,c:[{bK:"extends implements",r:10},b.UTM]},{b:b.UIR+"\\s*\\(",rB:true,c:[b.UTM]}]},b.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("php",function(b){var e={cN:"variable",b:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var a={cN:"preprocessor",b:/<\?(php)?|\?>/};var c={cN:"string",c:[b.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},b.inherit(b.ASM,{i:null}),b.inherit(b.QSM,{i:null})]};var d={v:[b.BNM,b.CNM]};return{cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[b.CLCM,b.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"},a]},{cN:"comment",b:"__halt_compiler.+?;",eW:true,k:"__halt_compiler",l:b.UIR},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[b.BE]},a,e,{cN:"function",bK:"function",e:/[;{]/,i:"\\$|\\[|%",c:[b.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",e,b.CBLCLM,c,d]}]},{cN:"class",bK:"class interface",e:"{",i:/[:\(\$"]/,c:[{bK:"extends implements",r:10},b.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[b.UTM]},{bK:"use",e:";",c:[b.UTM]},{b:"=>"},c,d]}});hljs.registerLanguage("python",function(a){var f={cN:"prompt",b:/^(>>>|\.\.\.) /};var b={cN:"string",c:[a.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[f],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[f],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/,},{b:/(b|br)"/,e:/"/,},a.ASM,a.QSM]};var d={cN:"number",r:0,v:[{b:a.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:a.CNR+"[lLjJ]?"}]};var e={cN:"params",b:/\(/,e:/\)/,c:["self",f,d,b]};var c={e:/:/,i:/[${=;\n]/,c:[a.UTM,e]};return{k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[f,d,b,a.HCM,a.inherit(c,{cN:"function",bK:"def",r:10}),a.inherit(c,{cN:"class",bK:"class"}),{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("sql",function(a){return{cI:true,i:/[<>]/,c:[{cN:"operator",b:"\\b(begin|end|start|commit|rollback|savepoint|lock|alter|create|drop|rename|call|delete|do|handler|insert|load|replace|select|truncate|update|set|show|pragma|grant|merge)\\b(?!:)",e:";",eW:true,k:{keyword:"all partial global month current_timestamp using go revoke smallint indicator end-exec disconnect zone with character assertion to add current_user usage input local alter match collate real then rollback get read timestamp session_user not integer bit unique day minute desc insert execute like ilike|2 level decimal drop continue isolation found where constraints domain right national some module transaction relative second connect escape close system_user for deferred section cast current sqlstate allocate intersect deallocate numeric public preserve full goto initially asc no key output collation group by union session both last language constraint column of space foreign deferrable prior connection unknown action commit view or first into float year primary cascaded except restrict set references names table outer open select size are rows from prepare distinct leading create only next inner authorization schema corresponding option declare precision immediate else timezone_minute external varying translation true case exception join hour default double scroll value cursor descriptor values dec fetch procedure delete and false int is describe char as at in varchar null trailing any absolute current_time end grant privileges when cross check write current_date pad begin temporary exec time update catalog user sql date on identity timezone_hour natural whenever interval work order cascade diagnostics nchar having left call do handler load replace truncate start lock show pragma exists number trigger if before after each row merge matched database",aggregate:"count sum min max avg"},c:[{cN:"string",b:"'",e:"'",c:[a.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[a.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[a.BE]},a.CNM]},a.CBLCLM,{cN:"comment",b:"--",e:"$"}]}});hljs.registerLanguage("ini",function(a){return{cI:true,i:/\S/,c:[{cN:"comment",b:";",e:"$"},{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9\\[\\]_-]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:true,k:"on off true false yes no",c:[a.QSM,a.NM],r:0}]}]}});hljs.registerLanguage("perl",function(c){var d="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when";var f={cN:"subst",b:"[$@]\\{",e:"\\}",k:d};var g={b:"->{",e:"}"};var a={cN:"variable",v:[{b:/\$\d/},{b:/[\$\%\@\*](\^\w\b|#\w+(\:\:\w+)*|{\w+}|\w+(\:\:\w*)*)/},{b:/[\$\%\@\*][^\s\w{]/,r:0}]};var e={cN:"comment",b:"^(__END__|__DATA__)",e:"\\n$",r:5};var h=[c.BE,f,a];var b=[a,c.HCM,e,{cN:"comment",b:"^\\=\\w",e:"\\=cut",eW:true},g,{cN:"string",c:h,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[c.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[c.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+c.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[c.HCM,e,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[c.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0}];f.c=b;g.c=b;return{k:d,c:b}});hljs.registerLanguage("objectivec",function(a){var d={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign self synchronized id nonatomic super unichar IBOutlet IBAction strong weak @private @protected @public @try @property @end @throw @catch @finally @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"NSString NSDictionary CGRect CGPoint UIButton UILabel UITextView UIWebView MKMapView UISegmentedControl NSObject UITableViewDelegate UITableViewDataSource NSThread UIActivityIndicator UITabbar UIToolBar UIBarButtonItem UIImageView NSAutoreleasePool UITableView BOOL NSInteger CGFloat NSException NSLog NSMutableString NSMutableArray NSMutableDictionary NSURL NSIndexPath CGSize UITableViewCell UIView UIViewController UINavigationBar UINavigationController UITabBarController UIPopoverController UIPopoverControllerDelegate UIImage NSNumber UISearchBar NSFetchedResultsController NSFetchedResultsChangeType UIScrollView UIScrollViewDelegate UIEdgeInsets UIColor UIFont UIApplication NSNotFound NSNotificationCenter NSNotification UILocalNotification NSBundle NSFileManager NSTimeInterval NSDate NSCalendar NSUserDefaults UIWindow NSRange NSArray NSError NSURLRequest NSURLConnection UIInterfaceOrientation MPMoviePlayerController dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"};var c=/[a-zA-Z@][a-zA-Z0-9_]*/;var b="@interface @class @protocol @implementation";return{k:d,l:c,i:""}]},{cN:"preprocessor",b:"#",e:"$"},{cN:"class",b:"("+b.split(" ").join("|")+")\\b",e:"({|$)",k:b,l:c,c:[a.UTM]},{cN:"variable",b:"\\."+a.UIR,r:0}]}});hljs.registerLanguage("coffeescript",function(c){var b={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",reserved:"case default function var void with const let enum export import native __hasProp __extends __slice __bind __indexOf",built_in:"npm require console print module exports global window document"};var a="[A-Za-z$_][0-9A-Za-z$_]*";var f=c.inherit(c.TM,{b:a});var e={cN:"subst",b:/#\{/,e:/}/,k:b};var d=[c.BNM,c.inherit(c.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[c.BE]},{b:/'/,e:/'/,c:[c.BE]},{b:/"""/,e:/"""/,c:[c.BE,e]},{b:/"/,e:/"/,c:[c.BE,e]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[e,c.HCM]},{b:"//[gim]*",r:0},{b:"/\\S(\\\\.|[^\\n])*?/[gim]*(?=\\s|\\W|$)"}]},{cN:"property",b:"@"+a},{b:"`",e:"`",eB:true,eE:true,sL:"javascript"}];e.c=d;return{k:b,c:d.concat([{cN:"comment",b:"###",e:"###"},c.HCM,{cN:"function",b:"("+a+"\\s*=\\s*)?(\\(.*\\))?\\s*\\B[-=]>",e:"[-=]>",rB:true,c:[f,{cN:"params",b:"\\(",rB:true,c:[{b:/\(/,e:/\)/,k:b,c:["self"].concat(d)}]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:true,i:/[:="\[\]]/,c:[f]},f]},{cN:"attribute",b:a+":",e:":",rB:true,eE:true,r:0}])}});hljs.registerLanguage("nginx",function(c){var b={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+c.UIR}]};var a={eW:true,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[c.HCM,{cN:"string",c:[c.BE,b],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:true,eE:true},{cN:"regexp",c:[c.BE,b],v:[{b:"\\s\\^",e:"\\s|{|;",rE:true},{b:"~\\*?\\s+",e:"\\s|{|;",rE:true},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},b]};return{c:[c.HCM,{b:c.UIR+"\\s",e:";|{",rB:true,c:[c.inherit(c.UTM,{starts:a})],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});hljs.registerLanguage("apache",function(a){var b={cN:"number",b:"[\\$%]\\d+"};return{cI:true,c:[a.HCM,{cN:"tag",b:""},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",b]},b,a.QSM]}}],i:/\S/}});hljs.registerLanguage("cpp",function(a){var b={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long throw volatile static protected bool template mutable if public friend do return goto auto void enum else break new extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c"],k:b,i:"",i:"\\n"},a.CLCM]},{cN:"stl_container",b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:b,r:10,c:["self"]}]}});hljs.registerLanguage("makefile",function(a){var b={cN:"variable",b:/\$\(/,e:/\)/,c:[a.BE]};return{c:[a.HCM,{b:/^\w+\s*\W*=/,rB:true,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:true,starts:{e:/$/,r:0,c:[b],}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,c:[a.QSM,b]}]}}); \ No newline at end of file diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/arta.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/arta.css new file mode 100644 index 00000000..02db86a2 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/arta.css @@ -0,0 +1,160 @@ +/* +Date: 17.V.2011 +Author: pumbur +*/ + +.hljs +{ + display: block; padding: 0.5em; + background: #222; +} + +.profile .hljs-header *, +.ini .hljs-title, +.nginx .hljs-title +{ + color: #fff; +} + +.hljs-comment, +.hljs-javadoc, +.hljs-preprocessor, +.hljs-preprocessor .hljs-title, +.hljs-pragma, +.hljs-shebang, +.profile .hljs-summary, +.diff, +.hljs-pi, +.hljs-doctype, +.hljs-tag, +.hljs-template_comment, +.css .hljs-rules, +.tex .hljs-special +{ + color: #444; +} + +.hljs-string, +.hljs-symbol, +.diff .hljs-change, +.hljs-regexp, +.xml .hljs-attribute, +.smalltalk .hljs-char, +.xml .hljs-value, +.ini .hljs-value, +.clojure .hljs-attribute, +.coffeescript .hljs-attribute +{ + color: #ffcc33; +} + +.hljs-number, +.hljs-addition +{ + color: #00cc66; +} + +.hljs-built_in, +.hljs-literal, +.vhdl .hljs-typename, +.go .hljs-constant, +.go .hljs-typename, +.ini .hljs-keyword, +.lua .hljs-title, +.perl .hljs-variable, +.php .hljs-variable, +.mel .hljs-variable, +.django .hljs-variable, +.css .funtion, +.smalltalk .method, +.hljs-hexcolor, +.hljs-important, +.hljs-flow, +.hljs-inheritance, +.parser3 .hljs-variable +{ + color: #32AAEE; +} + +.hljs-keyword, +.hljs-tag .hljs-title, +.css .hljs-tag, +.css .hljs-class, +.css .hljs-id, +.css .hljs-pseudo, +.css .hljs-attr_selector, +.lisp .hljs-title, +.clojure .hljs-built_in, +.hljs-winutils, +.tex .hljs-command, +.hljs-request, +.hljs-status +{ + color: #6644aa; +} + +.hljs-title, +.ruby .hljs-constant, +.vala .hljs-constant, +.hljs-parent, +.hljs-deletion, +.hljs-template_tag, +.css .hljs-keyword, +.objectivec .hljs-class .hljs-id, +.smalltalk .hljs-class, +.lisp .hljs-keyword, +.apache .hljs-tag, +.nginx .hljs-variable, +.hljs-envvar, +.bash .hljs-variable, +.go .hljs-built_in, +.vbscript .hljs-built_in, +.lua .hljs-built_in, +.rsl .hljs-built_in, +.tail, +.avrasm .hljs-label, +.tex .hljs-formula, +.tex .hljs-formula * +{ + color: #bb1166; +} + +.hljs-yardoctag, +.hljs-phpdoc, +.profile .hljs-header, +.ini .hljs-title, +.apache .hljs-tag, +.parser3 .hljs-title +{ + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata +{ + opacity: 0.6; +} + +.hljs, +.javascript, +.css, +.xml, +.hljs-subst, +.diff .hljs-chunk, +.css .hljs-value, +.css .hljs-attribute, +.lisp .hljs-string, +.lisp .hljs-number, +.tail .hljs-params, +.hljs-container, +.haskell *, +.erlang *, +.erlang_repl * +{ + color: #aaa; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ascetic.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ascetic.css new file mode 100644 index 00000000..031c88a0 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ascetic.css @@ -0,0 +1,50 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; padding: 0.5em; + background: white; color: black; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-filter .hljs-argument, +.hljs-addition, +.hljs-change, +.apache .hljs-tag, +.apache .hljs-cbracket, +.nginx .hljs-built_in, +.tex .hljs-formula { + color: #888; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-shebang, +.hljs-doctype, +.hljs-pi, +.hljs-javadoc, +.hljs-deletion, +.apache .hljs-sqbracket { + color: #CCC; +} + +.hljs-keyword, +.hljs-tag .hljs-title, +.ini .hljs-title, +.lisp .hljs-title, +.clojure .hljs-title, +.http .hljs-title, +.nginx .hljs-title, +.css .hljs-tag, +.hljs-winutils, +.hljs-flow, +.apache .hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.dark.css new file mode 100644 index 00000000..27796015 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.dark.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Dune Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Dune Dark Comment */ +.hljs-comment, +.hljs-title { + color: #999580; +} + +/* Atelier Dune Dark Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d73737; +} + +/* Atelier Dune Dark Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #b65611; +} + +/* Atelier Dune Dark Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #cfb017; +} + +/* Atelier Dune Dark Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #60ac39; +} + +/* Atelier Dune Dark Aqua */ +.css .hljs-hexcolor { + color: #1fad83; +} + +/* Atelier Dune Dark Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #6684e1; +} + +/* Atelier Dune Dark Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b854d4; +} + +.hljs { + display: block; + background: #292824; + color: #a6a28c; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.light.css new file mode 100644 index 00000000..11c74232 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-dune.light.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Dune Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Dune Light Comment */ +.hljs-comment, +.hljs-title { + color: #7d7a68; +} + +/* Atelier Dune Light Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d73737; +} + +/* Atelier Dune Light Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #b65611; +} + +/* Atelier Dune Light Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #cfb017; +} + +/* Atelier Dune Light Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #60ac39; +} + +/* Atelier Dune Light Aqua */ +.css .hljs-hexcolor { + color: #1fad83; +} + +/* Atelier Dune Light Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #6684e1; +} + +/* Atelier Dune Light Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b854d4; +} + +.hljs { + display: block; + background: #fefbec; + color: #6e6b5e; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.dark.css new file mode 100644 index 00000000..c1f7211f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.dark.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Forest Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Forest Dark Comment */ +.hljs-comment, +.hljs-title { + color: #9c9491; +} + +/* Atelier Forest Dark Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #f22c40; +} + +/* Atelier Forest Dark Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #df5320; +} + +/* Atelier Forest Dark Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #d5911a; +} + +/* Atelier Forest Dark Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #5ab738; +} + +/* Atelier Forest Dark Aqua */ +.css .hljs-hexcolor { + color: #00ad9c; +} + +/* Atelier Forest Dark Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #407ee7; +} + +/* Atelier Forest Dark Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6666ea; +} + +.hljs { + display: block; + background: #2c2421; + color: #a8a19f; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.light.css new file mode 100644 index 00000000..806ba739 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-forest.light.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Forest Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Forest Light Comment */ +.hljs-comment, +.hljs-title { + color: #766e6b; +} + +/* Atelier Forest Light Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #f22c40; +} + +/* Atelier Forest Light Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #df5320; +} + +/* Atelier Forest Light Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #d5911a; +} + +/* Atelier Forest Light Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #5ab738; +} + +/* Atelier Forest Light Aqua */ +.css .hljs-hexcolor { + color: #00ad9c; +} + +/* Atelier Forest Light Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #407ee7; +} + +/* Atelier Forest Light Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6666ea; +} + +.hljs { + display: block; + background: #f1efee; + color: #68615e; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.dark.css new file mode 100644 index 00000000..36706698 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.dark.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Heath Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Heath Dark Comment */ +.hljs-comment, +.hljs-title { + color: #9e8f9e; +} + +/* Atelier Heath Dark Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ca402b; +} + +/* Atelier Heath Dark Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #a65926; +} + +/* Atelier Heath Dark Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #bb8a35; +} + +/* Atelier Heath Dark Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #379a37; +} + +/* Atelier Heath Dark Aqua */ +.css .hljs-hexcolor { + color: #159393; +} + +/* Atelier Heath Dark Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #516aec; +} + +/* Atelier Heath Dark Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #7b59c0; +} + +.hljs { + display: block; + background: #292329; + color: #ab9bab; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.light.css new file mode 100644 index 00000000..e73a0b8b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-heath.light.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Heath Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Heath Light Comment */ +.hljs-comment, +.hljs-title { + color: #776977; +} + +/* Atelier Heath Light Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ca402b; +} + +/* Atelier Heath Light Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #a65926; +} + +/* Atelier Heath Light Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #bb8a35; +} + +/* Atelier Heath Light Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #379a37; +} + +/* Atelier Heath Light Aqua */ +.css .hljs-hexcolor { + color: #159393; +} + +/* Atelier Heath Light Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #516aec; +} + +/* Atelier Heath Light Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #7b59c0; +} + +.hljs { + display: block; + background: #f7f3f7; + color: #695d69; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.dark.css new file mode 100644 index 00000000..8506246d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.dark.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Lakeside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside/) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Lakeside Dark Comment */ +.hljs-comment, +.hljs-title { + color: #7195a8; +} + +/* Atelier Lakeside Dark Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d22d72; +} + +/* Atelier Lakeside Dark Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #935c25; +} + +/* Atelier Lakeside Dark Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #8a8a0f; +} + +/* Atelier Lakeside Dark Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #568c3b; +} + +/* Atelier Lakeside Dark Aqua */ +.css .hljs-hexcolor { + color: #2d8f6f; +} + +/* Atelier Lakeside Dark Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #257fad; +} + +/* Atelier Lakeside Dark Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #5d5db1; +} + +.hljs { + display: block; + background: #1f292e; + color: #7ea2b4; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.light.css new file mode 100644 index 00000000..006ae6d9 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-lakeside.light.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Lakeside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside/) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Lakeside Light Comment */ +.hljs-comment, +.hljs-title { + color: #5a7b8c; +} + +/* Atelier Lakeside Light Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d22d72; +} + +/* Atelier Lakeside Light Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #935c25; +} + +/* Atelier Lakeside Light Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #8a8a0f; +} + +/* Atelier Lakeside Light Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #568c3b; +} + +/* Atelier Lakeside Light Aqua */ +.css .hljs-hexcolor { + color: #2d8f6f; +} + +/* Atelier Lakeside Light Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #257fad; +} + +/* Atelier Lakeside Light Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #5d5db1; +} + +.hljs { + display: block; + background: #ebf8ff; + color: #516d7b; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.dark.css new file mode 100644 index 00000000..cbea6ed4 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.dark.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Seaside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside/) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Seaside Dark Comment */ +.hljs-comment, +.hljs-title { + color: #809980; +} + +/* Atelier Seaside Dark Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #e6193c; +} + +/* Atelier Seaside Dark Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #87711d; +} + +/* Atelier Seaside Dark Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #c3c322; +} + +/* Atelier Seaside Dark Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #29a329; +} + +/* Atelier Seaside Dark Aqua */ +.css .hljs-hexcolor { + color: #1999b3; +} + +/* Atelier Seaside Dark Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #3d62f5; +} + +/* Atelier Seaside Dark Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #ad2bee; +} + +.hljs { + display: block; + background: #242924; + color: #8ca68c; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.light.css new file mode 100644 index 00000000..159121e7 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/atelier-seaside.light.css @@ -0,0 +1,93 @@ +/* Base16 Atelier Seaside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside/) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Atelier Seaside Light Comment */ +.hljs-comment, +.hljs-title { + color: #687d68; +} + +/* Atelier Seaside Light Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #e6193c; +} + +/* Atelier Seaside Light Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #87711d; +} + +/* Atelier Seaside Light Yellow */ +.hljs-ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #c3c322; +} + +/* Atelier Seaside Light Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #29a329; +} + +/* Atelier Seaside Light Aqua */ +.css .hljs-hexcolor { + color: #1999b3; +} + +/* Atelier Seaside Light Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #3d62f5; +} + +/* Atelier Seaside Light Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #ad2bee; +} + +.hljs { + display: block; + background: #f0fff0; + color: #5e6e5e; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_paper.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_paper.css new file mode 100644 index 00000000..f9541c3a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_paper.css @@ -0,0 +1,105 @@ +/* + +Brown Paper style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; padding: 0.5em; + background:#b7a68e url(./brown_papersq.png); +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.tex .hljs-special, +.hljs-request, +.hljs-status { + color:#005599; + font-weight:bold; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-keyword { + color: #363C69; +} + +.hljs-string, +.hljs-title, +.haskell .hljs-type, +.hljs-tag .hljs-value, +.css .hljs-rules .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-javadoc, +.ruby .hljs-string, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-number { + color: #2C009F; +} + +.hljs-comment, +.java .hljs-annotation, +.python .hljs-decorator, +.hljs-template_comment, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula { + color: #802022; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-phpdoc, +.hljs-title, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-command { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.8; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_papersq.png b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/brown_papersq.png new file mode 100644 index 0000000000000000000000000000000000000000..3813903dbf9fa7b1fb5bd11d9534c06667d9056f GIT binary patch literal 18198 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
    G3S-EN>9CM!{YB*|;6wg-K3V?)eR((z#1 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 literal 0 HcmV?d00001 diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/dark.css new file mode 100644 index 00000000..e479d0a6 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/dark.css @@ -0,0 +1,105 @@ +/* + +Dark style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #444; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.tex .hljs-special { + color: white; +} + +.hljs, +.hljs-subst { + color: #DDD; +} + +.hljs-string, +.hljs-title, +.haskell .hljs-type, +.ini .hljs-title, +.hljs-tag .hljs-value, +.css .hljs-rules .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-javadoc, +.ruby .hljs-string, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.coffeescript .hljs-attribute { + color: #D88; +} + +.hljs-comment, +.java .hljs-annotation, +.python .hljs-decorator, +.hljs-template_comment, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #777; +} + +.hljs-keyword, +.hljs-literal, +.hljs-title, +.css .hljs-id, +.hljs-phpdoc, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-special, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/default.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/default.css new file mode 100644 index 00000000..3d8485b4 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/default.css @@ -0,0 +1,153 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #F0F0F0; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-title, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-constant, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-rules .hljs-value .hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.haml .hljs-symbol, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-aggregate, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.tex .hljs-special, +.erlang_repl .hljs-function_or_atom, +.asciidoc .hljs-header, +.markdown .hljs-header, +.coffeescript .hljs-attribute { + color: #800; +} + +.smartquote, +.hljs-comment, +.hljs-annotation, +.hljs-template_comment, +.diff .hljs-header, +.hljs-chunk, +.asciidoc .hljs-blockquote, +.markdown .hljs-blockquote { + color: #888; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.hljs-hexcolor, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.go .hljs-constant, +.hljs-change, +.lasso .hljs-variable, +.makefile .hljs-variable, +.asciidoc .hljs-bullet, +.markdown .hljs-bullet, +.asciidoc .hljs-link_url, +.markdown .hljs-link_url { + color: #080; +} + +.hljs-label, +.hljs-javadoc, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-important, +.hljs-pseudo, +.hljs-pi, +.haml .hljs-bullet, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula, +.erlang_repl .hljs-reserved, +.hljs-prompt, +.asciidoc .hljs-link_label, +.markdown .hljs-link_label, +.vhdl .hljs-attribute, +.clojure .hljs-attribute, +.asciidoc .hljs-attribute, +.lasso .hljs-attribute, +.coffeescript .hljs-property, +.hljs-phony { + color: #88F +} + +.hljs-keyword, +.hljs-id, +.hljs-title, +.hljs-built_in, +.hljs-aggregate, +.css .hljs-tag, +.hljs-javadoctag, +.hljs-phpdoc, +.hljs-yardoctag, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.go .hljs-typename, +.tex .hljs-command, +.asciidoc .hljs-strong, +.markdown .hljs-strong, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.asciidoc .hljs-emphasis, +.markdown .hljs-emphasis { + font-style: italic; +} + +.nginx .hljs-built_in { + font-weight: normal; +} + +.coffeescript .javascript, +.javascript .xml, +.lasso .markup, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/docco.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/docco.css new file mode 100644 index 00000000..993fd268 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/docco.css @@ -0,0 +1,132 @@ +/* +Docco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars) +*/ + +.hljs { + display: block; padding: 0.5em; + color: #000; + background: #f8f8ff +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-javadoc { + color: #408080; + font-style: italic +} + +.hljs-keyword, +.assignment, +.hljs-literal, +.css .rule .hljs-keyword, +.hljs-winutils, +.javascript .hljs-title, +.lisp .hljs-title, +.hljs-subst { + color: #954121; +} + +.hljs-number, +.hljs-hexcolor { + color: #40a070 +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula { + color: #219161; +} + +.hljs-title, +.hljs-id { + color: #19469D; +} +.hljs-params { + color: #00F; +} + +.javascript .hljs-title, +.lisp .hljs-title, +.hljs-subst { + font-weight: normal +} + +.hljs-class .hljs-title, +.haskell .hljs-label, +.tex .hljs-command { + color: #458; + font-weight: bold +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-rules .hljs-property, +.django .hljs-tag .hljs-keyword { + color: #000080; + font-weight: normal +} + +.hljs-attribute, +.hljs-variable, +.instancevar, +.lisp .hljs-body { + color: #008080 +} + +.hljs-regexp { + color: #B68 +} + +.hljs-class { + color: #458; + font-weight: bold +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-symbol .hljs-keyword, +.ruby .hljs-symbol .keymethods, +.lisp .hljs-keyword, +.tex .hljs-special, +.input_number { + color: #990073 +} + +.builtin, +.constructor, +.hljs-built_in, +.lisp .hljs-title { + color: #0086b3 +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-doctype, +.hljs-shebang, +.hljs-cdata { + color: #999; + font-weight: bold +} + +.hljs-deletion { + background: #fdd +} + +.hljs-addition { + background: #dfd +} + +.diff .hljs-change { + background: #0086b3 +} + +.hljs-chunk { + color: #aaa +} + +.tex .hljs-formula { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/far.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/far.css new file mode 100644 index 00000000..ecac3c9a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/far.css @@ -0,0 +1,113 @@ +/* + +FAR Style (c) MajestiC + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #000080; +} + +.hljs, +.hljs-subst { + color: #0FF; +} + +.hljs-string, +.ruby .hljs-string, +.haskell .hljs-type, +.hljs-tag .hljs-value, +.css .hljs-rules .hljs-value, +.css .hljs-rules .hljs-value .hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.clojure .hljs-title, +.coffeescript .hljs-attribute { + color: #FF0; +} + +.hljs-keyword, +.css .hljs-id, +.hljs-title, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.xml .hljs-tag .hljs-title, +.hljs-winutils, +.hljs-flow, +.hljs-change, +.hljs-envvar, +.bash .hljs-variable, +.tex .hljs-special, +.clojure .hljs-built_in { + color: #FFF; +} + +.hljs-comment, +.hljs-phpdoc, +.hljs-javadoc, +.java .hljs-annotation, +.hljs-template_comment, +.hljs-deletion, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #888; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.clojure .hljs-attribute { + color: #0F0; +} + +.python .hljs-decorator, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.xml .hljs-pi, +.diff .hljs-header, +.hljs-chunk, +.hljs-shebang, +.nginx .hljs-built_in, +.hljs-prompt { + color: #008080; +} + +.hljs-keyword, +.css .hljs-id, +.hljs-title, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.hljs-winutils, +.hljs-flow, +.apache .hljs-tag, +.nginx .hljs-built_in, +.tex .hljs-command, +.tex .hljs-special, +.hljs-request, +.hljs-status { + font-weight: bold; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/foundation.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/foundation.css new file mode 100644 index 00000000..bc8d4df4 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/foundation.css @@ -0,0 +1,133 @@ +/* +Description: Foundation 4 docs style for highlight.js +Author: Dan Allen +Website: http://foundation.zurb.com/docs/ +Version: 1.0 +Date: 2013-04-02 +*/ + +.hljs { + display: block; padding: 0.5em; + background: #eee; +} + +.hljs-header, +.hljs-decorator, +.hljs-annotation { + color: #000077; +} + +.hljs-horizontal_rule, +.hljs-link_url, +.hljs-emphasis, +.hljs-attribute { + color: #070; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-link_label, +.hljs-strong, +.hljs-value, +.hljs-string, +.scss .hljs-value .hljs-string { + color: #d14; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-blockquote, +.hljs-comment { + color: #998; + font-style: italic; +} + +.asciidoc .hljs-title, +.hljs-function .hljs-title { + color: #900; +} + +.hljs-class { + color: #458; +} + +.hljs-id, +.hljs-pseudo, +.hljs-constant, +.hljs-hexcolor { + color: teal; +} + +.hljs-variable { + color: #336699; +} + +.hljs-bullet, +.hljs-javadoc { + color: #997700; +} + +.hljs-pi, +.hljs-doctype { + color: #3344bb; +} + +.hljs-code, +.hljs-number { + color: #099; +} + +.hljs-important { + color: #f00; +} + +.smartquote, +.hljs-label { + color: #970; +} + +.hljs-preprocessor, +.hljs-pragma { + color: #579; +} + +.hljs-reserved, +.hljs-keyword, +.scss .hljs-value { + color: #000; +} + +.hljs-regexp { + background-color: #fff0ff; + color: #880088; +} + +.hljs-symbol { + color: #990073; +} + +.hljs-symbol .hljs-string { + color: #a60; +} + +.hljs-tag { + color: #007700; +} + +.hljs-at_rule, +.hljs-at_rule .hljs-keyword { + color: #088; +} + +.hljs-at_rule .hljs-preprocessor { + color: #808; +} + +.scss .hljs-tag, +.scss .hljs-attribute { + color: #339; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/github.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/github.css new file mode 100644 index 00000000..71967a37 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/github.css @@ -0,0 +1,125 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; padding: 0.5em; + color: #333; + background: #f8f8f8 +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-javadoc { + color: #998; + font-style: italic +} + +.hljs-keyword, +.css .rule .hljs-keyword, +.hljs-winutils, +.javascript .hljs-title, +.nginx .hljs-title, +.hljs-subst, +.hljs-request, +.hljs-status { + color: #333; + font-weight: bold +} + +.hljs-number, +.hljs-hexcolor, +.ruby .hljs-constant { + color: #099; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula { + color: #d14 +} + +.hljs-title, +.hljs-id, +.coffeescript .hljs-params, +.scss .hljs-preprocessor { + color: #900; + font-weight: bold +} + +.javascript .hljs-title, +.lisp .hljs-title, +.clojure .hljs-title, +.hljs-subst { + font-weight: normal +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.vhdl .hljs-literal, +.tex .hljs-command { + color: #458; + font-weight: bold +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-rules .hljs-property, +.django .hljs-tag .hljs-keyword { + color: #000080; + font-weight: normal +} + +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body { + color: #008080 +} + +.hljs-regexp { + color: #009926 +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.lisp .hljs-keyword, +.tex .hljs-special, +.hljs-prompt { + color: #990073 +} + +.hljs-built_in, +.lisp .hljs-title, +.clojure .hljs-built_in { + color: #0086b3 +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-doctype, +.hljs-shebang, +.hljs-cdata { + color: #999; + font-weight: bold +} + +.hljs-deletion { + background: #fdd +} + +.hljs-addition { + background: #dfd +} + +.diff .hljs-change { + background: #0086b3 +} + +.hljs-chunk { + color: #aaa +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/googlecode.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/googlecode.css new file mode 100644 index 00000000..45b8b3bf --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/googlecode.css @@ -0,0 +1,147 @@ +/* + +Google Code style (c) Aahan Krish + +*/ + +.hljs { + display: block; padding: 0.5em; + background: white; color: black; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc, +.hljs-comment * { + color: #800; +} + +.hljs-keyword, +.method, +.hljs-list .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.hljs-tag .hljs-title, +.setting .hljs-value, +.hljs-winutils, +.tex .hljs-command, +.http .hljs-title, +.hljs-request, +.hljs-status { + color: #008; +} + +.hljs-envvar, +.tex .hljs-special { + color: #660; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.hljs-regexp, +.coffeescript .hljs-attribute { + color: #080; +} + +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.ini .hljs-title, +.hljs-shebang, +.hljs-prompt, +.hljs-hexcolor, +.hljs-rules .hljs-value, +.css .hljs-value .hljs-number, +.hljs-literal, +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-number, +.css .hljs-function, +.clojure .hljs-attribute { + color: #066; +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.smalltalk .hljs-class, +.hljs-javadoctag, +.hljs-yardoctag, +.hljs-phpdoc, +.hljs-typename, +.hljs-tag .hljs-attribute, +.hljs-doctype, +.hljs-class .hljs-id, +.hljs-built_in, +.setting, +.hljs-params, +.hljs-variable, +.clojure .hljs-title { + color: #606; +} + +.css .hljs-tag, +.hljs-rules .hljs-property, +.hljs-pseudo, +.hljs-subst { + color: #000; +} + +.css .hljs-class, +.css .hljs-id { + color: #9B703F; +} + +.hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; +} + +.hljs-rules .hljs-keyword { + color: #C5AF75; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9B859D; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #444; +} + +.tex .hljs-formula { + background-color: #EEE; + font-style: italic; +} + +.diff .hljs-header, +.hljs-chunk { + color: #808080; + font-weight: bold; +} + +.diff .hljs-change { + background-color: #BCCFF9; +} + +.hljs-addition { + background-color: #BAEEBA; +} + +.hljs-deletion { + background-color: #FFC8BD; +} + +.hljs-comment .hljs-yardoctag { + font-weight: bold; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/idea.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/idea.css new file mode 100644 index 00000000..77352f4b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/idea.css @@ -0,0 +1,122 @@ +/* + +Intellij Idea-like styling (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; padding: 0.5em; + color: #000; + background: #fff; +} + +.hljs-subst, +.hljs-title { + font-weight: normal; + color: #000; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc, +.diff .hljs-header { + color: #808080; + font-style: italic; +} + +.hljs-annotation, +.hljs-decorator, +.hljs-preprocessor, +.hljs-pragma, +.hljs-doctype, +.hljs-pi, +.hljs-chunk, +.hljs-shebang, +.apache .hljs-cbracket, +.hljs-prompt, +.http .hljs-title { + color: #808000; +} + +.hljs-tag, +.hljs-pi { + background: #efefef; +} + +.hljs-tag .hljs-title, +.hljs-id, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-literal, +.hljs-keyword, +.hljs-hexcolor, +.css .hljs-function, +.ini .hljs-title, +.css .hljs-class, +.hljs-list .hljs-title, +.clojure .hljs-title, +.nginx .hljs-title, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; + color: #000080; +} + +.hljs-attribute, +.hljs-rules .hljs-keyword, +.hljs-number, +.hljs-date, +.hljs-regexp, +.tex .hljs-special { + font-weight: bold; + color: #0000ff; +} + +.hljs-number, +.hljs-regexp { + font-weight: normal; +} + +.hljs-string, +.hljs-value, +.hljs-filter .hljs-argument, +.css .hljs-function .hljs-params, +.apache .hljs-tag { + color: #008000; + font-weight: bold; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-char, +.tex .hljs-formula { + color: #000; + background: #d0eded; + font-style: italic; +} + +.hljs-phpdoc, +.hljs-yardoctag, +.hljs-javadoctag { + text-decoration: underline; +} + +.hljs-variable, +.hljs-envvar, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #660e7a; +} + +.hljs-addition { + background: #baeeba; +} + +.hljs-deletion { + background: #ffc8bd; +} + +.diff .hljs-change { + background: #bccff9; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ir_black.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ir_black.css new file mode 100644 index 00000000..cc64ef5c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/ir_black.css @@ -0,0 +1,105 @@ +/* + IR_Black style (c) Vasily Mikhailitchenko +*/ + +.hljs { + display: block; padding: 0.5em; + background: #000; color: #f8f8f8; +} + +.hljs-shebang, +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc { + color: #7c7c7c; +} + +.hljs-keyword, +.hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status, +.clojure .hljs-attribute { + color: #96CBFE; +} + +.hljs-sub .hljs-keyword, +.method, +.hljs-list .hljs-title, +.nginx .hljs-title { + color: #FFFFB6; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.coffeescript .hljs-attribute { + color: #A8FF60; +} + +.hljs-subst { + color: #DAEFA3; +} + +.hljs-regexp { + color: #E9C062; +} + +.hljs-title, +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-decorator, +.tex .hljs-special, +.haskell .hljs-type, +.hljs-constant, +.smalltalk .hljs-class, +.hljs-javadoctag, +.hljs-yardoctag, +.hljs-phpdoc, +.nginx .hljs-built_in { + color: #FFFFB6; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-number, +.hljs-variable, +.vbscript, +.hljs-literal { + color: #C6C5FE; +} + +.css .hljs-tag { + color: #96CBFE; +} + +.css .hljs-rules .hljs-property, +.css .hljs-id { + color: #FFFFB6; +} + +.css .hljs-class { + color: #FFF; +} + +.hljs-hexcolor { + color: #C6C5FE; +} + +.hljs-number { + color:#FF73FD; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.7; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/magula.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/magula.css new file mode 100644 index 00000000..cafe3d3e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/magula.css @@ -0,0 +1,123 @@ +/* +Description: Magula style for highligh.js +Author: Ruslan Keba +Website: http://rukeba.com/ +Version: 1.0 +Date: 2009-01-03 +Music: Aphex Twin / Xtal +*/ + +.hljs { + display: block; padding: 0.5em; + background-color: #f4f4f4; +} + +.hljs, +.hljs-subst, +.lisp .hljs-title, +.clojure .hljs-built_in { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-rules .hljs-value .hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-aggregate, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.apache .hljs-cbracket, +.coffeescript .hljs-attribute { + color: #050; +} + +.hljs-comment, +.hljs-annotation, +.hljs-template_comment, +.diff .hljs-header, +.hljs-chunk { + color: #777; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.hljs-change, +.tex .hljs-special { + color: #800; +} + +.hljs-label, +.hljs-javadoc, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula, +.hljs-prompt, +.clojure .hljs-attribute { + color: #00e; +} + +.hljs-keyword, +.hljs-id, +.hljs-phpdoc, +.hljs-title, +.hljs-built_in, +.hljs-aggregate, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.xml .hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; + color: navy; +} + +.nginx .hljs-built_in { + font-weight: normal; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + +/* --- */ +.apache .hljs-tag { + font-weight: bold; + color: blue; +} + diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/mono-blue.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/mono-blue.css new file mode 100644 index 00000000..4152d82d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/mono-blue.css @@ -0,0 +1,62 @@ +/* + Five-color theme from a single blue hue. +*/ +.hljs { + display: block; padding: 0.5em; + background: #EAEEF3; color: #00193A; +} + +.hljs-keyword, +.hljs-title, +.hljs-important, +.hljs-request, +.hljs-header, +.hljs-javadoctag { + font-weight: bold; +} + +.hljs-comment, +.hljs-chunk, +.hljs-template_comment { + color: #738191; +} + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-built_in, +.hljs-literal, +.hljs-filename, +.hljs-value, +.hljs-addition, +.hljs-tag, +.hljs-argument, +.hljs-link_label, +.hljs-blockquote, +.hljs-header { + color: #0048AB; +} + +.hljs-decorator, +.hljs-prompt, +.hljs-yardoctag, +.hljs-subst, +.hljs-symbol, +.hljs-doctype, +.hljs-regexp, +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-attribute, +.hljs-attr_selector, +.hljs-javadoc, +.hljs-xmlDocTag, +.hljs-deletion, +.hljs-shebang, +.hljs-string .hljs-variable, +.hljs-link_url, +.hljs-bullet, +.hljs-sqbracket, +.hljs-phony { + color: #4C81C9; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai.css new file mode 100644 index 00000000..4e49befd --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai.css @@ -0,0 +1,127 @@ +/* +Monokai style - ported by Luigi Maselli - http://grigio.org +*/ + +.hljs { + display: block; padding: 0.5em; + background: #272822; +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-keyword, +.hljs-literal, +.hljs-strong, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.tex .hljs-special { + color: #F92672; +} + +.hljs { + color: #DDD; +} + +.hljs .hljs-constant, +.asciidoc .hljs-code { + color: #66D9EF; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-header { + color: white; +} + +.hljs-link_label, +.hljs-attribute, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-value, +.hljs-regexp { + color: #BF79DB; +} + +.hljs-link_url, +.hljs-tag .hljs-value, +.hljs-string, +.hljs-bullet, +.hljs-subst, +.hljs-title, +.hljs-emphasis, +.haskell .hljs-type, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-javadoc, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt { + color: #A6E22E; +} + +.hljs-comment, +.java .hljs-annotation, +.smartquote, +.hljs-blockquote, +.hljs-horizontal_rule, +.python .hljs-decorator, +.hljs-template_comment, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #75715E; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-phpdoc, +.hljs-title, +.hljs-header, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-special, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai_sublime.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai_sublime.css new file mode 100644 index 00000000..7b0eb2e3 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai_sublime.css @@ -0,0 +1,149 @@ +/* + +Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ + +*/ + +.hljs { + display: block; + padding: 0.5em; + background: #23241f; +} + +.hljs, +.hljs-tag, +.css .hljs-rules, +.css .hljs-value, +.css .hljs-function +.hljs-preprocessor, +.hljs-pragma { + color: #f8f8f2; +} + +.hljs-strongemphasis, +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-blockquote, +.hljs-horizontal_rule, +.hljs-number, +.hljs-regexp, +.alias .hljs-keyword, +.hljs-literal, +.hljs-hexcolor { + color: #ae81ff; +} + +.hljs-tag .hljs-value, +.hljs-code, +.hljs-title, +.css .hljs-class, +.hljs-class .hljs-title:last-child { + color: #a6e22e; +} + +.hljs-link_url { + font-size: 80%; +} + +.hljs-strong, +.hljs-strongemphasis { + font-weight: bold; +} + +.hljs-emphasis, +.hljs-strongemphasis, +.hljs-class .hljs-title:last-child { + font-style: italic; +} + +.hljs-keyword, +.hljs-function, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.tex .hljs-special, +.hljs-header, +.hljs-attribute, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-tag .hljs-title, +.hljs-value, +.alias .hljs-keyword:first-child, +.css .hljs-tag, +.css .unit, +.css .hljs-important { + color: #F92672; +} + +.hljs-function .hljs-keyword, +.hljs-class .hljs-keyword:first-child, +.hljs-constant, +.css .hljs-attribute { + color: #66d9ef; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title { + color: #f8f8f2; +} + +.hljs-string, +.css .hljs-id, +.hljs-subst, +.haskell .hljs-type, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.hljs-link_label, +.hljs-link_url { + color: #e6db74; +} + +.hljs-comment, +.hljs-javadoc, +.java .hljs-annotation, +.python .hljs-decorator, +.hljs-template_comment, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #75715e; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata, +.xml .php, +.php .xml { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css new file mode 100644 index 00000000..1174e4c1 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css @@ -0,0 +1,154 @@ +/** + * Obsidian style + * ported by Alexander Marenin (http://github.com/ioncreature) + */ + +.hljs { + display: block; padding: 0.5em; + background: #282B2E; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.css .hljs-id, +.tex .hljs-special { + color: #93C763; +} + +.hljs-number { + color: #FFCD22; +} + +.hljs { + color: #E0E2E4; +} + +.css .hljs-tag, +.css .hljs-pseudo { + color: #D0D2B5; +} + +.hljs-attribute, +.hljs .hljs-constant { + color: #668BB0; +} + +.xml .hljs-attribute { + color: #B3B689; +} + +.xml .hljs-tag .hljs-value { + color: #E8E2B7; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-header { + color: white; +} + +.hljs-class, +.hljs-hexcolor { + color: #93C763; +} + +.hljs-regexp { + color: #D39745; +} + +.hljs-at_rule, +.hljs-at_rule .hljs-keyword { + color: #A082BD; +} + +.hljs-doctype { + color: #557182; +} + +.hljs-link_url, +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-bullet, +.hljs-subst, +.hljs-emphasis, +.haskell .hljs-type, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-javadoc, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt { + color: #8CBBAD; +} + +.hljs-string { + color: #EC7600; +} + +.hljs-comment, +.java .hljs-annotation, +.hljs-blockquote, +.hljs-horizontal_rule, +.python .hljs-decorator, +.hljs-template_comment, +.hljs-pi, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #818E96; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-phpdoc, +.hljs-title, +.hljs-header, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-special, +.hljs-request, +.hljs-at_rule .hljs-keyword, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.dark.css new file mode 100644 index 00000000..bbbccdd5 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.dark.css @@ -0,0 +1,93 @@ +/* + Paraíso (dark) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-title { + color: #8d8687; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f99b15; +} + +/* Paraíso Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #48b685; +} + +/* Paraíso Aqua */ +.css .hljs-hexcolor { + color: #5bc4bf; +} + +/* Paraíso Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #06b6ef; +} + +/* Paraíso Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #815ba4; +} + +.hljs { + display: block; + background: #2f1e2e; + color: #a39e9b; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.light.css new file mode 100644 index 00000000..494fcb4c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/paraiso.light.css @@ -0,0 +1,93 @@ +/* + Paraíso (light) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-title { + color: #776e71; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f99b15; +} + +/* Paraíso Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #48b685; +} + +/* Paraíso Aqua */ +.css .hljs-hexcolor { + color: #5bc4bf; +} + +/* Paraíso Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #06b6ef; +} + +/* Paraíso Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #815ba4; +} + +.hljs { + display: block; + background: #e7e9db; + color: #4f424c; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.css new file mode 100644 index 00000000..6ee925de --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.css @@ -0,0 +1,106 @@ +/* + +Pojoaque Style by Jason Tate +http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html +Based on Solarized Style from http://ethanschoonover.com/solarized + +*/ + +.hljs { + display: block; padding: 0.5em; + color: #DCCF8F; + background: url(./pojoaque.jpg) repeat scroll left top #181914; +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-doctype, +.lisp .hljs-string, +.hljs-javadoc { + color: #586e75; + font-style: italic; +} + +.hljs-keyword, +.css .rule .hljs-keyword, +.hljs-winutils, +.javascript .hljs-title, +.method, +.hljs-addition, +.css .hljs-tag, +.clojure .hljs-title, +.nginx .hljs-title { + color: #B64926; +} + +.hljs-number, +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor { + color: #468966; +} + +.hljs-title, +.hljs-localvars, +.hljs-function .hljs-title, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.lisp .hljs-title, +.clojure .hljs-built_in, +.hljs-identifier, +.hljs-id { + color: #FFB03B; +} + +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.haskell .hljs-type { + color: #b58900; +} + +.css .hljs-attribute { + color: #b89859; +} + +.css .hljs-number, +.css .hljs-hexcolor { + color: #DCCF8F; +} + +.css .hljs-class { + color: #d3a60c; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-important, +.hljs-subst, +.hljs-cdata { + color: #cb4b16; +} + +.hljs-deletion { + color: #dc322f; +} + +.tex .hljs-formula { + background: #073642; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.jpg b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/pojoaque.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9c07d4ab40b6d77e90ff69f0012bcd33b21d31c3 GIT binary patch literal 1186 zcmZXSe^8Tk9LK-kXFs3)f@f?)Cddzw3v4wdZyXQ;4x3=;Ja*N#%n9ik!UGmt9H3k0 zJST|5jOc(ID$FQt3C?jQZBws#kXolO1lg9Pba9BB=Q+UEBX!nY@6Uhl&+ofe$Q$y5 z@ci`~)&qzDP(lOiQ5p?p z(`j^e7!yUAVHk%K#^GQXn?s0=VLYCI$HRoe=xCuZ>A6A3@sxEP#XqNFpIb=0)KQ#Nss_tD17;m4@$JKL;LR|K|QF3f%!L5+s(9Ft8SQ zG|~pGpEGFW5Z|OA)-O@mNHy-g@7m8JTf?kl@vUKBGmw)Y*9sDRNr3PN!IKefWaydTe1D zjzpyzPnD3}hBNaS4aFX7=0&~I*Hu7#4au@qVBglH#-m;QFOx_`=j z{EqRY#Eh*yoWP^pa4H>8GH{rO?!_+xwL0(k4yL^D%^nBkJ*UI;Lx;ped8d|f*S_s@ z3~ilcRC(&NT#9Gn#UD;o^EYSMXDMf%XcUi3>;WXXD-QX3P9wMyP7eA&RS{)h5{??W3^Rq=goFJ>?lA~J- zdYe>!xvYLW*fPT0RK7wsJRg^?x#W1*GP9_f`6t>QD_X>0d!owyN>nO2?U5}|3?hX_UZYT@^>S!9eB~bZ9U`q;`U)@L670o1g z`Hd}h<_WRvUc|n*%v4Hbb-4tJD40iyF^q%g*&!6>hkYDvi-{Uc4yTM zzcthN4Z{ka!+F_KzYV#yWi;c^X^q6g`pD8cp?$Kl?hCz0s^a|mH%P!CF%*<6k^~i` zT5Mi-t5-frUcHkk^Qh}+N)Kz1&Bi95`oNc|quI>tUi~BY>xcF9(%tv2i{G6kE9*q~ qCoAGl20`)w0rdgp9H%Q=M5|p`hOhFz6$I%Y&ncY8>c?7PXyh+SL&XXJ literal 0 HcmV?d00001 diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/railscasts.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/railscasts.css new file mode 100644 index 00000000..6a380644 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/railscasts.css @@ -0,0 +1,182 @@ +/* + +Railscasts-like style (c) Visoft, Inc. (Damien White) + +*/ + +.hljs { + display: block; + padding: 0.5em; + background: #232323; + color: #E6E1DC; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc, +.hljs-shebang { + color: #BC9458; + font-style: italic; +} + +.hljs-keyword, +.ruby .hljs-function .hljs-keyword, +.hljs-request, +.hljs-status, +.nginx .hljs-title, +.method, +.hljs-list .hljs-title { + color: #C26230; +} + +.hljs-string, +.hljs-number, +.hljs-regexp, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.tex .hljs-command, +.markdown .hljs-link_label { + color: #A5C261; +} + +.hljs-subst { + color: #519F50; +} + +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-tag .hljs-title, +.hljs-doctype, +.hljs-sub .hljs-identifier, +.hljs-pi, +.input_number { + color: #E8BF6A; +} + +.hljs-identifier { + color: #D0D0FF; +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.smalltalk .hljs-class, +.hljs-javadoctag, +.hljs-yardoctag, +.hljs-phpdoc { + text-decoration: none; +} + +.hljs-constant { + color: #DA4939; +} + + +.hljs-symbol, +.hljs-built_in, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-symbol .hljs-identifier, +.markdown .hljs-link_url, +.hljs-attribute { + color: #6D9CBE; +} + +.markdown .hljs-link_url { + text-decoration: underline; +} + + + +.hljs-params, +.hljs-variable, +.clojure .hljs-attribute { + color: #D0D0FF; +} + +.css .hljs-tag, +.hljs-rules .hljs-property, +.hljs-pseudo, +.tex .hljs-special { + color: #CDA869; +} + +.css .hljs-class { + color: #9B703F; +} + +.hljs-rules .hljs-keyword { + color: #C5AF75; +} + +.hljs-rules .hljs-value { + color: #CF6A4C; +} + +.css .hljs-id { + color: #8B98AB; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9B859D; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #8996A8 !important; +} + +.hljs-hexcolor, +.css .hljs-value .hljs-number { + color: #A5C261; +} + +.hljs-title, +.hljs-decorator, +.css .hljs-function { + color: #FFC66D; +} + +.diff .hljs-header, +.hljs-chunk { + background-color: #2F33AB; + color: #E6E1DC; + display: inline-block; + width: 100%; +} + +.diff .hljs-change { + background-color: #4A410D; + color: #F8F8F8; + display: inline-block; + width: 100%; +} + +.hljs-addition { + background-color: #144212; + color: #E6E1DC; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + color: #E6E1DC; + display: inline-block; + width: 100%; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.7; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/rainbow.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/rainbow.css new file mode 100644 index 00000000..d9ffef6d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/rainbow.css @@ -0,0 +1,112 @@ +/* + +Style with support for rainbow parens + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #474949; color: #D1D9E1; +} + + +.hljs-body, +.hljs-collection { + color: #D1D9E1; +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-doctype, +.lisp .hljs-string, +.hljs-javadoc { + color: #969896; + font-style: italic; +} + +.hljs-keyword, +.clojure .hljs-attribute, +.hljs-winutils, +.javascript .hljs-title, +.hljs-addition, +.css .hljs-tag { + color: #cc99cc; +} + +.hljs-number { color: #f99157; } + +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor { + color: #8abeb7; +} + +.hljs-title, +.hljs-localvars, +.hljs-function .hljs-title, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.lisp .hljs-title, +.hljs-identifier +{ + color: #b5bd68; +} + +.hljs-class .hljs-keyword +{ + color: #f2777a; +} + +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.haskell .hljs-label, +.hljs-id, +.lisp .hljs-title, +.clojure .hljs-title .hljs-built_in { + color: #ffcc66; +} + +.hljs-tag .hljs-title, +.hljs-rules .hljs-property, +.django .hljs-tag .hljs-keyword, +.clojure .hljs-title .hljs-built_in { + font-weight: bold; +} + +.hljs-attribute, +.clojure .hljs-title { + color: #81a2be; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-important, +.hljs-subst, +.hljs-cdata { + color: #f99157; +} + +.hljs-deletion { + color: #dc322f; +} + +.tex .hljs-formula { + background: #eee8d5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.css new file mode 100644 index 00000000..98a3bd27 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.css @@ -0,0 +1,113 @@ +/* + +School Book style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; padding: 15px 0.5em 0.5em 30px; + font-size: 11px !important; + line-height:16px !important; +} + +pre{ + background:#f6f6ae url(./school_book.png); + border-top: solid 2px #d2e8b9; + border-bottom: solid 1px #d2e8b9; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title, +.tex .hljs-special { + color:#005599; + font-weight:bold; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-keyword { + color: #3E5915; +} + +.hljs-string, +.hljs-title, +.haskell .hljs-type, +.hljs-tag .hljs-value, +.css .hljs-rules .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.sql .hljs-aggregate, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-javadoc, +.ruby .hljs-string, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.nginx .hljs-built_in, +.tex .hljs-command, +.coffeescript .hljs-attribute { + color: #2C009F; +} + +.hljs-comment, +.java .hljs-annotation, +.python .hljs-decorator, +.hljs-template_comment, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket { + color: #E60415; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-phpdoc, +.hljs-title, +.haskell .hljs-type, +.vbscript .hljs-built_in, +.sql .hljs-aggregate, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.xml .hljs-tag .hljs-title, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.png b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/school_book.png new file mode 100644 index 0000000000000000000000000000000000000000..956e9790a0e2c079b3d568348ff3accd1d9cac30 GIT binary patch literal 486 zcmeAS@N?(olHy`uVBq!ia0y~yV7?7x3vjRjNjAS6Ga$v1?&#~tz_9*=IcwKTAYZb? zHKHUqKdq!Zu_%?nF(p4KRlzeiF+DXXH8G{K@MNkD0|R4)r;B4q#jQ7Ycl#YS5MfK$ z?b^fh#qmaEhFDxvyThwfhdfkOPApt1lr{NA;Vr%uzxJuVIyzm(ed_8_-0$LLU})H&o5Re&aDemE>EG#(|F^t9_pa-H z_Mf?rMVrs}-M?S|?ZdY@c6s41zy8~}@a{v&#Ea7V)wJ$+#K|u$5UvWCdFLwGac}6w{_s*=8A6L7Rfc|9gboFyt I=akR{0OLZ+qyPW_ literal 0 HcmV?d00001 diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_dark.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_dark.css new file mode 100644 index 00000000..f520533f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_dark.css @@ -0,0 +1,107 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + padding: 0.5em; + background: #002b36; + color: #839496; +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-doctype, +.hljs-pi, +.lisp .hljs-string, +.hljs-javadoc { + color: #586e75; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-winutils, +.method, +.hljs-addition, +.css .hljs-tag, +.hljs-request, +.hljs-status, +.nginx .hljs-title { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor, +.hljs-link_url { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-localvars, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.hljs-identifier, +.vhdl .hljs-literal, +.hljs-id, +.css .hljs-function { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.haskell .hljs-type, +.hljs-link_reference { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-preprocessor, +.hljs-preprocessor .hljs-keyword, +.hljs-pragma, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-subst, +.hljs-cdata, +.clojure .hljs-title, +.css .hljs-pseudo, +.hljs-header { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-deletion, +.hljs-important { + color: #dc322f; +} + +/* Solarized Violet */ +.hljs-link_label { + color: #6c71c4; +} + +.tex .hljs-formula { + background: #073642; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_light.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_light.css new file mode 100644 index 00000000..ad704741 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/solarized_light.css @@ -0,0 +1,107 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-doctype, +.hljs-pi, +.lisp .hljs-string, +.hljs-javadoc { + color: #93a1a1; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-winutils, +.method, +.hljs-addition, +.css .hljs-tag, +.hljs-request, +.hljs-status, +.nginx .hljs-title { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor, +.hljs-link_url { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-localvars, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.hljs-identifier, +.vhdl .hljs-literal, +.hljs-id, +.css .hljs-function { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.haskell .hljs-type, +.hljs-link_reference { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-preprocessor, +.hljs-preprocessor .hljs-keyword, +.hljs-pragma, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-subst, +.hljs-cdata, +.clojure .hljs-title, +.css .hljs-pseudo, +.hljs-header { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-deletion, +.hljs-important { + color: #dc322f; +} + +/* Solarized Violet */ +.hljs-link_label { + color: #6c71c4; +} + +.tex .hljs-formula { + background: #eee8d5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/sunburst.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/sunburst.css new file mode 100644 index 00000000..07b30c24 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/sunburst.css @@ -0,0 +1,160 @@ +/* + +Sunburst-like style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #000; color: #f8f8f8; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc { + color: #aeaeae; + font-style: italic; +} + +.hljs-keyword, +.ruby .hljs-function .hljs-keyword, +.hljs-request, +.hljs-status, +.nginx .hljs-title { + color: #E28964; +} + +.hljs-function .hljs-keyword, +.hljs-sub .hljs-keyword, +.method, +.hljs-list .hljs-title { + color: #99CF50; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.tex .hljs-command, +.coffeescript .hljs-attribute { + color: #65B042; +} + +.hljs-subst { + color: #DAEFA3; +} + +.hljs-regexp { + color: #E9C062; +} + +.hljs-title, +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.hljs-shebang, +.hljs-prompt { + color: #89BDFF; +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.smalltalk .hljs-class, +.hljs-javadoctag, +.hljs-yardoctag, +.hljs-phpdoc { + text-decoration: underline; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-number { + color: #3387CC; +} + +.hljs-params, +.hljs-variable, +.clojure .hljs-attribute { + color: #3E87E3; +} + +.css .hljs-tag, +.hljs-rules .hljs-property, +.hljs-pseudo, +.tex .hljs-special { + color: #CDA869; +} + +.css .hljs-class { + color: #9B703F; +} + +.hljs-rules .hljs-keyword { + color: #C5AF75; +} + +.hljs-rules .hljs-value { + color: #CF6A4C; +} + +.css .hljs-id { + color: #8B98AB; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9B859D; +} + +.hljs-preprocessor, +.hljs-pragma { + color: #8996A8; +} + +.hljs-hexcolor, +.css .hljs-value .hljs-number { + color: #DD7B3B; +} + +.css .hljs-function { + color: #DAD085; +} + +.diff .hljs-header, +.hljs-chunk, +.tex .hljs-formula { + background-color: #0E2231; + color: #F8F8F8; + font-style: italic; +} + +.diff .hljs-change { + background-color: #4A410D; + color: #F8F8F8; +} + +.hljs-addition { + background-color: #253B22; + color: #F8F8F8; +} + +.hljs-deletion { + background-color: #420E09; + color: #F8F8F8; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-blue.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-blue.css new file mode 100644 index 00000000..dfe26752 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-blue.css @@ -0,0 +1,93 @@ +/* Tomorrow Night Blue Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-title { + color: #7285b7; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ff9da4; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #ffc58f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #ffeead; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #d1f1a9; +} + +/* Tomorrow Aqua */ +.css .hljs-hexcolor { + color: #99ffff; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #bbdaff; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #ebbbff; +} + +.hljs { + display: block; + background: #002451; + color: white; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-bright.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-bright.css new file mode 100644 index 00000000..4ad5d25f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-bright.css @@ -0,0 +1,92 @@ +/* Tomorrow Night Bright Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-title { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d54e53; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #e78c45; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #e7c547; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b9ca4a; +} + +/* Tomorrow Aqua */ +.css .hljs-hexcolor { + color: #70c0b1; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #7aa6da; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #c397d8; +} + +.hljs { + display: block; + background: black; + color: #eaeaea; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-eighties.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-eighties.css new file mode 100644 index 00000000..08b49c62 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night-eighties.css @@ -0,0 +1,92 @@ +/* Tomorrow Night Eighties Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-title { + color: #999999; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #f2777a; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f99157; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #ffcc66; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #99cc99; +} + +/* Tomorrow Aqua */ +.css .hljs-hexcolor { + color: #66cccc; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #6699cc; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #cc99cc; +} + +.hljs { + display: block; + background: #2d2d2d; + color: #cccccc; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night.css new file mode 100644 index 00000000..c269b17e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow-night.css @@ -0,0 +1,93 @@ +/* Tomorrow Night Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-title { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #de935f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b5bd68; +} + +/* Tomorrow Aqua */ +.css .hljs-hexcolor { + color: #8abeb7; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b294bb; +} + +.hljs { + display: block; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow.css new file mode 100644 index 00000000..3bdead60 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/tomorrow.css @@ -0,0 +1,90 @@ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-title { + color: #8e908c; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #c82829; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f5871f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rules .hljs-attribute { + color: #eab700; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #718c00; +} + +/* Tomorrow Aqua */ +.css .hljs-hexcolor { + color: #3e999f; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #4271ae; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #8959a8; +} + +.hljs { + display: block; + background: white; + color: #4d4d4c; + padding: 0.5em; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/vs.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/vs.css new file mode 100644 index 00000000..bf33f0fb --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/vs.css @@ -0,0 +1,89 @@ +/* + +Visual Studio-like style based on original C# coloring by Jason Diamond + +*/ +.hljs { + display: block; padding: 0.5em; + background: white; color: black; +} + +.hljs-comment, +.hljs-annotation, +.hljs-template_comment, +.diff .hljs-header, +.hljs-chunk, +.apache .hljs-cbracket { + color: #008000; +} + +.hljs-keyword, +.hljs-id, +.hljs-built_in, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.tex .hljs-command, +.hljs-request, +.hljs-status, +.nginx .hljs-title, +.xml .hljs-tag, +.xml .hljs-tag .hljs-value { + color: #00f; +} + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-rules .hljs-value .hljs-number, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-aggregate, +.hljs-template_tag, +.django .hljs-variable, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.apache .hljs-tag, +.hljs-date, +.tex .hljs-formula, +.coffeescript .hljs-attribute { + color: #a31515; +} + +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.hljs-preprocessor, +.hljs-pragma, +.userType, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-special, +.hljs-prompt { + color: #2b91af; +} + +.hljs-phpdoc, +.hljs-javadoc, +.hljs-xmlDocTag { + color: #808080; +} + +.vhdl .hljs-typename { font-weight: bold; } +.vhdl .hljs-string { color: #666666; } +.vhdl .hljs-literal { color: #a31515; } +.vhdl .hljs-attribute { color: #00B0E8; } + +.xml .hljs-attribute { color: #f00; } diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/xcode.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/xcode.css new file mode 100644 index 00000000..57bd748e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/xcode.css @@ -0,0 +1,158 @@ +/* + +XCode style (c) Angel Garcia + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #fff; color: black; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc, +.hljs-comment * { + color: #006a00; +} + +.hljs-keyword, +.hljs-literal, +.nginx .hljs-title { + color: #aa0d91; +} +.method, +.hljs-list .hljs-title, +.hljs-tag .hljs-title, +.setting .hljs-value, +.hljs-winutils, +.tex .hljs-command, +.http .hljs-title, +.hljs-request, +.hljs-status { + color: #008; +} + +.hljs-envvar, +.tex .hljs-special { + color: #660; +} + +.hljs-string { + color: #c41a16; +} +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.hljs-regexp { + color: #080; +} + +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.ini .hljs-title, +.hljs-shebang, +.hljs-prompt, +.hljs-hexcolor, +.hljs-rules .hljs-value, +.css .hljs-value .hljs-number, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-number, +.css .hljs-function, +.clojure .hljs-title, +.clojure .hljs-built_in, +.hljs-function .hljs-title, +.coffeescript .hljs-attribute { + color: #1c00cf; +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.smalltalk .hljs-class, +.hljs-javadoctag, +.hljs-yardoctag, +.hljs-phpdoc, +.hljs-typename, +.hljs-tag .hljs-attribute, +.hljs-doctype, +.hljs-class .hljs-id, +.hljs-built_in, +.setting, +.hljs-params, +.clojure .hljs-attribute { + color: #5c2699; +} + +.hljs-variable { + color: #3f6e74; +} +.css .hljs-tag, +.hljs-rules .hljs-property, +.hljs-pseudo, +.hljs-subst { + color: #000; +} + +.css .hljs-class, +.css .hljs-id { + color: #9B703F; +} + +.hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; +} + +.hljs-rules .hljs-keyword { + color: #C5AF75; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9B859D; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #643820; +} + +.tex .hljs-formula { + background-color: #EEE; + font-style: italic; +} + +.diff .hljs-header, +.hljs-chunk { + color: #808080; + font-weight: bold; +} + +.diff .hljs-change { + background-color: #BCCFF9; +} + +.hljs-addition { + background-color: #BAEEBA; +} + +.hljs-deletion { + background-color: #FFC8BD; +} + +.hljs-comment .hljs-yardoctag { + font-weight: bold; +} + +.method .hljs-id { + color: #000; +} diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/zenburn.css b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/zenburn.css new file mode 100644 index 00000000..f6cb0983 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/lib/highlight/styles/zenburn.css @@ -0,0 +1,117 @@ +/* + +Zenburn style from voldmar.ru (c) Vladimir Epifanov +based on dark.css by Ivan Sagalaev + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #3F3F3F; + color: #DCDCDC; +} + +.hljs-keyword, +.hljs-tag, +.css .hljs-class, +.css .hljs-id, +.lisp .hljs-title, +.nginx .hljs-title, +.hljs-request, +.hljs-status, +.clojure .hljs-attribute { + color: #E3CEAB; +} + +.django .hljs-template_tag, +.django .hljs-variable, +.django .hljs-filter .hljs-argument { + color: #DCDCDC; +} + +.hljs-number, +.hljs-date { + color: #8CD0D3; +} + +.dos .hljs-envvar, +.dos .hljs-stream, +.hljs-variable, +.apache .hljs-sqbracket { + color: #EFDCBC; +} + +.dos .hljs-flow, +.diff .hljs-change, +.python .exception, +.python .hljs-built_in, +.hljs-literal, +.tex .hljs-special { + color: #EFEFAF; +} + +.diff .hljs-chunk, +.hljs-subst { + color: #8F8F8F; +} + +.dos .hljs-keyword, +.python .hljs-decorator, +.hljs-title, +.haskell .hljs-type, +.diff .hljs-header, +.ruby .hljs-class .hljs-parent, +.apache .hljs-tag, +.nginx .hljs-built_in, +.tex .hljs-command, +.hljs-prompt { + color: #efef8f; +} + +.dos .hljs-winutils, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-string { + color: #DCA3A3; +} + +.diff .hljs-deletion, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.sql .hljs-aggregate, +.hljs-javadoc, +.smalltalk .hljs-class, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.css .hljs-rules .hljs-value, +.hljs-attr_selector, +.hljs-pseudo, +.apache .hljs-cbracket, +.tex .hljs-formula, +.coffeescript .hljs-attribute { + color: #CC9393; +} + +.hljs-shebang, +.diff .hljs-addition, +.hljs-comment, +.java .hljs-annotation, +.hljs-template_comment, +.hljs-pi, +.hljs-doctype { + color: #7F9F7F; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/plugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/plugin.js new file mode 100644 index 00000000..5c6906cb --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/plugin.js @@ -0,0 +1,484 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + + /** + * @fileOverview Rich code snippets for CKEditor. + */ + +'use strict'; + +( function() { + var isBrowserSupported = !CKEDITOR.env.ie || CKEDITOR.env.version > 8; + + CKEDITOR.plugins.add( 'codesnippet', { + requires: 'widget,dialog', + lang: 'ar,az,bg,ca,cs,da,de,de-ch,el,en,en-au,en-gb,eo,es,es-mx,et,eu,fa,fi,fr,fr-ca,gl,he,hr,hu,id,it,ja,km,ko,ku,lt,lv,nb,nl,no,oc,pl,pt,pt-br,ro,ru,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE% + icons: 'codesnippet', // %REMOVE_LINE_CORE% + hidpi: true, // %REMOVE_LINE_CORE% + + beforeInit: function( editor ) { + editor._.codesnippet = {}; + + /** + * Sets the custom syntax highlighter. See {@link CKEDITOR.plugins.codesnippet.highlighter} + * to learn how to register a custom highlighter. + * + * **Note**: + * + * * This method can only be called while initialising plugins (in one of + * the three callbacks). + * * This method is accessible through the `editor.plugins.codesnippet` namespace only. + * + * @since 4.4 + * @member CKEDITOR.plugins.codesnippet + * @param {CKEDITOR.plugins.codesnippet.highlighter} highlighter + */ + this.setHighlighter = function( highlighter ) { + editor._.codesnippet.highlighter = highlighter; + + var langs = editor._.codesnippet.langs = + editor.config.codeSnippet_languages || highlighter.languages; + + // We might escape special regex chars below, but we expect that there + // should be no crazy values used as lang keys. + editor._.codesnippet.langsRegex = new RegExp( '(?:^|\\s)language-(' + + CKEDITOR.tools.objectKeys( langs ).join( '|' ) + ')(?:\\s|$)' ); + }; + + editor.once( 'pluginsLoaded', function() { + // Remove the method once it cannot be used, because it leaks the editor reference (#589). + this.setHighlighter = null; + }, this ); + }, + + onLoad: function() { + CKEDITOR.dialog.add( 'codeSnippet', this.path + 'dialogs/codesnippet.js' ); + }, + + init: function( editor ) { + editor.ui.addButton && editor.ui.addButton( 'CodeSnippet', { + label: editor.lang.codesnippet.button, + command: 'codeSnippet', + toolbar: 'insert,10' + } ); + }, + + afterInit: function( editor ) { + var path = this.path; + + registerWidget( editor ); + + // At the very end, if no custom highlighter was set so far (by plugin#setHighlighter) + // we will set default one. + if ( !editor._.codesnippet.highlighter ) { + var hljsHighlighter = new CKEDITOR.plugins.codesnippet.highlighter( { + languages: { + apache: 'Apache', + bash: 'Bash', + coffeescript: 'CoffeeScript', + cpp: 'C++', + cs: 'C#', + css: 'CSS', + diff: 'Diff', + html: 'HTML', + http: 'HTTP', + ini: 'INI', + java: 'Java', + javascript: 'JavaScript', + json: 'JSON', + makefile: 'Makefile', + markdown: 'Markdown', + nginx: 'Nginx', + objectivec: 'Objective-C', + perl: 'Perl', + php: 'PHP', + python: 'Python', + ruby: 'Ruby', + sql: 'SQL', + vbscript: 'VBScript', + xhtml: 'XHTML', + xml: 'XML' + }, + + init: function( callback ) { + var that = this; + + if ( isBrowserSupported ) { + CKEDITOR.scriptLoader.load( path + 'lib/highlight/highlight.pack.js', function() { + that.hljs = window.hljs; + callback(); + } ); + } + + // Method is available only if wysiwygarea exists. + if ( editor.addContentsCss ) { + editor.addContentsCss( path + 'lib/highlight/styles/' + editor.config.codeSnippet_theme + '.css' ); + } + }, + + highlighter: function( code, language, callback ) { + var highlighted = this.hljs.highlightAuto( code, + this.hljs.getLanguage( language ) ? [ language ] : undefined ); + + if ( highlighted ) + callback( highlighted.value ); + } + } ); + + this.setHighlighter( hljsHighlighter ); + } + } + } ); + + /** + * Global helpers and classes of the Code Snippet plugin. + * + * For more information see the {@glink guide/dev_codesnippet Code Snippet Guide}. + * + * @class + * @singleton + */ + CKEDITOR.plugins.codesnippet = { + highlighter: Highlighter + }; + + /** + * A Code Snippet highlighter. It can be set as a default highlighter + * using {@link CKEDITOR.plugins.codesnippet#setHighlighter}, for example: + * + * // Create a new plugin which registers a custom code highlighter + * // based on customEngine in order to replace the one that comes + * // with the Code Snippet plugin. + * CKEDITOR.plugins.add( 'myCustomHighlighter', { + * afterInit: function( editor ) { + * // Create a new instance of the highlighter. + * var myHighlighter = new CKEDITOR.plugins.codesnippet.highlighter( { + * init: function( ready ) { + * // Asynchronous code to load resources and libraries for customEngine. + * customEngine.loadResources( function() { + * // Let the editor know that everything is ready. + * ready(); + * } ); + * }, + * highlighter: function( code, language, callback ) { + * // Let the customEngine highlight the code. + * customEngine.highlight( code, language, function() { + * callback( highlightedCode ); + * } ); + * } + * } ); + * + * // Check how it performs. + * myHighlighter.highlight( 'foo()', 'javascript', function( highlightedCode ) { + * console.log( highlightedCode ); // -> foo() + * } ); + * + * // From now on, myHighlighter will be used as a Code Snippet + * // highlighter, overwriting the default engine. + * editor.plugins.codesnippet.setHighlighter( myHighlighter ); + * } + * } ); + * + * @since 4.4 + * @class CKEDITOR.plugins.codesnippet.highlighter + * @extends CKEDITOR.plugins.codesnippet + * @param {Object} def Highlighter definition. See {@link #highlighter}, {@link #init} and {@link #languages}. + */ + function Highlighter( def ) { + CKEDITOR.tools.extend( this, def ); + + /** + * A queue of {@link #highlight} jobs to be + * done once the highlighter is {@link #ready}. + * + * @readonly + * @property {Array} [=[]] + */ + this.queue = []; + + // Async init – execute jobs when ready. + if ( this.init ) { + this.init( CKEDITOR.tools.bind( function() { + // Execute pending jobs. + var job; + + while ( ( job = this.queue.pop() ) ) + job.call( this ); + + this.ready = true; + }, this ) ); + } else { + this.ready = true; + } + + /** + * If specified, this function should asynchronously load highlighter-specific + * resources and execute `ready` when the highlighter is ready. + * + * @property {Function} [init] + * @param {Function} ready The function to be called once + * the highlighter is {@link #ready}. + */ + + /** + * A function which highlights given plain text `code` in a given `language` and, once done, + * calls the `callback` function with highlighted markup as an argument. + * + * @property {Function} [highlighter] + * @param {String} code Code to be formatted. + * @param {String} lang Language to be used ({@link CKEDITOR.config#codeSnippet_languages}). + * @param {Function} callback Function which accepts highlighted String as an argument. + */ + + /** + * Defines languages supported by the highlighter. + * They can be restricted with the {@link CKEDITOR.config#codeSnippet_languages} configuration option. + * + * **Note**: If {@link CKEDITOR.config#codeSnippet_languages} is set, **it will + * overwrite** the languages listed in `languages`. + * + * languages: { + * coffeescript: 'CoffeeScript', + * cpp: 'C++', + * cs: 'C#', + * css: 'CSS' + * } + * + * More information on how to change the list of languages is available + * in the {@glink guide/dev_codesnippet#changing-supported-languages Code Snippet documentation}. + * + * @property {Object} languages + */ + + /** + * A flag which indicates whether the highlighter is ready to do jobs + * from the {@link #queue}. + * + * @readonly + * @property {Boolean} ready + */ + } + + /** + * Executes the {@link #highlighter}. If the highlighter is not ready, it defers the job ({@link #queue}) + * and executes it when the highlighter is {@link #ready}. + * + * @param {String} code Code to be formatted. + * @param {String} lang Language to be used ({@link CKEDITOR.config#codeSnippet_languages}). + * @param {Function} callback Function which accepts highlighted String as an argument. + */ + Highlighter.prototype.highlight = function() { + var arg = arguments; + + // Highlighter is ready – do it now. + if ( this.ready ) + this.highlighter.apply( this, arg ); + // Queue the job. It will be done once ready. + else { + this.queue.push( function() { + this.highlighter.apply( this, arg ); + } ); + } + }; + + // Encapsulates snippet widget registration code. + // @param {CKEDITOR.editor} editor + function registerWidget( editor ) { + var codeClass = editor.config.codeSnippet_codeClass, + newLineRegex = /\r?\n/g, + textarea = new CKEDITOR.dom.element( 'textarea' ), + lang = editor.lang.codesnippet; + + editor.widgets.add( 'codeSnippet', { + allowedContent: 'pre; code(language-*)', + // Actually we need both - pre and code, but ACF does not make it possible + // to defire required content with "and" operator. + requiredContent: 'pre', + styleableElements: 'pre', + template: '
    ', + dialog: 'codeSnippet', + pathName: lang.pathName, + mask: true, + + parts: { + pre: 'pre', + code: 'code' + }, + + highlight: function() { + var that = this, + widgetData = this.data, + callback = function( formatted ) { + // IE8 (not supported browser) have issue with new line chars, when using innerHTML. + // It will simply strip it. + that.parts.code.setHtml( isBrowserSupported ? + formatted : formatted.replace( newLineRegex, '
    ' ) ); + }; + + // Set plain code first, so even if custom handler will not call it the code will be there. + callback( CKEDITOR.tools.htmlEncode( widgetData.code ) ); + + // Call higlighter to apply its custom highlighting. + editor._.codesnippet.highlighter.highlight( widgetData.code, widgetData.lang, function( formatted ) { + editor.fire( 'lockSnapshot' ); + callback( formatted ); + editor.fire( 'unlockSnapshot' ); + } ); + }, + + data: function() { + var newData = this.data, + oldData = this.oldData; + + if ( newData.code ) + this.parts.code.setHtml( CKEDITOR.tools.htmlEncode( newData.code ) ); + + // Remove old .language-* class. + if ( oldData && newData.lang != oldData.lang ) + this.parts.code.removeClass( 'language-' + oldData.lang ); + + // Lang needs to be specified in order to apply formatting. + if ( newData.lang ) { + // Apply new .language-* class. + this.parts.code.addClass( 'language-' + newData.lang ); + + this.highlight(); + } + + // Save oldData. + this.oldData = CKEDITOR.tools.copy( newData ); + }, + + // Upcasts
    ...
    + upcast: function( el, data ) { + if ( el.name != 'pre' ) + return; + + var childrenArray = getNonEmptyChildren( el ), + code; + + if ( childrenArray.length != 1 || ( code = childrenArray[ 0 ] ).name != 'code' ) + return; + + // Upcast with text only: https://dev.ckeditor.com/ticket/11926#comment:4 + if ( code.children.length != 1 || code.children[ 0 ].type != CKEDITOR.NODE_TEXT ) + return; + + // Read language-* from class attribute. + var matchResult = editor._.codesnippet.langsRegex.exec( code.attributes[ 'class' ] ); + + if ( matchResult ) + data.lang = matchResult[ 1 ]; + + // Use textarea to decode HTML entities (https://dev.ckeditor.com/ticket/11926). + textarea.setHtml( code.getHtml() ); + data.code = textarea.getValue(); + + code.addClass( codeClass ); + + return el; + }, + + // Downcasts to
    ...
    + downcast: function( el ) { + var code = el.getFirst( 'code' ); + + // Remove pretty formatting from .... + code.children.length = 0; + + // Remove config#codeSnippet_codeClass. + code.removeClass( codeClass ); + + // Set raw text inside .... + code.add( new CKEDITOR.htmlParser.text( CKEDITOR.tools.htmlEncode( this.data.code ) ) ); + + return el; + } + } ); + + // Returns an **array** of child elements, with whitespace-only text nodes + // filtered out. + // @param {CKEDITOR.htmlParser.element} parentElement + // @return Array - array of CKEDITOR.htmlParser.node + var whitespaceOnlyRegex = /^[\s\n\r]*$/; + + function getNonEmptyChildren( parentElement ) { + var ret = [], + preChildrenList = parentElement.children, + curNode; + + // Filter out empty text nodes. + for ( var i = preChildrenList.length - 1; i >= 0; i-- ) { + curNode = preChildrenList[ i ]; + + if ( curNode.type != CKEDITOR.NODE_TEXT || !curNode.value.match( whitespaceOnlyRegex ) ) + ret.push( curNode ); + } + + return ret; + } + } +} )(); + +/** + * A CSS class of the `` element used internally for styling + * (by default [highlight.js](http://highlightjs.org) themes, see + * {@link CKEDITOR.config#codeSnippet_theme config.codeSnippet_theme}), + * which means that it is **not present** in the editor output data. + * + * // Changes the class to "myCustomClass". + * config.codeSnippet_codeClass = 'myCustomClass'; + * + * **Note**: The class might need to be changed when you are using a custom + * highlighter (the default is [highlight.js](http://highlightjs.org)). + * See {@link CKEDITOR.plugins.codesnippet.highlighter} to read more. + * + * Read more in the {@glink guide/dev_codesnippet documentation} + * and see the {@glink examples/codesnippet example}. + * + * @since 4.4 + * @cfg {String} [codeSnippet_codeClass='hljs'] + * @member CKEDITOR.config + */ +CKEDITOR.config.codeSnippet_codeClass = 'hljs'; + +/** + * Restricts languages available in the "Code Snippet" dialog window. + * An empty value is always added to the list. + * + * **Note**: If using a custom highlighter library (the default is [highlight.js](http://highlightjs.org)), + * you may need to refer to external documentation to set `config.codeSnippet_languages` properly. + * + * Read more in the [documentation](#!/guide/dev_codesnippet-section-changing-supported-languages) + * and see the {@glink examples/codesnippet example}. + * + * // Restricts languages to JavaScript and PHP. + * config.codeSnippet_languages = { + * javascript: 'JavaScript', + * php: 'PHP' + * }; + * + * @since 4.4 + * @cfg {Object} [codeSnippet_languages=null] + * @member CKEDITOR.config + */ + +/** + * A theme used to render code snippets. See [available themes](http://highlightjs.org/static/test.html). + * + * **Note**: This will only work with the default highlighter + * ([highlight.js](http://highlightjs.org/static/test.html)). + * + * Read more in the [documentation](#!/guide/dev_codesnippet-section-changing-highlighter-theme) + * and see the {@glink examples/codesnippet example}. + * + * // Changes the theme to "pojoaque". + * config.codeSnippet_theme = 'pojoaque'; + * + * @since 4.4 + * @cfg {String} [codeSnippet_theme='default'] + * @member CKEDITOR.config + */ +CKEDITOR.config.codeSnippet_theme = 'default'; diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/samples/codesnippet.html b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/samples/codesnippet.html new file mode 100644 index 00000000..bfe436cb --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/codesnippet/samples/codesnippet.html @@ -0,0 +1,239 @@ + + + + + + Code Snippet — CKEditor Sample + + + + + + + + + + + +

    + CKEditor Samples » Code Snippet Plugin +

    +
    + This sample is not maintained anymore. Check out its brand new version in CKEditor Examples. +
    + +
    +

    + This editor is using the Code Snippet plugin which introduces beautiful code snippets. + By default the codesnippet plugin depends on the built-in client-side syntax highlighting + library highlight.js. +

    +

    + You can adjust the appearance of code snippets using the codeSnippet_theme configuration variable + (see available themes). +

    +

    + Select theme: +

    +

    + The CKEditor instance below was created by using the following configuration settings: +

    + +
    +CKEDITOR.replace( 'editor1', {
    +	extraPlugins: 'codesnippet',
    +	codeSnippet_theme: 'monokai_sublime'
    +} );
    +
    + +

    + Please note that this plugin is not compatible with Internet Explorer 8. +

    +
    + + + +

    Inline editor

    + +
    +

    + The following sample shows the Code Snippet plugin running inside + an inline CKEditor instance. The CKEditor instance below was created by using the following configuration settings: +

    + +
    +CKEDITOR.inline( 'editable', {
    +	extraPlugins: 'codesnippet'
    +} );
    +
    + +

    + Note: The highlight.js themes + must be loaded manually to be applied inside an inline editor instance, as the + codeSnippet_theme setting will not work in that case. + You need to include the stylesheet in the <head> section of the page, for example: +

    + +
    +<head>
    +	...
    +	<link href="path/to/highlight.js/styles/monokai_sublime.css" rel="stylesheet">
    +</head>
    +
    + +
    + +
    + +

    JavaScript code:

    + +
    function isEmpty( object ) {
    +	for ( var i in object ) {
    +		if ( object.hasOwnProperty( i ) )
    +			return false;
    +	}
    +	return true;
    +}
    + +

    SQL query:

    + +
    SELECT cust.id, cust.name, loc.city FROM cust LEFT JOIN loc ON ( cust.loc_id = loc.id ) WHERE cust.type IN ( 1, 2 );
    + +

    Unknown markup:

    + +
     ________________
    +/                \
    +| How about moo? |  ^__^
    +\________________/  (oo)\_______
    +                  \ (__)\       )\/\
    +                        ||----w |
    +                        ||     ||
    +
    +
    + +

    Server-side Highlighting and Custom Highlighting Engines

    + +

    + The Code Snippet GeSHi plugin is an + extension of the Code Snippet plugin which uses a server-side highligter. +

    + +

    + It also is possible to replace the default highlighter with any library using + the Highlighter API + and the editor.plugins.codesnippet.setHighlighter() method. +

    + + + + + + From 362efe47f7591fb19e60d959ef7f6a30579d911a Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 18 Jul 2019 12:32:36 +0530 Subject: [PATCH 0416/1137] Change styling of suborg form --- gsoc/forms.py | 9 +++++ gsoc/models.py | 8 +++-- suborg/templates/register_suborg.html | 51 +++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index c8bcd173..dc3c9e18 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,3 +1,5 @@ +from PIL import Image + from .models import (UserDetails, UserProfile, RegLink, BlogPostDueDate, Event, SubOrgDetails, SubOrg, GsocEndDate) @@ -76,6 +78,10 @@ def clean(self): applied_not_selected = cd.get('applied_but_not_selected').all() suborg_name = cd.get('suborg_name') suborg = cd.get('suborg') + logo = cd.get('logo') + + im = Image.open(logo) + width, height = im.size contact = [ cd.get('chat', None), @@ -87,6 +93,9 @@ def clean(self): contact = list(filter(lambda a: a is not None, contact)) + if width != 256 or height != 256: + raise ValidationError('The image should of size 256 x 256 pixels') + if not suborg and suborg_name: suborg = SubOrg.objects.filter(suborg_name=suborg_name) if len(suborg) > 0: diff --git a/gsoc/models.py b/gsoc/models.py index a66be31d..652aae81 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -188,14 +188,16 @@ class SubOrgDetails(models.Model): ) past_gsoc_experience = models.BooleanField( verbose_name='Has your org been accepted as a mentor org ' - 'in Google Summer of Code before?' + 'in Google Summer of Code before?', + help_text='Mark the checkbox for yes' ) past_years = models.ManyToManyField( GsocYear, blank=True, verbose_name='Which years did your org participate in GSoC?' ) - suborg_in_past = models.BooleanField(verbose_name='Was this as a Suborg?') + suborg_in_past = models.BooleanField(verbose_name='Was this as a Suborg?', + help_text='Mark the checkbox for yes') applied_but_not_selected = models.ManyToManyField( GsocYear, @@ -225,7 +227,7 @@ class SubOrgDetails(models.Model): null=True, blank=True) description = models.TextField(verbose_name='A very short description of your organization') logo = models.ImageField(upload_to='logos/', verbose_name='Your organization logo', - help_text='Must be a 24-bit PNG, minimum height 256 pixels.') + help_text='Must be a 24-bit PNG of 256 x 256 pixels.') primary_os_license = models.CharField(max_length=50, verbose_name='Primary Open Source License') ideas_list = models.URLField(verbose_name='Ideas List') diff --git a/suborg/templates/register_suborg.html b/suborg/templates/register_suborg.html index ce7435cd..b6cf048b 100644 --- a/suborg/templates/register_suborg.html +++ b/suborg/templates/register_suborg.html @@ -13,6 +13,57 @@ .suborg-form .pure-button { margin: 20px 0 0 0; } + + .suborg-form textarea { + width: 100%; + } + + .suborg-form input[type=text] { + width: 100%; + } + + .suborg-form input[type=url] { + width: 100%; + } + + .suborg-form input[type=number] { + width: 100%; + } + + ul#id_past_years { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + } + + #id_past_years li { + float: left; + margin: 0 20px 0 0; + } + + ul#id_applied_but_not_selected { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + } + + #id_applied_but_not_selected li { + float: left; + margin: 0 20px 0 0; + } + + form ul.errorlist { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + } + + .errorlist li { + color: red; + } {% endblock %} From c2f95387c59a2ab2d0e868043b9710282616f59e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 18 Jul 2019 13:35:43 +0530 Subject: [PATCH 0417/1137] Disable fields when checkbox not selected in suborg form --- suborg/templates/register_suborg.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/suborg/templates/register_suborg.html b/suborg/templates/register_suborg.html index b6cf048b..86be5f57 100644 --- a/suborg/templates/register_suborg.html +++ b/suborg/templates/register_suborg.html @@ -91,5 +91,18 @@

    Apply for participating in GSoC@PSF as a SubOrg!

    console.log(helptext) helptext.classList.add('pure-form-message'); } + + function togglePastExpFields() { + let flag = document.getElementById('id_past_gsoc_experience').checked; + let past_exp_cbs = document.getElementsByName('past_years'); + past_exp_cbs.forEach(element => { + element.disabled = !flag; + }); + } + + let pastExpFlag = document.getElementById('id_past_gsoc_experience'); + pastExpFlag.onchange = togglePastExpFields; + + togglePastExpFields(); {% endblock %} \ No newline at end of file From 44cc619d70a6723e554664f6fa8faa906ff6d0a1 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Jul 2019 11:54:46 -0700 Subject: [PATCH 0418/1137] add redirect --- gsoc/migrations/0045_auto_20190719_1851.py | 28 ++++++++++++++++++++++ gsoc/urls.py | 4 ++++ gsoc/views.py | 4 ++++ 3 files changed, 36 insertions(+) create mode 100644 gsoc/migrations/0045_auto_20190719_1851.py diff --git a/gsoc/migrations/0045_auto_20190719_1851.py b/gsoc/migrations/0045_auto_20190719_1851.py new file mode 100644 index 00000000..b5f9fad7 --- /dev/null +++ b/gsoc/migrations/0045_auto_20190719_1851.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.10 on 2019-07-19 18:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0044_auto_20190716_0700'), + ] + + operations = [ + migrations.AlterField( + model_name='suborgdetails', + name='logo', + field=models.ImageField(help_text='Must be a 24-bit PNG of 256 x 256 pixels.', upload_to='logos/', verbose_name='Your organization logo'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='past_gsoc_experience', + field=models.BooleanField(help_text='Mark the checkbox for yes', verbose_name='Has your org been accepted as a mentor org in Google Summer of Code before?'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='suborg_in_past', + field=models.BooleanField(help_text='Mark the checkbox for yes', verbose_name='Was this as a Suborg?'), + ), + ] diff --git a/gsoc/urls.py b/gsoc/urls.py index 225c52e4..2f4fbc2a 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -74,3 +74,7 @@ url(r'^article/publish/(?P[0-9]+)/', gsoc.views.publish_article, name='publish_article') ] + +urlpatterns += [ + url(r'.*\/blogs\/.*', gsoc.views.redirect_blogs) +] diff --git a/gsoc/views.py b/gsoc/views.py index b2fbf981..98102ab4 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -29,6 +29,9 @@ from profanityfilter import ProfanityFilter +def redirect_blogs(request): + return redirect('/') + # handle proposal upload def convert_pdf_to_txt(f): @@ -346,3 +349,4 @@ def publish_article(request, article_id): else: messages.error(request, 'User does not have permission to publish article') return redirect(reverse('{}:article-detail'.format(a.app_config.namespace), args=[a.slug])) + From f7d312d9a86fca3e74b5f51fdb77b7fe56eabc5c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 06:58:21 +0530 Subject: [PATCH 0419/1137] Redirect /blogs to / --- gsoc/urls.py | 10 +++++----- gsoc/views.py | 12 +++++++++--- project.db | Bin 1523712 -> 1523712 bytes 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/gsoc/urls.py b/gsoc/urls.py index 2f4fbc2a..6a706bb7 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -37,7 +37,11 @@ urlpatterns += i18n_patterns( path('admin/', admin.site.urls), - url(r'^', include('cms.urls')) + url(r'^blogs/$', gsoc.views.redirect_blogs_list), + url(r'^blogs/(?P[\w-]+)/$', gsoc.views.redirect_blogs), + url(r'^blogs/(?P[\w-]+)/(?P[\w-]+)/', + gsoc.views.redirect_articles), + url(r'^', include('cms.urls')), ) # This is only needed when using runserver. if settings.DEBUG: @@ -74,7 +78,3 @@ url(r'^article/publish/(?P[0-9]+)/', gsoc.views.publish_article, name='publish_article') ] - -urlpatterns += [ - url(r'.*\/blogs\/.*', gsoc.views.redirect_blogs) -] diff --git a/gsoc/views.py b/gsoc/views.py index 98102ab4..17eb6e45 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -14,7 +14,7 @@ from django.contrib.auth import decorators, password_validation, validators, logout from django.contrib.auth.models import User from django import shortcuts -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponseRedirect from django.core.exceptions import ValidationError from django.shortcuts import redirect from django.urls import reverse @@ -29,8 +29,14 @@ from profanityfilter import ProfanityFilter -def redirect_blogs(request): - return redirect('/') +def redirect_blogs_list(request): + return HttpResponseRedirect(f'/') + +def redirect_blogs(request, blog_name): + return HttpResponseRedirect(f'/{blog_name}/') + +def redirect_articles(request, blog_name, article_name): + return HttpResponseRedirect(f'/{blog_name}/{article_name}/') # handle proposal upload diff --git a/project.db b/project.db index 7484c5d807d2013a370f8e7671b39c5186a46835..24252ea64a0ba3cb5fa923c891390f6fd156587b 100644 GIT binary patch delta 620 zcmX}qOK1~87zgm#-N`m$9ir;sMGmSAkENqQ&< z8+uTBs11dV;LStzBBFsVNYP3U36hf+J&0a==s^#f#1`}mf`{M041bvae2ihl4I{pO z%uz##DS$-;3qsOC**C=0Ud@UMGhE3ik2`ts8w!fm4MANJ?n@uVs#vW!jELAvN(-DW z&gXOKM825H-J8ETS4d{Ec`1eZ%Hf`kqyAf1s-y05*j-uneqfnTD^cHrsE+|G-~a{| zzykq@Kmt}^106sHcA$Vx-~dkGGNV5CyOZ1*evb+<_aw{jL9rYkoG8s$5IG-iSJSlN zKq6Tzmw61stU%_a z`77(mGG_lD@;S5>;?!>0vV#OvMD~aB^Nv+iw0*VpS(i}}!g0JBSqo94MRaD4-YvLY z^hKJ;n@xi0e%+~?!f$e#F|QtyC60OziE8mGSMXbC@CM`3@wEtDY>uf1Qr|yt$y3!=Q^m>!Hm}y$7IbEAsVs9VD#IVp6hupP)xnPSBDz-?$ wT*!EKVFU_YL2hnPw6v;t)u+o!fPo-i&i0Jd+;hV?%hsVG?&&zvWes0iM zK?pa%83YGH>P9Pl0I$3*C^{bUEBcdoSnZ<2O1~}Jx8T;4bDr)pm)Nd81Zk8{V8j}^b?%x^vB;hX)vgwD}GzJnNfPxSR0~shl z1raa+q96t|po2ItfC&;oHf_D%MRv`;(nDM!zIyN zH4~3rrio`&Zi-%&@4*SNv&K(}k5xV*oEjezFKhguV#?d{8L4?vTv*Ts#7l?dJyco~cN65kPw6!|m>jluty;Q7fQ^@n|R7B+1jlaC{rV5q+at4?+^*PTkI#HT2V?tKPm+?E?<%a-D0c3+uzK^6t+1JS5NW6W_-~h V)P7DK*$(Iu@9*n4#0K&I{sP2Go^JpE From 5b716730dc4304f516c120fd7157e6337c379a32 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 07:00:01 +0530 Subject: [PATCH 0420/1137] Fix pep8 warnings --- gsoc/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gsoc/views.py b/gsoc/views.py index 17eb6e45..636927eb 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -32,9 +32,11 @@ def redirect_blogs_list(request): return HttpResponseRedirect(f'/') + def redirect_blogs(request, blog_name): return HttpResponseRedirect(f'/{blog_name}/') + def redirect_articles(request, blog_name, article_name): return HttpResponseRedirect(f'/{blog_name}/{article_name}/') From 0de00271ddaf01e4c219d53f0356c671a4065681 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 08:12:14 +0530 Subject: [PATCH 0421/1137] Add send email --- gsoc/admin.py | 13 ++++++- gsoc/models.py | 52 +++++++++++++++++++++++++ gsoc/templates/email/generic_email.html | 6 +++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 gsoc/templates/email/generic_email.html diff --git a/gsoc/admin.py b/gsoc/admin.py index 5e65d580..21005a36 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,6 +1,6 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, - GsocEndDate, Comment) + GsocEndDate, Comment, SendEmail) from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, GsocEndDateForm) @@ -533,3 +533,14 @@ def has_add_permission(self, request, obj=None): admin.site.register(Comment, CommentAdmin) + + +class SendEmailAdmin(admin.ModelAdmin): + list_display = ('to', 'to_group', 'subject') + exclude = ('scheduler', ) + + def has_change_permission(self, request, obj=None): + return False + + +admin.site.register(SendEmail, SendEmailAdmin) diff --git a/gsoc/models.py b/gsoc/models.py index 652aae81..e15c8bb1 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -874,6 +874,58 @@ def save(self, *args, **kwargs): super(ArticleReview, self).save(*args, **kwargs) +class SendEmail(models.Model): + groups = ( + ('students', 'Students'), + ('mentors', 'Mentors'), + ('suborg_admins', 'Suborg Admins'), + ('admins', 'Admins'), + ('all', 'All') + ) + + to = models.CharField(null=True, blank=True, max_length=255, + help_text='Separate email with a comma') + to_group = models.CharField(max_length=80, choices=groups, null=True, blank=True) + subject = models.CharField(max_length=255) + body = models.TextField() + scheduler = models.ForeignKey(Scheduler, blank=True, null=True, on_delete=models.CASCADE) + + def save(self, *args, **kwargs): + if not (self.to or self.to_group): + raise ValidationError( + message="Any one of the fields 'to' or 'to_group' should be filled." + ) + emails = [] + if self.to: + emails.extend(self.to.split(',')) + + gsoc_year = GsocYear.objects.first() + + if self.to_group in 'students': + ups = UserProfile.objects.filter(role=3, gsoc_year=gsoc_year).all() + emails.extend([_.user.email for _ in ups]) + elif self.to_group == 'mentors': + ups = UserProfile.objects.filter(role=2, gsoc_year=gsoc_year).all() + emails.extend([_.user.email for _ in ups]) + elif self.to_group == 'suborg_admins': + ups = UserProfile.objects.filter(role=1, gsoc_year=gsoc_year).all() + emails.extend([_.user.email for _ in ups]) + elif self.to_group == 'all': + ups = UserProfile.objects.filter(gsoc_year=gsoc_year).all() + emails.extend([_.user.email for _ in ups]) + + scheduler_data = build_send_mail_json(emails, + template='generic_email.html', + subject=self.subject, + template_data={ + 'body': self.body + }) + self.scheduler = Scheduler.objects.create(command='send_email', + data=scheduler_data) + + super(SendEmail, self).save(*args, **kwargs) + + # Receivers # Update blog count when new UserProfile is created diff --git a/gsoc/templates/email/generic_email.html b/gsoc/templates/email/generic_email.html new file mode 100644 index 00000000..64585ed8 --- /dev/null +++ b/gsoc/templates/email/generic_email.html @@ -0,0 +1,6 @@ +{{ body }} +
    +If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org
    +
    +Thank you
    +Python GSoC Team!
    From dd5282648485531529ae7b6cd2fc56963ff8b68d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 08:12:40 +0530 Subject: [PATCH 0422/1137] Migrate db --- gsoc/migrations/0046_sendemail.py | 25 +++++++++++++++++++++++++ project.db | Bin 1523712 -> 1527808 bytes 2 files changed, 25 insertions(+) create mode 100644 gsoc/migrations/0046_sendemail.py diff --git a/gsoc/migrations/0046_sendemail.py b/gsoc/migrations/0046_sendemail.py new file mode 100644 index 00000000..c48c3b2f --- /dev/null +++ b/gsoc/migrations/0046_sendemail.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.10 on 2019-07-20 02:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0045_auto_20190719_1851'), + ] + + operations = [ + migrations.CreateModel( + name='SendEmail', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('to', models.CharField(blank=True, help_text='Separate email with a comma', max_length=255, null=True)), + ('to_group', models.CharField(blank=True, choices=[('students', 'Students'), ('mentors', 'Mentors'), ('suborg_admins', 'Suborg Admins'), ('admins', 'Admins'), ('all', 'All')], max_length=80, null=True)), + ('subject', models.CharField(max_length=255)), + ('body', models.TextField()), + ('scheduler', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Scheduler')), + ], + ), + ] diff --git a/project.db b/project.db index 24252ea64a0ba3cb5fa923c891390f6fd156587b..56d75f7cedb752e9fe1d488fba1b359fa81a050d 100644 GIT binary patch delta 1018 zcmZo@h;3L9J3(6T3tIF*6Xe05K~NvjH(X5OV-AClGT1F*gwN05LBR^8qnG5DNgY zAP@^}PrEKWuYy^ft$#XufQT;>TR+fxOEwe3#t2phQB6mXgAEK!%;MqxGcqu=)HN{I zH8M~zFtRc+vNAT&GdD0dFg9*K944~;aG0p0By%>K`*ijK(eoT^y=5~N;qPLeOh{Qr=WzYRY^fZ3E2iE%_iqQ7j|(?O{Nz6lEkE(R8;k-JjMd&AXmo_SA`HqCm+}8 zwIw3#oNz}>cH}W-D#@Szkef}6qa;5*y(qu5VEVxytfFkirAb+-$6m zFO=jEp1$d-a1^8F_Npwgu22asJ~amZll;^8Gx^QLTV3*b`&h8Y)My?Rps zD+8mZFh!{w;I zlMm`UGe@$`p3H7AfAb212dtA78bmkGHp}D$iq4wM?yz~YV8a8}&9mIHI+=ag(>AkT zn8^ebI>yqRcD+6AIwKG>0WmWWvj8zG5VHX>I}mdKF((jn0Wmia^8hg~5c2^sKM)H5 zu^+Nx)DF17d%m#2fxa9|V>;FvBqbHeq?8 From 938c5cf4e49b5e84ad1a182f0e530fbbbce0e607 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 08:14:27 +0530 Subject: [PATCH 0423/1137] Fix pep8 warnings --- gsoc/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index e15c8bb1..8b60ce82 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -898,9 +898,9 @@ def save(self, *args, **kwargs): emails = [] if self.to: emails.extend(self.to.split(',')) - + gsoc_year = GsocYear.objects.first() - + if self.to_group in 'students': ups = UserProfile.objects.filter(role=3, gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) @@ -913,7 +913,7 @@ def save(self, *args, **kwargs): elif self.to_group == 'all': ups = UserProfile.objects.filter(gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) - + scheduler_data = build_send_mail_json(emails, template='generic_email.html', subject=self.subject, From 02d231e4fcf97dcaadd242b4b077fded0e8d34cd Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 11:06:55 +0530 Subject: [PATCH 0424/1137] Add 404 template --- gsoc/templates/404.html | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 gsoc/templates/404.html diff --git a/gsoc/templates/404.html b/gsoc/templates/404.html new file mode 100644 index 00000000..7ee15b8a --- /dev/null +++ b/gsoc/templates/404.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block title %} +Page Not Found +{% endblock %} + +{% block content %} +
    +

    404 Page Not Found

    +
    +{% endblock %} \ No newline at end of file From 2af54064ab20715d352fca5b45667621d63ba2d0 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 19:53:33 +0530 Subject: [PATCH 0425/1137] cleanup index.html --- gsoc/common/utils/commands.py | 24 +++++++++++++++++++++++- gsoc/templates/site/index.html | 30 +++++++++++------------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 8ccf1046..f1de6557 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -131,8 +131,28 @@ def update_site_template(scheduler: Scheduler): 'duedates': BlogPostDueDate.objects.filter(timeline__gsoc_year=gsoc_year).all(), } elif template == 'index.html': + # change this if the number of contact fields increase + contact_fields = ('chat', 'mailing_list', 'twitter_url', 'blog_url', 'link') + suborgs = SubOrgDetails.objects.filter(gsoc_year=gsoc_year, accepted=True).all() + suborg_list = [] + for suborg in suborgs: + _ = { + 'name': suborg.suborg.suborg_name, + 'description': suborg.description, + 'logo': f'/{suborg.logo.name}', + 'ideas_list': suborg.ideas_list, + 'contact': [] + } + contact_count = 0 + for field in contact_fields: + if getattr(suborg, field): + contact_count += 1 + _['contact'].append((field.title().replace('_', ' '), + getattr(suborg, field))) + _['count'] = (contact_count // 2) + 1 + suborg_list.append(_) context = { - 'suborgs': SubOrgDetails.objects.filter(gsoc_year=gsoc_year, accepted=True).all(), + 'suborgs': suborg_list } content = render_site_template(template, context) push_site_template(settings.GITHUB_FILE_PATH[template], content) @@ -144,3 +164,5 @@ def archive_gsoc_pages(scheduler: Scheduler): try: gsoc_year = GsocYear.objects.first() archive_current_gsoc_files(gsoc_year.gsoc_year) + except Exception as e: + return str(e) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 5fe2a57a..34c95346 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -353,36 +353,28 @@

    Ideas

    - {{ suborg.suborg.suborg_name }} + {{ suborg.name }}


    {{ suborg.description }} -
    - {% if suborg.link %} -
    -

    Website

    -
    - {% endif %} - {% if suborg.mailing_list %} - - {% endif %} - {% if suborg.chat %} -
    -

    Contact

    +
    +
    +

    Contact Links

    +
    + {% for c in suborg.contact %} + - {% endif %} -
    + {% endfor %} +
    -
    Status: Ideas ready

    {% endfor %} From e3932ab1b91f6003b3c40ce2aef1b547eff6ff32 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 20 Jul 2019 21:03:11 +0530 Subject: [PATCH 0426/1137] push images to github pages site --- gsoc/common/utils/commands.py | 8 +++++++- gsoc/common/utils/tools.py | 6 ++++++ gsoc/templates/site/index.html | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index f1de6557..28ce0aa8 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -9,7 +9,7 @@ from gsoc.models import (Scheduler, RegLink, GsocYear, UserProfile, Event, BlogPostDueDate, SubOrgDetails) from .tools import (send_mail, render_site_template, push_site_template, - archive_current_gsoc_files) + archive_current_gsoc_files, push_images) def send_email(scheduler: Scheduler): @@ -136,6 +136,12 @@ def update_site_template(scheduler: Scheduler): suborgs = SubOrgDetails.objects.filter(gsoc_year=gsoc_year, accepted=True).all() suborg_list = [] for suborg in suborgs: + f = open(suborg.logo.path, 'rb') + lines = f.readlines() + content = b'' + for line in lines: + content = content + line + push_images(suborg.logo.name, content) _ = { 'name': suborg.suborg.suborg_name, 'description': suborg.description, diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 92e06cfd..5e91b780 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -69,6 +69,12 @@ def push_site_template(file_path, content): repo.update_file(f.path, f'Update {file_path}', content, f.sha) +def push_images(file_path, content): + g = Github(settings.GITHUB_ACCESS_TOKEN) + repo = g.get_repo(settings.STATIC_SITE_REPO) + repo.create_file(file_path, f'Add {file_path} logo', content) + + def is_year(file_name): try: year = int(file_name) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 34c95346..e194a3d0 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -356,7 +356,7 @@

    Ideas

    -

    +

    {{ suborg.name }}

    From d81a475385c1429acd2edebd5dd9ec009276ea8e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 21 Jul 2019 07:39:40 +0530 Subject: [PATCH 0427/1137] Add activation_date to SendEmail --- .../0047_sendemail_activation_date.py | 18 ++++++++++++++++++ gsoc/models.py | 4 +++- project.db | Bin 1527808 -> 1531904 bytes 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 gsoc/migrations/0047_sendemail_activation_date.py diff --git a/gsoc/migrations/0047_sendemail_activation_date.py b/gsoc/migrations/0047_sendemail_activation_date.py new file mode 100644 index 00000000..a4261b1e --- /dev/null +++ b/gsoc/migrations/0047_sendemail_activation_date.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.10 on 2019-07-21 02:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0046_sendemail'), + ] + + operations = [ + migrations.AddField( + model_name='sendemail', + name='activation_date', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 8b60ce82..5ae9a6ca 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -888,6 +888,7 @@ class SendEmail(models.Model): to_group = models.CharField(max_length=80, choices=groups, null=True, blank=True) subject = models.CharField(max_length=255) body = models.TextField() + activation_date = models.DateTimeField(blank=True, null=True) scheduler = models.ForeignKey(Scheduler, blank=True, null=True, on_delete=models.CASCADE) def save(self, *args, **kwargs): @@ -921,7 +922,8 @@ def save(self, *args, **kwargs): 'body': self.body }) self.scheduler = Scheduler.objects.create(command='send_email', - data=scheduler_data) + data=scheduler_data, + activation_date=self.activation_date) super(SendEmail, self).save(*args, **kwargs) diff --git a/project.db b/project.db index 56d75f7cedb752e9fe1d488fba1b359fa81a050d..e3986cc6b0111c20f4b6ab607485a0e9011598e1 100644 GIT binary patch delta 401 zcmZoz5ZiDdc7n9vIR*yCG9WHx01^xgoS!D@7&D&Rn6N~i(QY!ULLK9a&4LZi9Fvc0 zNK0`7)i5v!Hs&*Ha-hTe!N9=89N~`m#N?99vc!_i{Ji*-#FA7a14Bz)19M#?Lj?mP zD+5a_Q)4|dV+&&g>#_4Bt vg%w#f`Mntb0A2foaeGykSX=9&4+_g1m<3>XVgMHtV*yb9J^#c2R*)zFu!eSe delta 280 zcmZoT5ZkaIc7n9v83qQ%QU*BSd^b_YnDNZUgeCHfwv$;E>KKo27Hn|ln0#DAno)M5 zqvYm48XnAyH=12_+Ff-RftU%1nSq!Eh*^P{4T#x+m;;D8ftU-3xq+Amh Date: Sun, 21 Jul 2019 07:56:43 +0530 Subject: [PATCH 0428/1137] Minor fix --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 5ae9a6ca..eb8b17d6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -902,7 +902,7 @@ def save(self, *args, **kwargs): gsoc_year = GsocYear.objects.first() - if self.to_group in 'students': + if self.to_group == 'students': ups = UserProfile.objects.filter(role=3, gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) elif self.to_group == 'mentors': From 5f964f3622cd6a6cac76b7e32dea46c57759273e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 21 Jul 2019 11:06:05 +0530 Subject: [PATCH 0429/1137] Upload attachment/image ckeditor --- gsoc/settings.py | 3 +- .../plugins/filetools/dev/uploaddebugger.js | 61 ++ .../ckeditor/plugins/filetools/lang/az.js | 13 + .../ckeditor/plugins/filetools/lang/bg.js | 13 + .../ckeditor/plugins/filetools/lang/ca.js | 13 + .../ckeditor/plugins/filetools/lang/cs.js | 13 + .../ckeditor/plugins/filetools/lang/da.js | 13 + .../ckeditor/plugins/filetools/lang/de-ch.js | 13 + .../ckeditor/plugins/filetools/lang/de.js | 13 + .../ckeditor/plugins/filetools/lang/en-au.js | 13 + .../ckeditor/plugins/filetools/lang/en.js | 13 + .../ckeditor/plugins/filetools/lang/eo.js | 13 + .../ckeditor/plugins/filetools/lang/es-mx.js | 13 + .../ckeditor/plugins/filetools/lang/es.js | 13 + .../ckeditor/plugins/filetools/lang/et.js | 13 + .../ckeditor/plugins/filetools/lang/eu.js | 13 + .../ckeditor/plugins/filetools/lang/fa.js | 13 + .../ckeditor/plugins/filetools/lang/fr.js | 13 + .../ckeditor/plugins/filetools/lang/gl.js | 13 + .../ckeditor/plugins/filetools/lang/hr.js | 13 + .../ckeditor/plugins/filetools/lang/hu.js | 13 + .../ckeditor/plugins/filetools/lang/id.js | 13 + .../ckeditor/plugins/filetools/lang/it.js | 13 + .../ckeditor/plugins/filetools/lang/ja.js | 13 + .../ckeditor/plugins/filetools/lang/km.js | 13 + .../ckeditor/plugins/filetools/lang/ko.js | 13 + .../ckeditor/plugins/filetools/lang/ku.js | 13 + .../ckeditor/plugins/filetools/lang/lv.js | 13 + .../ckeditor/plugins/filetools/lang/nb.js | 13 + .../ckeditor/plugins/filetools/lang/nl.js | 13 + .../ckeditor/plugins/filetools/lang/oc.js | 13 + .../ckeditor/plugins/filetools/lang/pl.js | 13 + .../ckeditor/plugins/filetools/lang/pt-br.js | 13 + .../ckeditor/plugins/filetools/lang/pt.js | 13 + .../ckeditor/plugins/filetools/lang/ro.js | 13 + .../ckeditor/plugins/filetools/lang/ru.js | 13 + .../ckeditor/plugins/filetools/lang/sk.js | 13 + .../ckeditor/plugins/filetools/lang/sq.js | 13 + .../ckeditor/plugins/filetools/lang/sv.js | 13 + .../ckeditor/plugins/filetools/lang/tr.js | 13 + .../ckeditor/plugins/filetools/lang/ug.js | 13 + .../ckeditor/plugins/filetools/lang/uk.js | 13 + .../ckeditor/plugins/filetools/lang/zh-cn.js | 13 + .../ckeditor/plugins/filetools/lang/zh.js | 13 + .../ckeditor/plugins/filetools/plugin.js | 931 ++++++++++++++++++ .../plugins/notificationaggregator/plugin.js | 548 +++++++++++ .../ckeditor/plugins/uploadfile/plugin.js | 42 + .../ckeditor/plugins/uploadimage/plugin.js | 150 +++ .../plugins/uploadwidget/dev/cors.html | 34 + .../uploadwidget/dev/filereaderplugin.js | 55 ++ .../plugins/uploadwidget/dev/upload.html | 97 ++ .../ckeditor/plugins/uploadwidget/lang/az.js | 12 + .../ckeditor/plugins/uploadwidget/lang/bg.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ca.js | 12 + .../ckeditor/plugins/uploadwidget/lang/cs.js | 12 + .../ckeditor/plugins/uploadwidget/lang/da.js | 12 + .../plugins/uploadwidget/lang/de-ch.js | 12 + .../ckeditor/plugins/uploadwidget/lang/de.js | 12 + .../ckeditor/plugins/uploadwidget/lang/el.js | 12 + .../plugins/uploadwidget/lang/en-au.js | 12 + .../ckeditor/plugins/uploadwidget/lang/en.js | 12 + .../ckeditor/plugins/uploadwidget/lang/eo.js | 12 + .../plugins/uploadwidget/lang/es-mx.js | 12 + .../ckeditor/plugins/uploadwidget/lang/es.js | 12 + .../ckeditor/plugins/uploadwidget/lang/et.js | 12 + .../ckeditor/plugins/uploadwidget/lang/eu.js | 12 + .../ckeditor/plugins/uploadwidget/lang/fa.js | 12 + .../ckeditor/plugins/uploadwidget/lang/fr.js | 12 + .../ckeditor/plugins/uploadwidget/lang/gl.js | 12 + .../ckeditor/plugins/uploadwidget/lang/hr.js | 12 + .../ckeditor/plugins/uploadwidget/lang/hu.js | 12 + .../ckeditor/plugins/uploadwidget/lang/id.js | 12 + .../ckeditor/plugins/uploadwidget/lang/it.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ja.js | 12 + .../ckeditor/plugins/uploadwidget/lang/km.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ko.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ku.js | 12 + .../ckeditor/plugins/uploadwidget/lang/lv.js | 12 + .../ckeditor/plugins/uploadwidget/lang/nb.js | 12 + .../ckeditor/plugins/uploadwidget/lang/nl.js | 12 + .../ckeditor/plugins/uploadwidget/lang/no.js | 12 + .../ckeditor/plugins/uploadwidget/lang/oc.js | 12 + .../ckeditor/plugins/uploadwidget/lang/pl.js | 12 + .../plugins/uploadwidget/lang/pt-br.js | 12 + .../ckeditor/plugins/uploadwidget/lang/pt.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ro.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ru.js | 12 + .../ckeditor/plugins/uploadwidget/lang/sk.js | 12 + .../ckeditor/plugins/uploadwidget/lang/sq.js | 12 + .../ckeditor/plugins/uploadwidget/lang/sv.js | 12 + .../ckeditor/plugins/uploadwidget/lang/tr.js | 12 + .../ckeditor/plugins/uploadwidget/lang/ug.js | 12 + .../ckeditor/plugins/uploadwidget/lang/uk.js | 12 + .../plugins/uploadwidget/lang/zh-cn.js | 12 + .../ckeditor/plugins/uploadwidget/lang/zh.js | 12 + .../ckeditor/plugins/uploadwidget/plugin.js | 569 +++++++++++ gsoc/urls.py | 5 + gsoc/views.py | 24 + 98 files changed, 3592 insertions(+), 1 deletion(-) create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/dev/uploaddebugger.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/az.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/bg.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ca.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/cs.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/da.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de-ch.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en-au.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eo.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es-mx.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/et.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eu.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fa.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/gl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hu.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/id.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/it.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ja.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/km.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ko.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ku.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/lv.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nb.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/oc.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt-br.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ro.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ru.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sk.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sq.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sv.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/tr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ug.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/uk.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh-cn.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/plugin.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/notificationaggregator/plugin.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadfile/plugin.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadimage/plugin.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/cors.html create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/filereaderplugin.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/upload.html create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/az.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/bg.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ca.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/cs.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/da.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de-ch.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/el.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en-au.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eo.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es-mx.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/et.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eu.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fa.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/gl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hu.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/id.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/it.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ja.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/km.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ko.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ku.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/lv.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nb.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/no.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/oc.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pl.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt-br.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ro.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ru.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sk.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sq.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sv.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/tr.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ug.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/uk.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh-cn.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh.js create mode 100644 gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/plugin.js diff --git a/gsoc/settings.py b/gsoc/settings.py index baabe69a..e359fb5b 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -341,7 +341,8 @@ def gettext(s): return s CKEDITOR_SETTINGS = { 'disableNativeSpellChecker': False, 'language': '{{ language }}', - 'extraPlugins': 'button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube,codesnippet', + 'extraPlugins': 'button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube,codesnippet,notificationaggregator,filetools,uploadwidget,uploadfile,uploadimage', + 'uploadUrl': '/upload/', 'toolbar': [ {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']}, diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/dev/uploaddebugger.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/dev/uploaddebugger.js new file mode 100644 index 00000000..4fb158cd --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/dev/uploaddebugger.js @@ -0,0 +1,61 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +'use strict'; + +// Slow down the upload process. +// This trick works only on Chrome. + +( function() { + XMLHttpRequest.prototype.baseSend = XMLHttpRequest.prototype.send; + + XMLHttpRequest.prototype.send = function( data ) { + var baseOnProgress = this.onprogress, + baseOnLoad = this.onload; + + this.onprogress = function() {}; + + this.onload = function( evt ) { + // Total file size. + var total = 1163, + step = Math.round( total / 10 ), + loaded = 0, + xhr = this; + + function progress() { + setTimeout( function() { + if ( xhr.aborted ) { + return; + } + + loaded += step; + if ( loaded > total ) { + loaded = total; + } + + if ( loaded > step * 4 && xhr.responseText.indexOf( 'incorrectFile' ) > 0 ) { + xhr.aborted = true; + xhr.onerror(); + } else if ( loaded < total ) { + evt.loaded = loaded; + baseOnProgress( { loaded: loaded } ); + progress(); + } else { + baseOnLoad( evt ); + } + }, 300 ); + } + + progress(); + }; + + this.abort = function() { + this.aborted = true; + this.onabort(); + }; + + this.baseSend( data ); + }; +} )(); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/az.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/az.js new file mode 100644 index 00000000..1e77748f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/az.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'az', { + loadError: 'Faylını oxumaq mümkün deyil', + networkError: 'Xəta baş verdi.', + httpError404: 'Serverə göndərilməsinin zamanı xəta baş verdi (404 - fayl tapılmayıb)', + httpError403: 'Serverə göndərilməsinin zamanı xəta baş verdi (403 - gadağandır)', + httpError: 'Serverə göndərilməsinin zamanı xəta baş verdi (xətanın ststusu: %1)', + noUrlError: 'Yükləmə linki təyin edilməyib', + responseError: 'Serverin cavabı yanlışdır' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/bg.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/bg.js new file mode 100644 index 00000000..d473ffab --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/bg.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'bg', { + loadError: 'Възникна грешка при четене на файла.', + networkError: 'Възникна мрежова грешка при качването на файла.', + httpError404: 'Възникна HTTP грешка при качване на файла (404: Файлът не е намерен).', + httpError403: 'Възникна HTTP грешка при качване на файла (403: Забранено).', + httpError: 'Възникна HTTP грешка при качване на файла (статус на грешката: %1).', + noUrlError: 'URL адресът за качване не е дефиниран.', + responseError: 'Неправилен отговор на сървъра.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ca.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ca.js new file mode 100644 index 00000000..1061211c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ca.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ca', { + loadError: 'S\'ha produït un error durant la lectura del fitxer.', + networkError: 'S\'ha produït un error de xarxa durant la càrrega del fitxer.', + httpError404: 'S\'ha produït un error HTTP durant la càrrega del fitxer (404: Fitxer no trobat).', + httpError403: 'S\'ha produït un error HTTP durant la càrrega del fitxer (403: Permís denegat).', + httpError: 'S\'ha produït un error HTTP durant la càrrega del fitxer (estat d\'error: %1).', + noUrlError: 'La URL de càrrega no està definida.', + responseError: 'Resposta incorrecte del servidor' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/cs.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/cs.js new file mode 100644 index 00000000..a558fa2d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/cs.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'cs', { + loadError: 'Při čtení souboru došlo k chybě.', + networkError: 'Při nahrávání souboru došlo k chybě v síti.', + httpError404: 'Při nahrávání souboru došlo k chybě HTTP (404: Soubor nenalezen).', + httpError403: 'Při nahrávání souboru došlo k chybě HTTP (403: Zakázáno).', + httpError: 'Při nahrávání souboru došlo k chybě HTTP (chybový stav: %1).', + noUrlError: 'URL pro nahrání není zadána.', + responseError: 'Nesprávná odpověď serveru.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/da.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/da.js new file mode 100644 index 00000000..6778c9f8 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/da.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'da', { + loadError: 'Der skete en fejl ved indlæsningen af filen.', + networkError: 'Der skete en netværks fejl under uploadingen.', + httpError404: 'Der skete en HTTP fejl under uploadingen (404: File not found).', + httpError403: 'Der skete en HTTP fejl under uploadingen (403: Forbidden).', + httpError: 'Der skete en HTTP fejl under uploadingen (error status: %1).', + noUrlError: 'Upload URL er ikke defineret.', + responseError: 'Ikke korrekt server svar.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de-ch.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de-ch.js new file mode 100644 index 00000000..2ce36fe2 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de-ch.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'de-ch', { + loadError: 'Während dem Lesen der Datei ist ein Fehler aufgetreten.', + networkError: 'Während dem Hochladen der Datei ist ein Netzwerkfehler aufgetreten.', + httpError404: 'Während dem Hochladen der Datei ist ein HTTP-Fehler aufgetreten (404: Datei nicht gefunden).', + httpError403: 'Während dem Hochladen der Datei ist ein HTTP-Fehler aufgetreten (403: Verboten).', + httpError: 'Während dem Hochladen der Datei ist ein HTTP-Fehler aufgetreten (Fehlerstatus: %1).', + noUrlError: 'Hochlade-URL ist nicht definiert.', + responseError: 'Falsche Antwort des Servers.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de.js new file mode 100644 index 00000000..ff3f2142 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/de.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'de', { + loadError: 'Während des Lesens der Datei ist ein Fehler aufgetreten.', + networkError: 'Während des Hochladens der Datei ist ein Netzwerkfehler aufgetreten.', + httpError404: 'Während des Hochladens der Datei ist ein HTTP-Fehler aufgetreten (404: Datei nicht gefunden).', + httpError403: 'Während des Hochladens der Datei ist ein HTTP-Fehler aufgetreten (403: Verboten).', + httpError: 'Während des Hochladens der Datei ist ein HTTP-Fehler aufgetreten (Fehlerstatus: %1).', + noUrlError: 'Hochlade-URL ist nicht definiert.', + responseError: 'Falsche Antwort des Servers.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en-au.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en-au.js new file mode 100644 index 00000000..0ca16739 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en-au.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'en-au', { + loadError: 'Error occurred during file read.', + networkError: 'Network error occurred during file upload.', + httpError404: 'HTTP error occurred during file upload (404: File not found).', + httpError403: 'HTTP error occurred during file upload (403: Forbidden).', + httpError: 'HTTP error occurred during file upload (error status: %1).', + noUrlError: 'Upload URL is not defined.', + responseError: 'Incorrect server response.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en.js new file mode 100644 index 00000000..343821c7 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/en.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'en', { + loadError: 'Error occurred during file read.', + networkError: 'Network error occurred during file upload.', + httpError404: 'HTTP error occurred during file upload (404: File not found).', + httpError403: 'HTTP error occurred during file upload (403: Forbidden).', + httpError: 'HTTP error occurred during file upload (error status: %1).', + noUrlError: 'Upload URL is not defined.', + responseError: 'Incorrect server response.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eo.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eo.js new file mode 100644 index 00000000..887cbbaa --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eo.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'eo', { + loadError: 'Eraro okazis dum la dosiera legado.', + networkError: 'Reta eraro okazis dum la dosiera alŝuto.', + httpError404: 'HTTP eraro okazis dum la dosiera alŝuto (404: dosiero ne trovita).', + httpError403: 'HTTP eraro okazis dum la dosiera alŝuto (403: malpermesita).', + httpError: 'HTTP eraro okazis dum la dosiera alŝuto (erara stato: %1).', + noUrlError: 'Alŝuta URL ne estas difinita.', + responseError: 'Malĝusta respondo de la servilo.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es-mx.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es-mx.js new file mode 100644 index 00000000..f3ab805c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es-mx.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'es-mx', { + loadError: 'Ha ocurrido un error al leer el archivo', + networkError: 'Ha ocurrido un error de red durante la carga del archivo.', + httpError404: 'Se ha producido un error HTTP durante la subida de archivos (404: archivo no encontrado).', + httpError403: 'Se ha producido un error HTTP durante la subida de archivos (403: Prohibido).', + httpError: 'Se ha producido un error HTTP durante la subida de archivos (error: %1).', + noUrlError: 'La URL de subida no está definida.', + responseError: 'Respuesta incorrecta del servidor.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es.js new file mode 100644 index 00000000..57f0a834 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/es.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'es', { + loadError: 'Ha ocurrido un error durante la lectura del archivo.', + networkError: 'Error de red ocurrido durante carga de archivo.', + httpError404: 'Un error HTTP ha ocurrido durante la carga del archivo (404: Archivo no encontrado).', + httpError403: 'Un error HTTP ha ocurrido durante la carga del archivo (403: Prohibido).', + httpError: 'Error HTTP ocurrido durante la carga del archivo (Estado del error: %1).', + noUrlError: 'URL cargada no está definida.', + responseError: 'Respueta del servidor incorrecta.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/et.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/et.js new file mode 100644 index 00000000..36ec39b5 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/et.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'et', { + loadError: 'Error occurred during file read.', // MISSING + networkError: 'Network error occurred during file upload.', // MISSING + httpError404: 'HTTP error occurred during file upload (404: File not found).', // MISSING + httpError403: 'HTTP error occurred during file upload (403: Forbidden).', // MISSING + httpError: 'HTTP error occurred during file upload (error status: %1).', // MISSING + noUrlError: 'Upload URL is not defined.', // MISSING + responseError: 'Vigane serveri vastus.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eu.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eu.js new file mode 100644 index 00000000..f6969bda --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/eu.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'eu', { + loadError: 'Errorea gertatu da fitxategia irakurtzean.', + networkError: 'Sareko errorea gertatu da fitxategia kargatzean.', + httpError404: 'HTTP errorea gertatu da fitxategia kargatzean (404: Fitxategia ez da aurkitu).', + httpError403: 'HTTP errorea gertatu da fitxategia kargatzean (403: Debekatuta).', + httpError: 'HTTP errorea gertatu da fitxategia kargatzean (errore-egoera: %1).', + noUrlError: 'Kargatzeko URLa definitu gabe.', + responseError: 'Zerbitzariaren erantzun okerra.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fa.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fa.js new file mode 100644 index 00000000..79f7ca45 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fa.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'fa', { + loadError: 'هنگام خواندن فایل، خطایی رخ داد.', + networkError: 'هنگام آپلود فایل خطای شبکه رخ داد.', + httpError404: 'هنگام آپلود فایل خطای HTTP رخ داد (404: فایل یافت نشد).', + httpError403: 'هنگام آپلود فایل، خطای HTTP رخ داد (403: ممنوع).', + httpError: 'خطای HTTP در آپلود فایل رخ داده است (وضعیت خطا: %1).', + noUrlError: 'آدرس آپلود تعریف نشده است.', + responseError: 'پاسخ نادرست سرور.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fr.js new file mode 100644 index 00000000..465b7465 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/fr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'fr', { + loadError: 'Une erreur est survenue lors de la lecture du fichier.', + networkError: 'Une erreur réseau est survenue lors du téléversement du fichier.', + httpError404: 'Une erreur HTTP est survenue durant le téléversement du fichier (404 : fichier non trouvé).', + httpError403: 'Une erreur HTTP est survenue durant le téléversement du fichier (403 : accès refusé).', + httpError: 'Une erreur HTTP est survenue durant le téléversement du fichier (erreur : %1).', + noUrlError: 'L\'URL de téléversement n\'est pas spécifiée.', + responseError: 'Réponse du serveur incorrecte.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/gl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/gl.js new file mode 100644 index 00000000..138e118c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/gl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'gl', { + loadError: 'Produciuse un erro durante a lectura do ficheiro.', + networkError: 'Produciuse un erro na rede durante o envío do ficheiro.', + httpError404: 'Produciuse un erro HTTP durante o envío do ficheiro (404: Ficheiro non atopado).', + httpError403: 'Produciuse un erro HTTP durante o envío do ficheiro (403: Acceso denegado).', + httpError: 'Produciuse un erro HTTP durante o envío do ficheiro (erro de estado: %1).', + noUrlError: 'Non foi definido o URL para o envío.', + responseError: 'Resposta incorrecta do servidor.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hr.js new file mode 100644 index 00000000..289c7860 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'hr', { + loadError: 'Greška prilikom čitanja datoteke.', + networkError: 'Mrežna greška prilikom slanja datoteke.', + httpError404: 'HTTP greška tijekom slanja datoteke (404: datoteka nije pronađena).', + httpError403: 'HTTP greška tijekom slanja datoteke (403: Zabranjeno).', + httpError: 'HTTP greška tijekom slanja datoteke (greška status: %1).', + noUrlError: 'URL za slanje nije podešen.', + responseError: 'Neispravni odgovor servera.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hu.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hu.js new file mode 100644 index 00000000..d5d7ce70 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/hu.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'hu', { + loadError: 'Hiba történt a fájl olvasása közben.', + networkError: 'Hálózati hiba történt a fájl feltöltése közben.', + httpError404: 'HTTP hiba történt a fájl feltöltése alatt (404: A fájl nem található).', + httpError403: 'HTTP hiba történt a fájl feltöltése alatt (403: Tiltott).', + httpError: 'HTTP hiba történt a fájl feltöltése alatt (hiba státusz: %1).', + noUrlError: 'Feltöltési URL nincs megadva.', + responseError: 'Helytelen szerver válasz.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/id.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/id.js new file mode 100644 index 00000000..2f6959d7 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/id.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'id', { + loadError: 'Error terjadi ketika berkas dibaca', + networkError: 'Jaringan error terjadi ketika mengunggah berkas', + httpError404: 'HTTP error terjadi ketika mengunggah berkas (404: Berkas tidak ditemukan)', + httpError403: 'HTTP error terjadi ketika mengunggah berkas (403: Gangguan)', + httpError: 'HTTP error terjadi ketika mengunggah berkas (status error: %1)', + noUrlError: 'Unggahan URL tidak terdefinisi', + responseError: 'Respon server tidak sesuai' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/it.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/it.js new file mode 100644 index 00000000..acd6df9c --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/it.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'it', { + loadError: 'Si è verificato un errore durante la lettura del file.', + networkError: 'Si è verificato un errore di rete durante il caricamento del file.', + httpError404: 'Si è verificato un errore HTTP durante il caricamento del file (404: file non trovato).', + httpError403: 'Si è verificato un errore HTTP durante il caricamento del file (403: accesso negato).', + httpError: 'Si è verificato un errore HTTP durante il caricamento del file (stato dell\'errore: %1).', + noUrlError: 'L\'URL per il caricamento non è stato definito.', + responseError: 'La risposta del server non è corretta.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ja.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ja.js new file mode 100644 index 00000000..c5fc8c33 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ja.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ja', { + loadError: 'ファイルの読み込み中にエラーが発生しました。', + networkError: 'ファイルのアップロード中にネットワークエラーが発生しました。', + httpError404: 'ファイルのアップロード中にHTTPエラーが発生しました。(404: File not found)', + httpError403: 'ファイルのアップロード中にHTTPエラーが発生しました。(403: Forbidden)', + httpError: 'ファイルのアップロード中にHTTPエラーが発生しました。(error status: %1)', + noUrlError: 'アップロードURLが定義されていません。', + responseError: 'サーバーの応答が不正です。' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/km.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/km.js new file mode 100644 index 00000000..810fac3f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/km.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'km', { + loadError: 'មាន​បញ្ហា​កើតឡើង​ក្នុង​ពេល​អាន​ឯកសារ។', + networkError: 'មាន​បញ្ហា​បណ្ដាញ​កើត​ឡើង​ក្នុង​ពេល​ផ្ទុកឡើង​ឯកសារ។', + httpError404: 'មាន​បញ្ហា HTTP កើត​ឡើង​ក្នុង​ពេល​ផ្ទុកឡើង​ឯកសារ (404៖ រក​ឯកសារ​មិន​ឃើញ)។', + httpError403: 'មាន​បញ្ហា HTTP កើត​ឡើង​ក្នុង​ពេល​ផ្ទុកឡើង​ឯកសារ (403៖ ហាមឃាត់)។', + httpError: 'មាន​បញ្ហា HTTP កើត​ឡើង​ក្នុង​ពេល​ផ្ទុកឡើង​ឯកសារ (ស្ថានភាព​កំហុស៖ %1)។', + noUrlError: 'មិន​មាន​បញ្ជាក់ URL ផ្ទុក​ឡើង។', + responseError: 'ការ​ឆ្លើយតប​របស់​ម៉ាស៊ីនបម្រើ មិន​ត្រឹមត្រូវ។' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ko.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ko.js new file mode 100644 index 00000000..3c486cb1 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ko.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ko', { + loadError: '파일을 읽는 중 오류가 발생했습니다.', + networkError: '파일 업로드 중 네트워크 오류가 발생했습니다.', + httpError404: '파일 업로드중 HTTP 오류가 발생했습니다 (404: 파일 찾을수 없음).', + httpError403: '파일 업로드중 HTTP 오류가 발생했습니다 (403: 권한 없음).', + httpError: '파일 업로드중 HTTP 오류가 발생했습니다 (오류 코드 %1).', + noUrlError: '업로드 주소가 정의되어 있지 않습니다.', + responseError: '잘못된 서버 응답.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ku.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ku.js new file mode 100644 index 00000000..8e7e6c1e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ku.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ku', { + loadError: 'هەڵەیەک ڕوویدا لە ماوەی خوێندنەوەی پەڕگەکە.', + networkError: 'هەڵەیەکی ڕایەڵە ڕوویدا لە ماوەی بارکردنی پەڕگەکە.', + httpError404: 'هەڵەیەک ڕوویدا لە ماوەی بارکردنی پەڕگەکە (404: پەڕگەکە نەدۆزراوە).', + httpError403: 'هەڵەیەک ڕوویدا لە ماوەی بارکردنی پەڕگەکە (403: قەدەغەکراو).', + httpError: 'هەڵەیەک ڕوویدا لە ماوەی بارکردنی پەڕگەکە (دۆخی هەڵە: %1).', + noUrlError: 'بەستەری پەڕگەکە پێناسە نەکراوە.', + responseError: 'وەڵامێکی نادروستی سێرڤەر.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/lv.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/lv.js new file mode 100644 index 00000000..346ab090 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/lv.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'lv', { + loadError: 'Radās kļūda nolasot failu.', + networkError: 'Radās tīkla kļūda, kamēr tika ielādēts fails.', + httpError404: 'Ielādējot failu, radās HTTP kļūda (404: Fails nav atrasts)', + httpError403: 'Ielādējot failu, radās HTTP kļūda (403: Pieeja liegta)', + httpError: 'Ielādējot failu, radās HTTP kļūda (kļūdas statuss: %1)', + noUrlError: 'Augšupielādes adrese nav norādīta.', + responseError: 'Nekorekta servera atbilde.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nb.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nb.js new file mode 100644 index 00000000..48cb0afe --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nb.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'nb', { + loadError: 'Feil oppsto under filinnlesing.', + networkError: 'Nettverksfeil oppsto under filopplasting.', + httpError404: 'HTTP-feil oppsto under filopplasting (404: Fant ikke filen).', + httpError403: 'HTTP-feil oppsto under filopplasting (403: Ikke tillatt).', + httpError: 'HTTP-feil oppsto under filopplasting (feilstatus: %1).', + noUrlError: 'URL for opplasting er ikke oppgitt.', + responseError: 'Ukorrekt svar fra serveren.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nl.js new file mode 100644 index 00000000..1491b4a3 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/nl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'nl', { + loadError: 'Fout tijdens lezen van bestand.', + networkError: 'Netwerkfout tijdens uploaden van bestand.', + httpError404: 'HTTP fout tijdens uploaden van bestand (404: Bestand niet gevonden).', + httpError403: 'HTTP fout tijdens uploaden van bestand (403: Verboden).', + httpError: 'HTTP fout tijdens uploaden van bestand (fout status: %1).', + noUrlError: 'Upload URL is niet gedefinieerd.', + responseError: 'Ongeldig antwoord van server.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/oc.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/oc.js new file mode 100644 index 00000000..04a2bfba --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/oc.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'oc', { + loadError: 'Una error s\'es produita pendent la lectura del fichièr.', + networkError: 'Una error de ret s\'es produita pendent lo mandadís del fichièr.', + httpError404: 'Una error HTTP s\'es produita pendent lo mandadís del fichièr (404 : fichièr pas trobat).', + httpError403: 'Una error HTTP s\'es produita pendent lo mandadís del fichièr (403 : accès refusat).', + httpError: 'Una error HTTP s\'es produita pendent lo mandadís del fichièr (error : %1).', + noUrlError: 'L\'URL de mandadís es pas especificada.', + responseError: 'Responsa del servidor incorrècta.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pl.js new file mode 100644 index 00000000..6db5be77 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pl.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'pl', { + loadError: 'Błąd podczas odczytu pliku.', + networkError: 'W trakcie wysyłania pliku pojawił się błąd sieciowy.', + httpError404: 'Błąd HTTP w trakcie wysyłania pliku (404: Nie znaleziono pliku).', + httpError403: 'Błąd HTTP w trakcie wysyłania pliku (403: Zabroniony).', + httpError: 'Błąd HTTP w trakcie wysyłania pliku (status błędu: %1).', + noUrlError: 'Nie zdefiniowano adresu URL do przesłania pliku.', + responseError: 'Niepoprawna odpowiedź serwera.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt-br.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt-br.js new file mode 100644 index 00000000..5030ba77 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt-br.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'pt-br', { + loadError: 'Um erro ocorreu durante a leitura do arquivo.', + networkError: 'Um erro de rede ocorreu durante o envio do arquivo.', + httpError404: 'Um erro HTTP ocorreu durante o envio do arquivo (404: Arquivo não encontrado).', + httpError403: 'Um erro HTTP ocorreu durante o envio do arquivo (403: Proibido).', + httpError: 'Um erro HTTP ocorreu durante o envio do arquivo (status do erro: %1)', + noUrlError: 'A URL de upload não está definida.', + responseError: 'Resposta incorreta do servidor.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt.js new file mode 100644 index 00000000..abcee4c1 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/pt.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'pt', { + loadError: 'Ocorreu um erro ao ler o ficheiro', + networkError: 'Ocorreu um erro de rede ao carregar o ficheiro.', + httpError404: 'HTTP error occurred during file upload (404: File not found).', // MISSING + httpError403: 'HTTP error occurred during file upload (403: Forbidden).', // MISSING + httpError: 'HTTP error occurred during file upload (error status: %1).', // MISSING + noUrlError: 'Upload URL is not defined.', // MISSING + responseError: 'Incorrect server response.' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ro.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ro.js new file mode 100644 index 00000000..a8bf96a6 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ro.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ro', { + loadError: 'Eroare în timpul citirii fișierului.', + networkError: 'Eroare de rețea în timpul încărcării fișierului.', + httpError404: 'Eroare HTTP în timpul încărcării fișierului (404: Fișier negăsit).', + httpError403: 'Eroare HTTP în timpul încărcării fișierului (403: Operașie nepermisă).', + httpError: 'Eroare HTTP în timpul încărcării fișierului (stare eroiare: %1).', + noUrlError: 'URL-ul de ăncărcare nu este specificat.', + responseError: 'Răspuns server incorect.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ru.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ru.js new file mode 100644 index 00000000..f41e23ef --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ru.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ru', { + loadError: 'Ошибка при чтении файла', + networkError: 'Сетевая ошибка при загрузке файла', + httpError404: 'HTTP ошибка при загрузке файла (404: Файл не найден)', + httpError403: 'HTTP ошибка при загрузке файла (403: Запрещено)', + httpError: 'HTTP ошибка при загрузке файла (%1)', + noUrlError: 'Не определен URL для загрузки файлов', + responseError: 'Некорректный ответ сервера' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sk.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sk.js new file mode 100644 index 00000000..a5e7cec6 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sk.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'sk', { + loadError: 'Počas čítania súboru nastala chyba.', + networkError: 'Počas nahrávania súboru nastala chyba siete.', + httpError404: 'Počas nahrávania súboru nastala HTTP chyba (404: Súbor nebol nájdený).', + httpError403: 'Počas nahrávania súboru nastala HTTP chyba (403: Zakázaný).', + httpError: 'Počas nahrávania súboru nastala HTTP chyba (error status: %1).', + noUrlError: 'URL nahrávania nie je definovaný.', + responseError: 'Nesprávna odpoveď servera.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sq.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sq.js new file mode 100644 index 00000000..82bcb233 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sq.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'sq', { + loadError: 'Gabimi u paraqit gjatë leximit të skedës.', + networkError: 'Gabimi në rrjetë u paraqitë gjatë ngarkimit të skedës.', + httpError404: 'Gabimi në HTTP u paraqit gjatë ngarkimit të skedës (404: Skeda nuk u gjetë).', + httpError403: 'Gabimi në HTTP u paraqitë gjatë ngarkimit të skedës (403: E ndaluar).', + httpError: 'Gabimi në HTTP u paraqit gjatë ngarkimit të skedës (gjendja e gabimit: %1).', + noUrlError: 'URL e ngarkimit nuk është vendosur.', + responseError: 'Përgjigje e gabuar e serverit.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sv.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sv.js new file mode 100644 index 00000000..797d3a4a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/sv.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'sv', { + loadError: 'Fel uppstod vid filläsning', + networkError: 'Nätverksfel uppstod vid filuppladdning.', + httpError404: 'HTTP-fel uppstod vid filuppladdning (404: Fil hittades inte).', + httpError403: 'HTTP-fel uppstod vid filuppladdning (403: Förbjuden).', + httpError: 'HTTP-fel uppstod vid filuppladdning (felstatus: %1).', + noUrlError: 'URL för uppladdning inte definierad.', + responseError: 'Felaktigt serversvar.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/tr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/tr.js new file mode 100644 index 00000000..3139cb19 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/tr.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'tr', { + loadError: 'Dosya okunurken hata oluştu.', + networkError: 'Dosya gönderilirken ağ hatası oluştu.', + httpError404: 'Dosya gönderilirken HTTP hatası oluştu (404: Dosya bulunamadı).', + httpError403: 'Dosya gönderilirken HTTP hatası oluştu (403: Yasaklı).', + httpError: 'Dosya gönderilirken HTTP hatası oluştu (hata durumu: %1).', + noUrlError: 'Gönderilecek URL belirtilmedi.', + responseError: 'Sunucu cevap veremedi.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ug.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ug.js new file mode 100644 index 00000000..7d26586b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/ug.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'ug', { + loadError: 'ھۆججەت ئوقۇشتا خاتالىق كۆرۈلدى', + networkError: 'ھۆججەت يۈكلەشتە تور خاتالىقى كۆرۈلدى.', + httpError404: 'ھۆججەت يۈكلىگەندە HTTP خاتالىقى كۆرۈلدى (404: ھۆججەت تېپىلمىدى).', + httpError403: 'ھۆججەت يۈكلىگەندە HTTP خاتالىقى كۆرۈلدى (403: چەكلەنگەن).', + httpError: 'ھۆججەت يۈكلىگەندە HTTP خاتالىقى كۆرۈلدى (404: خاتالىق نىسپىتى: 1%).', + noUrlError: 'چىقىردىغان ئۇلانما تەڭشەلمىگەن .', + responseError: 'مۇلازىمىتىردا ئىنكاس يوق .' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/uk.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/uk.js new file mode 100644 index 00000000..60fa3b34 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/uk.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'uk', { + loadError: 'Виникла помилка під час читання файлу', + networkError: 'Під час завантаження файлу виникла помилка мережі.', + httpError404: 'Під час завантаження файлу виникла помилка HTTP (404: Файл не знайдено).', + httpError403: 'Під час завантаження файлу виникла помилка HTTP (403: Доступ заборонено).', + httpError: 'Під час завантаження файлу виникла помилка (статус помилки: %1).', + noUrlError: 'URL завантаження не визначений.', + responseError: 'Невірна відповідь сервера.' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh-cn.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh-cn.js new file mode 100644 index 00000000..a385e439 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh-cn.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'zh-cn', { + loadError: '读取文件时发生错误', + networkError: '上传文件时发生网络错误', + httpError404: '上传文件时发生 HTTP 错误(404:无法找到文件)', + httpError403: '上传文件时发生 HTTP 错误(403:禁止访问)', + httpError: '上传文件时发生 HTTP 错误(错误代码:%1)', + noUrlError: '上传的 URL 未定义', + responseError: '不正确的服务器响应' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh.js new file mode 100644 index 00000000..5c5af054 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/lang/zh.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +CKEDITOR.plugins.setLang( 'filetools', 'zh', { + loadError: '在讀取檔案時發生錯誤。', + networkError: '在上傳檔案時發生網路錯誤。', + httpError404: '在上傳檔案時發生 HTTP 錯誤(404:檔案找不到)。', + httpError403: '在上傳檔案時發生 HTTP 錯誤(403:禁止)。', + httpError: '在上傳檔案時發生 HTTP 錯誤(錯誤狀態:%1)。', + noUrlError: '上傳的 URL 未被定義。', + responseError: '不正確的伺服器回應。' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/plugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/plugin.js new file mode 100644 index 00000000..577586fd --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/filetools/plugin.js @@ -0,0 +1,931 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +'use strict'; + +( function() { + CKEDITOR.plugins.add( 'filetools', { + lang: 'az,bg,ca,cs,da,de,de-ch,en,en-au,eo,es,es-mx,et,eu,fa,fr,gl,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,oc,pl,pt,pt-br,ro,ru,sk,sq,sv,tr,ug,uk,zh,zh-cn', // %REMOVE_LINE_CORE% + + beforeInit: function( editor ) { + /** + * An instance of the {@link CKEDITOR.fileTools.uploadRepository upload repository}. + * It allows you to create and get {@link CKEDITOR.fileTools.fileLoader file loaders}. + * + * var loader = editor.uploadRepository.create( file ); + * loader.loadAndUpload( 'http://foo/bar' ); + * + * @since 4.5 + * @readonly + * @property {CKEDITOR.fileTools.uploadRepository} uploadRepository + * @member CKEDITOR.editor + */ + editor.uploadRepository = new UploadRepository( editor ); + + /** + * Event fired when the {@link CKEDITOR.fileTools.fileLoader file loader} should send XHR. If the event is not + * {@link CKEDITOR.eventInfo#stop stopped} or {@link CKEDITOR.eventInfo#cancel canceled}, the default request + * will be sent. Refer to the {@glink guide/dev_file_upload Uploading Dropped or Pasted Files} article for more information. + * + * @since 4.5 + * @event fileUploadRequest + * @member CKEDITOR.editor + * @param data + * @param {CKEDITOR.fileTools.fileLoader} data.fileLoader A file loader instance. + * @param {Object} data.requestData An object containing all data to be sent to the server. + */ + editor.on( 'fileUploadRequest', function( evt ) { + var fileLoader = evt.data.fileLoader; + + fileLoader.xhr.open( 'POST', fileLoader.uploadUrl, true ); + + // Adding file to event's data by default - allows overwriting it by user's event listeners. (https://dev.ckeditor.com/ticket/13518) + evt.data.requestData.upload = { file: fileLoader.file, name: fileLoader.fileName }; + }, null, null, 5 ); + + editor.on( 'fileUploadRequest', function( evt ) { + var fileLoader = evt.data.fileLoader, + $formData = new FormData(), + requestData = evt.data.requestData, + configXhrHeaders = editor.config.fileTools_requestHeaders, + header; + + for ( var name in requestData ) { + var value = requestData[ name ]; + + // Treating files in special way + if ( typeof value === 'object' && value.file ) { + $formData.append( name, value.file, value.name ); + } + else { + $formData.append( name, value ); + } + } + // Append token preventing CSRF attacks. + $formData.append( 'ckCsrfToken', CKEDITOR.tools.getCsrfToken() ); + + if ( configXhrHeaders ) { + for ( header in configXhrHeaders ) { + fileLoader.xhr.setRequestHeader( header, configXhrHeaders[ header ] ); + } + } + + fileLoader.xhr.send( $formData ); + }, null, null, 999 ); + + /** + * Event fired when the {CKEDITOR.fileTools.fileLoader file upload} response is received and needs to be parsed. + * If the event is not {@link CKEDITOR.eventInfo#stop stopped} or {@link CKEDITOR.eventInfo#cancel canceled}, + * the default response handler will be used. Refer to the + * {@glink guide/dev_file_upload Uploading Dropped or Pasted Files} article for more information. + * + * @since 4.5 + * @event fileUploadResponse + * @member CKEDITOR.editor + * @param data All data will be passed to {@link CKEDITOR.fileTools.fileLoader#responseData}. + * @param {CKEDITOR.fileTools.fileLoader} data.fileLoader A file loader instance. + * @param {String} data.message The message from the server. Needs to be set in the listener — see the example above. + * @param {String} data.fileName The file name on server. Needs to be set in the listener — see the example above. + * @param {String} data.url The URL to the uploaded file. Needs to be set in the listener — see the example above. + */ + editor.on( 'fileUploadResponse', function( evt ) { + var fileLoader = evt.data.fileLoader, + xhr = fileLoader.xhr, + data = evt.data; + + try { + var response = JSON.parse( xhr.responseText ); + + // Error message does not need to mean that upload finished unsuccessfully. + // It could mean that ex. file name was changes during upload due to naming collision. + if ( response.error && response.error.message ) { + data.message = response.error.message; + } + + // But !uploaded means error. + if ( !response.uploaded ) { + evt.cancel(); + } else { + for ( var i in response ) { + data[ i ] = response[ i ]; + } + } + } catch ( err ) { + // Response parsing error. + data.message = fileLoader.lang.filetools.responseError; + CKEDITOR.warn( 'filetools-response-error', { responseText: xhr.responseText } ); + + evt.cancel(); + } + }, null, null, 999 ); + } + } ); + + /** + * File loader repository. It allows you to create and get {@link CKEDITOR.fileTools.fileLoader file loaders}. + * + * An instance of the repository is available as the {@link CKEDITOR.editor#uploadRepository}. + * + * var loader = editor.uploadRepository.create( file ); + * loader.loadAndUpload( 'http://foo/bar' ); + * + * To find more information about handling files see the {@link CKEDITOR.fileTools.fileLoader} class. + * + * @since 4.5 + * @class CKEDITOR.fileTools.uploadRepository + * @mixins CKEDITOR.event + * @constructor Creates an instance of the repository. + * @param {CKEDITOR.editor} editor Editor instance. Used only to get the language data. + */ + function UploadRepository( editor ) { + this.editor = editor; + + this.loaders = []; + } + + UploadRepository.prototype = { + /** + * Creates a {@link CKEDITOR.fileTools.fileLoader file loader} instance with a unique ID. + * The instance can be later retrieved from the repository using the {@link #loaders} array. + * + * Fires the {@link CKEDITOR.fileTools.uploadRepository#instanceCreated instanceCreated} event. + * + * @param {Blob/String} fileOrData See {@link CKEDITOR.fileTools.fileLoader}. + * @param {String} fileName See {@link CKEDITOR.fileTools.fileLoader}. + * @param {Function} [loaderType] Loader type to be created. If skipped, the default {@link CKEDITOR.fileTools.fileLoader} + * type will be used. + * @returns {CKEDITOR.fileTools.fileLoader} The created file loader instance. + */ + create: function( fileOrData, fileName, loaderType ) { + loaderType = loaderType || FileLoader; + + var id = this.loaders.length, + loader = new loaderType( this.editor, fileOrData, fileName ); + + loader.id = id; + this.loaders[ id ] = loader; + + this.fire( 'instanceCreated', loader ); + + return loader; + }, + + /** + * Returns `true` if all loaders finished their jobs. + * + * @returns {Boolean} `true` if all loaders finished their job, `false` otherwise. + */ + isFinished: function() { + for ( var id = 0; id < this.loaders.length; ++id ) { + if ( !this.loaders[ id ].isFinished() ) { + return false; + } + } + + return true; + } + + /** + * Array of loaders created by the {@link #create} method. Loaders' {@link CKEDITOR.fileTools.fileLoader#id IDs} + * are indexes. + * + * @readonly + * @property {CKEDITOR.fileTools.fileLoader[]} loaders + */ + + /** + * Event fired when the {@link CKEDITOR.fileTools.fileLoader file loader} is created. + * + * @event instanceCreated + * @param {CKEDITOR.fileTools.fileLoader} data Created file loader. + */ + }; + + /** + * The `FileLoader` class is a wrapper which handles two file operations: loading the content of the file stored on + * the user's device into the memory and uploading the file to the server. + * + * There are two possible ways to crate a `FileLoader` instance: with a [Blob](https://developer.mozilla.org/en/docs/Web/API/Blob) + * (e.g. acquired from the {@link CKEDITOR.plugins.clipboard.dataTransfer#getFile} method) or with data as a Base64 string. + * Note that if the constructor gets the data as a Base64 string, there is no need to load the data, the data is already loaded. + * + * The `FileLoader` is created for a single load and upload process so if you abort the process, + * you need to create a new `FileLoader`. + * + * All process parameters are stored in public properties. + * + * `FileLoader` implements events so you can listen to them to react to changes. There are two types of events: + * events to notify the listeners about changes and an event that lets the listeners synchronize with current {@link #status}. + * + * The first group of events contains {@link #event-loading}, {@link #event-loaded}, {@link #event-uploading}, + * {@link #event-uploaded}, {@link #event-error} and {@link #event-abort}. These events are called only once, + * when the {@link #status} changes. + * + * The second type is the {@link #event-update} event. It is fired every time the {@link #status} changes, the progress changes + * or the {@link #method-update} method is called. Is is created to synchronize the visual representation of the loader with + * its status. For example if the dialog window shows the upload progress, it should be refreshed on + * the {@link #event-update} listener. Then when the user closes and reopens this dialog, the {@link #method-update} method should + * be called to refresh the progress. + * + * Default request and response formats will work with CKFinder 2.4.3 and above. If you need a custom request + * or response handling you need to overwrite the default behavior using the {@link CKEDITOR.editor#fileUploadRequest} and + * {@link CKEDITOR.editor#fileUploadResponse} events. For more information see their documentation. + * + * To create a `FileLoader` instance, use the {@link CKEDITOR.fileTools.uploadRepository} class. + * + * Here is a simple `FileLoader` usage example: + * + * editor.on( 'paste', function( evt ) { + * for ( var i = 0; i < evt.data.dataTransfer.getFilesCount(); i++ ) { + * var file = evt.data.dataTransfer.getFile( i ); + * + * if ( CKEDITOR.fileTools.isTypeSupported( file, /image\/png/ ) ) { + * var loader = editor.uploadRepository.create( file ); + * + * loader.on( 'update', function() { + * document.getElementById( 'uploadProgress' ).innerHTML = loader.status; + * } ); + * + * loader.on( 'error', function() { + * alert( 'Error!' ); + * } ); + * + * loader.loadAndUpload( 'http://upload.url/' ); + * + * evt.data.dataValue += 'loading...' + * } + * } + * } ); + * + * Note that `FileLoader` uses the native file API which is supported **since Internet Explorer 10**. + * + * @since 4.5 + * @class CKEDITOR.fileTools.fileLoader + * @mixins CKEDITOR.event + * @constructor Creates an instance of the class and sets initial values for all properties. + * @param {CKEDITOR.editor} editor The editor instance. Used only to get language data. + * @param {Blob/String} fileOrData A [blob object](https://developer.mozilla.org/en/docs/Web/API/Blob) or a data + * string encoded with Base64. + * @param {String} [fileName] The file name. If not set and the second parameter is a file, then its name will be used. + * If not set and the second parameter is a Base64 data string, then the file name will be created based on + * the {@link CKEDITOR.config#fileTools_defaultFileName} option. + */ + function FileLoader( editor, fileOrData, fileName ) { + var mimeParts, + defaultFileName = editor.config.fileTools_defaultFileName; + + this.editor = editor; + this.lang = editor.lang; + + if ( typeof fileOrData === 'string' ) { + // Data is already loaded from disc. + this.data = fileOrData; + this.file = dataToFile( this.data ); + this.total = this.file.size; + this.loaded = this.total; + } else { + this.data = null; + this.file = fileOrData; + this.total = this.file.size; + this.loaded = 0; + } + + if ( fileName ) { + this.fileName = fileName; + } else if ( this.file.name ) { + this.fileName = this.file.name; + } else { + mimeParts = this.file.type.split( '/' ); + + if ( defaultFileName ) { + mimeParts[ 0 ] = defaultFileName; + } + + this.fileName = mimeParts.join( '.' ); + } + + this.uploaded = 0; + this.uploadTotal = null; + + this.responseData = null; + + this.status = 'created'; + + this.abort = function() { + this.changeStatus( 'abort' ); + }; + } + + /** + * The loader status. Possible values: + * + * * `created` – The loader was created, but neither load nor upload started. + * * `loading` – The file is being loaded from the user's storage. + * * `loaded` – The file was loaded, the process is finished. + * * `uploading` – The file is being uploaded to the server. + * * `uploaded` – The file was uploaded, the process is finished. + * * `error` – The process stops because of an error, more details are available in the {@link #message} property. + * * `abort` – The process was stopped by the user. + * + * @property {String} status + */ + + /** + * String data encoded with Base64. If the `FileLoader` is created with a Base64 string, the `data` is that string. + * If a file was passed to the constructor, the data is `null` until loading is completed. + * + * @readonly + * @property {String} data + */ + + /** + * File object which represents the handled file. This property is set for both constructor options (file or data). + * + * @readonly + * @property {Blob} file + */ + + /** + * The name of the file. If there is no file name, it is created by using the + * {@link CKEDITOR.config#fileTools_defaultFileName} option. + * + * @readonly + * @property {String} fileName + */ + + /** + * The number of loaded bytes. If the `FileLoader` was created with a data string, + * the loaded value equals the {@link #total} value. + * + * @readonly + * @property {Number} loaded + */ + + /** + * The number of uploaded bytes. + * + * @readonly + * @property {Number} uploaded + */ + + /** + * The total file size in bytes. + * + * @readonly + * @property {Number} total + */ + + /** + * All data received in the response from the server. If the server returns additional data, it will be available + * in this property. + * + * It contains all data set in the {@link CKEDITOR.editor#fileUploadResponse} event listener. + * + * @readonly + * @property {Object} responseData + */ + + /** + * The total size of upload data in bytes. + * If the `xhr.upload` object is present, this value will indicate the total size of the request payload, not only the file + * size itself. If the `xhr.upload` object is not available and the real upload size cannot be obtained, this value will + * be equal to {@link #total}. It has a `null` value until the upload size is known. + * + * loader.on( 'update', function() { + * // Wait till uploadTotal is present. + * if ( loader.uploadTotal ) { + * console.log( 'uploadTotal: ' + loader.uploadTotal ); + * } + * }); + * + * @readonly + * @property {Number} uploadTotal + */ + + /** + * The error message or additional information received from the server. + * + * @readonly + * @property {String} message + */ + + /** + * The URL to the file when it is uploaded or received from the server. + * + * @readonly + * @property {String} url + */ + + /** + * The target of the upload. + * + * @readonly + * @property {String} uploadUrl + */ + + /** + * + * Native `FileReader` reference used to load the file. + * + * @readonly + * @property {FileReader} reader + */ + + /** + * Native `XMLHttpRequest` reference used to upload the file. + * + * @readonly + * @property {XMLHttpRequest} xhr + */ + + /** + * If `FileLoader` was created using {@link CKEDITOR.fileTools.uploadRepository}, + * it gets an identifier which is stored in this property. + * + * @readonly + * @property {Number} id + */ + + /** + * Aborts the process. + * + * This method has a different behavior depending on the current {@link #status}. + * + * * If the {@link #status} is `loading` or `uploading`, current operation will be aborted. + * * If the {@link #status} is `created`, `loading` or `uploading`, the {@link #status} will be changed to `abort` + * and the {@link #event-abort} event will be called. + * * If the {@link #status} is `loaded`, `uploaded`, `error` or `abort`, this method will do nothing. + * + * @method abort + */ + + FileLoader.prototype = { + /** + * Loads a file from the storage on the user's device to the `data` attribute and uploads it to the server. + * + * The order of {@link #status statuses} for a successful load and upload is: + * + * * `created`, + * * `loading`, + * * `uploading`, + * * `uploaded`. + * + * @param {String} url The upload URL. + * @param {Object} [additionalRequestParameters] Additional parameters that would be passed to + * the {@link CKEDITOR.editor#fileUploadRequest} event. + */ + loadAndUpload: function( url, additionalRequestParameters ) { + var loader = this; + + this.once( 'loaded', function( evt ) { + // Cancel both 'loaded' and 'update' events, + // because 'loaded' is terminated state. + evt.cancel(); + + loader.once( 'update', function( evt ) { + evt.cancel(); + }, null, null, 0 ); + + // Start uploading. + loader.upload( url, additionalRequestParameters ); + }, null, null, 0 ); + + this.load(); + }, + + /** + * Loads a file from the storage on the user's device to the `data` attribute. + * + * The order of the {@link #status statuses} for a successful load is: + * + * * `created`, + * * `loading`, + * * `loaded`. + */ + load: function() { + var loader = this; + + this.reader = new FileReader(); + + var reader = this.reader; + + loader.changeStatus( 'loading' ); + + this.abort = function() { + loader.reader.abort(); + }; + + reader.onabort = function() { + loader.changeStatus( 'abort' ); + }; + + reader.onerror = function() { + loader.message = loader.lang.filetools.loadError; + loader.changeStatus( 'error' ); + }; + + reader.onprogress = function( evt ) { + loader.loaded = evt.loaded; + loader.update(); + }; + + reader.onload = function() { + loader.loaded = loader.total; + loader.data = reader.result; + loader.changeStatus( 'loaded' ); + }; + + reader.readAsDataURL( this.file ); + }, + + /** + * Uploads a file to the server. + * + * The order of the {@link #status statuses} for a successful upload is: + * + * * `created`, + * * `uploading`, + * * `uploaded`. + * + * @param {String} url The upload URL. + * @param {Object} [additionalRequestParameters] Additional data that would be passed to + * the {@link CKEDITOR.editor#fileUploadRequest} event. + */ + upload: function( url, additionalRequestParameters ) { + var requestData = additionalRequestParameters || {}; + + if ( !url ) { + this.message = this.lang.filetools.noUrlError; + this.changeStatus( 'error' ); + } else { + this.uploadUrl = url; + + this.xhr = new XMLHttpRequest(); + this.attachRequestListeners(); + + if ( this.editor.fire( 'fileUploadRequest', { fileLoader: this, requestData: requestData } ) ) { + this.changeStatus( 'uploading' ); + } + } + }, + + /** + * Attaches listeners to the XML HTTP request object. + * + * @private + * @param {XMLHttpRequest} xhr XML HTTP request object. + */ + attachRequestListeners: function() { + var loader = this, + xhr = this.xhr; + + loader.abort = function() { + xhr.abort(); + onAbort(); + }; + + xhr.onerror = onError; + xhr.onabort = onAbort; + + // https://dev.ckeditor.com/ticket/13533 - When xhr.upload is present attach onprogress, onerror and onabort functions to get actual upload + // information. + if ( xhr.upload ) { + xhr.upload.onprogress = function( evt ) { + if ( evt.lengthComputable ) { + // Set uploadTotal with correct data. + if ( !loader.uploadTotal ) { + loader.uploadTotal = evt.total; + } + loader.uploaded = evt.loaded; + loader.update(); + } + }; + + xhr.upload.onerror = onError; + xhr.upload.onabort = onAbort; + + } else { + // https://dev.ckeditor.com/ticket/13533 - If xhr.upload is not supported - fire update event anyway and set uploadTotal to file size. + loader.uploadTotal = loader.total; + loader.update(); + } + + xhr.onload = function() { + // https://dev.ckeditor.com/ticket/13433 - Call update at the end of the upload. When xhr.upload object is not supported there will be + // no update events fired during the whole process. + loader.update(); + + // https://dev.ckeditor.com/ticket/13433 - Check if loader was not aborted during last update. + if ( loader.status == 'abort' ) { + return; + } + + loader.uploaded = loader.uploadTotal; + + if ( xhr.status < 200 || xhr.status > 299 ) { + loader.message = loader.lang.filetools[ 'httpError' + xhr.status ]; + if ( !loader.message ) { + loader.message = loader.lang.filetools.httpError.replace( '%1', xhr.status ); + } + loader.changeStatus( 'error' ); + } else { + var data = { + fileLoader: loader + }, + // Values to copy from event to FileLoader. + valuesToCopy = [ 'message', 'fileName', 'url' ], + success = loader.editor.fire( 'fileUploadResponse', data ); + + for ( var i = 0; i < valuesToCopy.length; i++ ) { + var key = valuesToCopy[ i ]; + if ( typeof data[ key ] === 'string' ) { + loader[ key ] = data[ key ]; + } + } + + // The whole response is also hold for use by uploadwidgets (https://dev.ckeditor.com/ticket/13519). + loader.responseData = data; + // But without reference to the loader itself. + delete loader.responseData.fileLoader; + + if ( success === false ) { + loader.changeStatus( 'error' ); + } else { + loader.changeStatus( 'uploaded' ); + } + } + }; + + function onError() { + // Prevent changing status twice, when XHR.error and XHR.upload.onerror could be called together. + if ( loader.status == 'error' ) { + return; + } + + loader.message = loader.lang.filetools.networkError; + loader.changeStatus( 'error' ); + } + + function onAbort() { + // Prevent changing status twice, when XHR.onabort and XHR.upload.onabort could be called together. + if ( loader.status == 'abort' ) { + return; + } + loader.changeStatus( 'abort' ); + } + }, + + /** + * Changes {@link #status} to the new status, updates the {@link #method-abort} method if needed and fires two events: + * new status and {@link #event-update}. + * + * @private + * @param {String} newStatus New status to be set. + */ + changeStatus: function( newStatus ) { + this.status = newStatus; + + if ( newStatus == 'error' || newStatus == 'abort' || + newStatus == 'loaded' || newStatus == 'uploaded' ) { + this.abort = function() {}; + } + + this.fire( newStatus ); + this.update(); + }, + + /** + * Updates the state of the `FileLoader` listeners. This method should be called if the state of the visual representation + * of the upload process is out of synchronization and needs to be refreshed (e.g. because of an undo operation or + * because the dialog window with the upload is closed and reopened). Fires the {@link #event-update} event. + */ + update: function() { + this.fire( 'update' ); + }, + + /** + * Returns `true` if the loading and uploading finished (successfully or not), so the {@link #status} is + * `loaded`, `uploaded`, `error` or `abort`. + * + * @returns {Boolean} `true` if the loading and uploading finished. + */ + isFinished: function() { + return !!this.status.match( /^(?:loaded|uploaded|error|abort)$/ ); + } + + /** + * Event fired when the {@link #status} changes to `loading`. It will be fired once for the `FileLoader`. + * + * @event loading + */ + + /** + * Event fired when the {@link #status} changes to `loaded`. It will be fired once for the `FileLoader`. + * + * @event loaded + */ + + /** + * Event fired when the {@link #status} changes to `uploading`. It will be fired once for the `FileLoader`. + * + * @event uploading + */ + + /** + * Event fired when the {@link #status} changes to `uploaded`. It will be fired once for the `FileLoader`. + * + * @event uploaded + */ + + /** + * Event fired when the {@link #status} changes to `error`. It will be fired once for the `FileLoader`. + * + * @event error + */ + + /** + * Event fired when the {@link #status} changes to `abort`. It will be fired once for the `FileLoader`. + * + * @event abort + */ + + /** + * Event fired every time the `FileLoader` {@link #status} or progress changes or the {@link #method-update} method is called. + * This event was designed to allow showing the visualization of the progress and refresh that visualization + * every time the status changes. Note that multiple `update` events may be fired with the same status. + * + * @event update + */ + }; + + CKEDITOR.event.implementOn( UploadRepository.prototype ); + CKEDITOR.event.implementOn( FileLoader.prototype ); + + var base64HeaderRegExp = /^data:(\S*?);base64,/; + + // Transforms Base64 string data into file and creates name for that file based on the mime type. + // + // @private + // @param {String} data Base64 string data. + // @returns {Blob} File. + function dataToFile( data ) { + var contentType = data.match( base64HeaderRegExp )[ 1 ], + base64Data = data.replace( base64HeaderRegExp, '' ), + byteCharacters = atob( base64Data ), + byteArrays = [], + sliceSize = 512, + offset, slice, byteNumbers, i, byteArray; + + for ( offset = 0; offset < byteCharacters.length; offset += sliceSize ) { + slice = byteCharacters.slice( offset, offset + sliceSize ); + + byteNumbers = new Array( slice.length ); + for ( i = 0; i < slice.length; i++ ) { + byteNumbers[ i ] = slice.charCodeAt( i ); + } + + byteArray = new Uint8Array( byteNumbers ); + + byteArrays.push( byteArray ); + } + + return new Blob( byteArrays, { type: contentType } ); + } + + // + // PUBLIC API ------------------------------------------------------------- + // + + // Two plugins extend this object. + if ( !CKEDITOR.fileTools ) { + /** + * Helpers to load and upload a file. + * + * @since 4.5 + * @singleton + * @class CKEDITOR.fileTools + */ + CKEDITOR.fileTools = {}; + } + + CKEDITOR.tools.extend( CKEDITOR.fileTools, { + uploadRepository: UploadRepository, + fileLoader: FileLoader, + + /** + * Gets the upload URL from the {@link CKEDITOR.config configuration}. Because of backward compatibility + * the URL can be set using multiple configuration options. + * + * If the `type` is defined, then four configuration options will be checked in the following order + * (examples for `type='image'`): + * + * * `[type]UploadUrl`, e.g. {@link CKEDITOR.config#imageUploadUrl}, + * * {@link CKEDITOR.config#uploadUrl}, + * * `filebrowser[uppercased type]uploadUrl`, e.g. {@link CKEDITOR.config#filebrowserImageUploadUrl}, + * * {@link CKEDITOR.config#filebrowserUploadUrl}. + * + * If the `type` is not defined, two configuration options will be checked: + * + * * {@link CKEDITOR.config#uploadUrl}, + * * {@link CKEDITOR.config#filebrowserUploadUrl}. + * + * `filebrowser[type]uploadUrl` and `filebrowserUploadUrl` are checked for backward compatibility with the + * `filebrowser` plugin. + * + * For both `filebrowser[type]uploadUrl` and `filebrowserUploadUrl` `&responseType=json` is added to the end of the URL. + * + * @param {Object} config The configuration file. + * @param {String} [type] Upload file type. + * @returns {String/null} Upload URL or `null` if none of the configuration options were defined. + */ + getUploadUrl: function( config, type ) { + var capitalize = CKEDITOR.tools.capitalize; + + if ( type && config[ type + 'UploadUrl' ] ) { + return config[ type + 'UploadUrl' ]; + } else if ( config.uploadUrl ) { + return config.uploadUrl; + } else if ( type && config[ 'filebrowser' + capitalize( type, 1 ) + 'UploadUrl' ] ) { + return config[ 'filebrowser' + capitalize( type, 1 ) + 'UploadUrl' ] + '&responseType=json'; + } else if ( config.filebrowserUploadUrl ) { + return config.filebrowserUploadUrl + '&responseType=json'; + } + + return null; + }, + + /** + * Checks if the MIME type of the given file is supported. + * + * CKEDITOR.fileTools.isTypeSupported( { type: 'image/png' }, /image\/(png|jpeg)/ ); // true + * CKEDITOR.fileTools.isTypeSupported( { type: 'image/png' }, /image\/(gif|jpeg)/ ); // false + * + * @param {Blob} file The file to check. + * @param {RegExp} supportedTypes A regular expression to check the MIME type of the file. + * @returns {Boolean} `true` if the file type is supported. + */ + isTypeSupported: function( file, supportedTypes ) { + return !!file.type.match( supportedTypes ); + }, + + /** + * Feature detection indicating whether the current browser supports methods essential to send files over an XHR request. + * + * @since 4.9.0 + * @property {Boolean} isFileUploadSupported + */ + isFileUploadSupported: ( function() { + return typeof FileReader === 'function' && + typeof ( new FileReader() ).readAsDataURL === 'function' && + typeof FormData === 'function' && + typeof ( new FormData() ).append === 'function' && + typeof XMLHttpRequest === 'function' && + typeof Blob === 'function'; + } )() + } ); +} )(); + +/** + * The URL where files should be uploaded. + * + * An empty string means that the option is disabled. + * + * @since 4.5 + * @cfg {String} [uploadUrl=''] + * @member CKEDITOR.config + */ + +/** + * The default file name (without extension) that will be used for files created from a Base64 data string + * (for example for files pasted into the editor). + * This name will be combined with the MIME type to create the full file name with the extension. + * + * If `fileTools_defaultFileName` is set to `default-name` and data's MIME type is `image/png`, + * the resulting file name will be `default-name.png`. + * + * If `fileTools_defaultFileName` is not set, the file name will be created using only its MIME type. + * For example for `image/png` the file name will be `image.png`. + * + * @since 4.5.3 + * @cfg {String} [fileTools_defaultFileName=''] + * @member CKEDITOR.config + */ + +/** + * Allows to add extra headers for every request made using the {@link CKEDITOR.fileTools} API. + * + * Note that headers can still be customized per a single request, using the + * [`fileUploadRequest`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-fileUploadRequest) + * event. + * + * config.fileTools_requestHeaders = { + * 'X-Requested-With': 'XMLHttpRequest', + * 'Custom-Header': 'header value' + * }; + * + * @since 4.9.0 + * @cfg {Object} [fileTools_requestHeaders] + * @member CKEDITOR.config + */ diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/notificationaggregator/plugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/notificationaggregator/plugin.js new file mode 100644 index 00000000..2a31efb5 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/notificationaggregator/plugin.js @@ -0,0 +1,548 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @fileOverview The "Notification Aggregator" plugin. + * + */ + +( function() { + 'use strict'; + + CKEDITOR.plugins.add( 'notificationaggregator', { + requires: 'notification' + } ); + + /** + * An aggregator of multiple tasks (progresses) which should be displayed using one + * {@link CKEDITOR.plugins.notification notification}. + * + * Once all the tasks are done, it means that the whole process is finished and the {@link #finished} + * event will be fired. + * + * New tasks can be created after the previous set of tasks is finished. This will continue the process and + * fire the {@link #finished} event again when it is done. + * + * Simple usage example: + * + * // Declare one aggregator that will be used for all tasks. + * var aggregator; + * + * // ... + * + * // Create a new aggregator if the previous one finished all tasks. + * if ( !aggregator || aggregator.isFinished() ) { + * // Create a new notification aggregator instance. + * aggregator = new CKEDITOR.plugins.notificationAggregator( editor, 'Loading process, step {current} of {max}...' ); + * + * // Change the notification type to 'success' on finish. + * aggregator.on( 'finished', function() { + * aggregator.notification.update( { message: 'Done', type: 'success' } ); + * } ); + * } + * + * // Create 3 tasks. + * var taskA = aggregator.createTask(), + * taskB = aggregator.createTask(), + * taskC = aggregator.createTask(); + * + * // At this point the notification contains a message: "Loading process, step 0 of 3...". + * + * // Let's close the first one immediately. + * taskA.done(); // "Loading process, step 1 of 3...". + * + * // One second later the message will be: "Loading process, step 2 of 3...". + * setTimeout( function() { + * taskB.done(); + * }, 1000 ); + * + * // Two seconds after the previous message the last task will be completed, meaning that + * // the notification will be closed. + * setTimeout( function() { + * taskC.done(); + * }, 3000 ); + * + * @since 4.5 + * @class CKEDITOR.plugins.notificationAggregator + * @mixins CKEDITOR.event + * @constructor Creates a notification aggregator instance. + * @param {CKEDITOR.editor} editor + * @param {String} message The template for the message to be displayed in the notification. The template can use + * the following variables: + * + * * `{current}` – The number of completed tasks. + * * `{max}` – The number of tasks. + * * `{percentage}` – The progress (number 0-100). + * + * @param {String/null} [singularMessage=null] An optional template for the message to be displayed in the notification + * when there is only one task remaining. This template can use the same variables as the `message` template. + * If `null`, then the `message` template will be used. + */ + function Aggregator( editor, message, singularMessage ) { + /** + * @readonly + * @property {CKEDITOR.editor} editor + */ + this.editor = editor; + + /** + * Notification created by the aggregator. + * + * The notification object is modified as aggregator tasks are being closed. + * + * @readonly + * @property {CKEDITOR.plugins.notification/null} + */ + this.notification = null; + + /** + * A template for the notification message. + * + * The template can use the following variables: + * + * * `{current}` – The number of completed tasks. + * * `{max}` – The number of tasks. + * * `{percentage}` – The progress (number 0-100). + * + * @private + * @property {CKEDITOR.template} + */ + this._message = new CKEDITOR.template( message ); + + /** + * A template for the notification message used when only one task is loading. + * + * Sometimes there might be a need to specify a special message when there + * is only one task loading, and to display standard messages in other cases. + * + * For example, you might want to show a message "Translating a widget" rather than + * "Translating widgets (1 of 1)", but still you would want to have a message + * "Translating widgets (2 of 3)" if more widgets are being translated at the same + * time. + * + * Template variables are the same as in {@link #_message}. + * + * @private + * @property {CKEDITOR.template/null} + */ + this._singularMessage = singularMessage ? new CKEDITOR.template( singularMessage ) : null; + + // Set the _tasks, _totalWeights, _doneWeights, _doneTasks properties. + this._tasks = []; + this._totalWeights = 0; + this._doneWeights = 0; + this._doneTasks = 0; + + /** + * Array of tasks tracked by the aggregator. + * + * @private + * @property {CKEDITOR.plugins.notificationAggregator.task[]} _tasks + */ + + /** + * Stores the sum of declared weights for all contained tasks. + * + * @private + * @property {Number} _totalWeights + */ + + /** + * Stores the sum of done weights for all contained tasks. + * + * @private + * @property {Number} _doneWeights + */ + + /** + * Stores the count of tasks done. + * + * @private + * @property {Number} _doneTasks + */ + } + + Aggregator.prototype = { + /** + * Creates a new task that can be updated to indicate the progress. + * + * @param [options] Options object for the task creation. + * @param [options.weight] For more information about weight, see the + * {@link CKEDITOR.plugins.notificationAggregator.task} overview. + * @returns {CKEDITOR.plugins.notificationAggregator.task} An object that represents the task state, and allows + * for its manipulation. + */ + createTask: function( options ) { + options = options || {}; + + var initialTask = !this.notification, + task; + + if ( initialTask ) { + // It's a first call. + this.notification = this._createNotification(); + } + + task = this._addTask( options ); + + task.on( 'updated', this._onTaskUpdate, this ); + task.on( 'done', this._onTaskDone, this ); + task.on( 'canceled', function() { + this._removeTask( task ); + }, this ); + + // Update the aggregator. + this.update(); + + if ( initialTask ) { + this.notification.show(); + } + + return task; + }, + + /** + * Triggers an update on the aggregator, meaning that its UI will be refreshed. + * + * When all the tasks are done, the {@link #finished} event is fired. + */ + update: function() { + this._updateNotification(); + + if ( this.isFinished() ) { + this.fire( 'finished' ); + } + }, + + /** + * Returns a number from `0` to `1` representing the done weights to total weights ratio + * (showing how many of the tasks are done). + * + * Note: For an empty aggregator (without any tasks created) it will return `1`. + * + * @returns {Number} Returns the percentage of tasks done as a number ranging from `0` to `1`. + */ + getPercentage: function() { + // In case there are no weights at all we'll return 1. + if ( this.getTaskCount() === 0 ) { + return 1; + } + + return this._doneWeights / this._totalWeights; + }, + + /** + * @returns {Boolean} Returns `true` if all notification tasks are done + * (or there are no tasks at all). + */ + isFinished: function() { + return this.getDoneTaskCount() === this.getTaskCount(); + }, + + /** + * @returns {Number} Returns a total tasks count. + */ + getTaskCount: function() { + return this._tasks.length; + }, + + /** + * @returns {Number} Returns the number of tasks done. + */ + getDoneTaskCount: function() { + return this._doneTasks; + }, + + /** + * Updates the notification content. + * + * @private + */ + _updateNotification: function() { + this.notification.update( { + message: this._getNotificationMessage(), + progress: this.getPercentage() + } ); + }, + + /** + * Returns a message used in the notification. + * + * @private + * @returns {String} + */ + _getNotificationMessage: function() { + var tasksCount = this.getTaskCount(), + doneTasks = this.getDoneTaskCount(), + templateParams = { + current: doneTasks, + max: tasksCount, + percentage: Math.round( this.getPercentage() * 100 ) + }, + template; + + // If there's only one remaining task and we have a singular message, we should use it. + if ( tasksCount == 1 && this._singularMessage ) { + template = this._singularMessage; + } else { + template = this._message; + } + + return template.output( templateParams ); + }, + + /** + * Creates a notification object. + * + * @private + * @returns {CKEDITOR.plugins.notification} + */ + _createNotification: function() { + return new CKEDITOR.plugins.notification( this.editor, { + type: 'progress' + } ); + }, + + /** + * Creates a {@link CKEDITOR.plugins.notificationAggregator.task} instance based + * on `options`, and adds it to the task list. + * + * @private + * @param options Options object coming from the {@link #createTask} method. + * @returns {CKEDITOR.plugins.notificationAggregator.task} + */ + _addTask: function( options ) { + var task = new Task( options.weight ); + this._tasks.push( task ); + this._totalWeights += task._weight; + return task; + }, + + /** + * Removes a given task from the {@link #_tasks} array and updates the UI. + * + * @private + * @param {CKEDITOR.plugins.notificationAggregator.task} task Task to be removed. + */ + _removeTask: function( task ) { + var index = CKEDITOR.tools.indexOf( this._tasks, task ); + + if ( index !== -1 ) { + // If task was already updated with some weight, we need to remove + // this weight from our cache. + if ( task._doneWeight ) { + this._doneWeights -= task._doneWeight; + } + + this._totalWeights -= task._weight; + + this._tasks.splice( index, 1 ); + // And we also should inform the UI about this change. + this.update(); + } + }, + + /** + * A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#update} event. + * + * @private + * @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#update} event. + */ + _onTaskUpdate: function( evt ) { + this._doneWeights += evt.data; + this.update(); + }, + + /** + * A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event. + * + * @private + * @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event. + */ + _onTaskDone: function() { + this._doneTasks += 1; + this.update(); + } + }; + + CKEDITOR.event.implementOn( Aggregator.prototype ); + + /** + * # Overview + * + * This type represents a single task in the aggregator, and exposes methods to manipulate its state. + * + * ## Weights + * + * Task progess is based on its **weight**. + * + * As you create a task, you need to declare its weight. As you want the update to inform about the + * progress, you will need to {@link #update} the task, telling how much of this weight is done. + * + * For example, if you declare that your task has a weight that equals `50` and then call `update` with `10`, + * you will end up with telling that the task is done in 20%. + * + * ### Example Usage of Weights + * + * Let us say that you use tasks for file uploading. + * + * A single task is associated with a single file upload. You can use the file size in bytes as a weight, + * and then as the file upload progresses you just call the `update` method with the number of bytes actually + * downloaded. + * + * @since 4.5 + * @class CKEDITOR.plugins.notificationAggregator.task + * @mixins CKEDITOR.event + * @constructor Creates a task instance for notification aggregator. + * @param {Number} [weight=1] + */ + function Task( weight ) { + /** + * Total weight of the task. + * + * @private + * @property {Number} + */ + this._weight = weight || 1; + + /** + * Done weight of the task. + * + * @private + * @property {Number} + */ + this._doneWeight = 0; + + /** + * Indicates when the task is canceled. + * + * @private + * @property {Boolean} + */ + this._isCanceled = false; + } + + Task.prototype = { + /** + * Marks the task as done. + */ + done: function() { + this.update( this._weight ); + }, + + /** + * Updates the done weight of a task. + * + * @param {Number} weight Number indicating how much of the total task {@link #_weight} is done. + */ + update: function( weight ) { + // If task is already done or canceled there is no need to update it, and we don't expect + // progress to be reversed. + if ( this.isDone() || this.isCanceled() ) { + return; + } + + // Note that newWeight can't be higher than _doneWeight. + var newWeight = Math.min( this._weight, weight ), + weightChange = newWeight - this._doneWeight; + + this._doneWeight = newWeight; + + // Fire updated event even if task is done in order to correctly trigger updating the + // notification's message. If we wouldn't do this, then the last weight change would be ignored. + this.fire( 'updated', weightChange ); + + if ( this.isDone() ) { + this.fire( 'done' ); + } + }, + + /** + * Cancels the task (the task will be removed from the aggregator). + */ + cancel: function() { + // If task is already done or canceled. + if ( this.isDone() || this.isCanceled() ) { + return; + } + + // Mark task as canceled. + this._isCanceled = true; + + // We'll fire cancel event it's up to aggregator to listen for this event, + // and remove the task. + this.fire( 'canceled' ); + }, + + /** + * Checks if the task is done. + * + * @returns {Boolean} + */ + isDone: function() { + return this._weight === this._doneWeight; + }, + + /** + * Checks if the task is canceled. + * + * @returns {Boolean} + */ + isCanceled: function() { + return this._isCanceled; + } + }; + + CKEDITOR.event.implementOn( Task.prototype ); + + /** + * Fired when all tasks are done. When this event occurs, the notification may change its type to `success` or be hidden: + * + * aggregator.on( 'finished', function() { + * if ( aggregator.getTaskCount() == 0 ) { + * aggregator.notification.hide(); + * } else { + * aggregator.notification.update( { message: 'Done', type: 'success' } ); + * } + * } ); + * + * @event finished + * @member CKEDITOR.plugins.notificationAggregator + */ + + /** + * Fired upon each weight update of the task. + * + * var myTask = new Task( 100 ); + * myTask.update( 30 ); + * // Fires updated event with evt.data = 30. + * myTask.update( 40 ); + * // Fires updated event with evt.data = 10. + * myTask.update( 20 ); + * // Fires updated event with evt.data = -20. + * + * @event updated + * @param {Number} data The difference between the new weight and the previous one. + * @member CKEDITOR.plugins.notificationAggregator.task + */ + + /** + * Fired when the task is done. + * + * @event done + * @member CKEDITOR.plugins.notificationAggregator.task + */ + + /** + * Fired when the task is canceled. + * + * @event canceled + * @member CKEDITOR.plugins.notificationAggregator.task + */ + + // Expose Aggregator type. + CKEDITOR.plugins.notificationAggregator = Aggregator; + CKEDITOR.plugins.notificationAggregator.task = Task; +} )(); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadfile/plugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadfile/plugin.js new file mode 100644 index 00000000..50cbaf80 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadfile/plugin.js @@ -0,0 +1,42 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +'use strict'; + +( function() { + CKEDITOR.plugins.add( 'uploadfile', { + requires: 'uploadwidget,link', + init: function( editor ) { + // Do not execute this paste listener if it will not be possible to upload file. + if ( !CKEDITOR.plugins.clipboard.isFileApiSupported ) { + return; + } + + var fileTools = CKEDITOR.fileTools, + uploadUrl = fileTools.getUploadUrl( editor.config ); + + if ( !uploadUrl ) { + CKEDITOR.error( 'uploadfile-config' ); + return; + } + + fileTools.addUploadWidget( editor, 'uploadfile', { + uploadUrl: fileTools.getUploadUrl( editor.config ), + + fileToElement: function( file ) { + // Show a placeholder with an empty link during the upload. + var a = new CKEDITOR.dom.element( 'a' ); + a.setText( file.name ); + a.setAttribute( 'href', '#' ); + return a; + }, + + onUploaded: function( upload ) { + this.replaceWith( '' + upload.fileName + '' ); + } + } ); + } + } ); +} )(); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadimage/plugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadimage/plugin.js new file mode 100644 index 00000000..9a5025c4 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadimage/plugin.js @@ -0,0 +1,150 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +'use strict'; + +( function() { + var uniqueNameCounter = 0, + // Black rectangle which is shown before the image is loaded. + loadingImage = 'data:image/gif;base64,R0lGODlhDgAOAIAAAAAAAP///yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs='; + + // Returns number as a string. If a number has 1 digit only it returns it prefixed with an extra 0. + function padNumber( input ) { + if ( input <= 9 ) { + input = '0' + input; + } + + return String( input ); + } + + // Returns a unique image file name. + function getUniqueImageFileName( type ) { + var date = new Date(), + dateParts = [ date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds() ]; + + uniqueNameCounter += 1; + + return 'image-' + CKEDITOR.tools.array.map( dateParts, padNumber ).join( '' ) + '-' + uniqueNameCounter + '.' + type; + } + + CKEDITOR.plugins.add( 'uploadimage', { + requires: 'uploadwidget', + + onLoad: function() { + CKEDITOR.addCss( + '.cke_upload_uploading img{' + + 'opacity: 0.3' + + '}' + ); + }, + + init: function( editor ) { + // Do not execute this paste listener if it will not be possible to upload file. + if ( !CKEDITOR.plugins.clipboard.isFileApiSupported ) { + return; + } + + var fileTools = CKEDITOR.fileTools, + uploadUrl = fileTools.getUploadUrl( editor.config, 'image' ); + + if ( !uploadUrl ) { + return; + } + + // Handle images which are available in the dataTransfer. + fileTools.addUploadWidget( editor, 'uploadimage', { + supportedTypes: /image\/(jpeg|png|gif|bmp)/, + + uploadUrl: uploadUrl, + + fileToElement: function() { + var img = new CKEDITOR.dom.element( 'img' ); + img.setAttribute( 'src', loadingImage ); + return img; + }, + + parts: { + img: 'img' + }, + + onUploading: function( upload ) { + // Show the image during the upload. + this.parts.img.setAttribute( 'src', upload.data ); + }, + + onUploaded: function( upload ) { + // Width and height could be returned by server (https://dev.ckeditor.com/ticket/13519). + var $img = this.parts.img.$, + width = upload.responseData.width || $img.naturalWidth, + height = upload.responseData.height || $img.naturalHeight; + + // Set width and height to prevent blinking. + this.replaceWith( '' ); + } + } ); + + // Handle images which are not available in the dataTransfer. + // This means that we need to read them from the elements. + editor.on( 'paste', function( evt ) { + // For performance reason do not parse data if it does not contain img tag and data attribute. + if ( !evt.data.dataValue.match( / + + + + + CORS sample + + + + + + +

    CORS sample

    + + + + diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/filereaderplugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/filereaderplugin.js new file mode 100644 index 00000000..deb15062 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/filereaderplugin.js @@ -0,0 +1,55 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +'use strict'; + +( function() { + CKEDITOR.plugins.add( 'filereader', { + requires: 'uploadwidget', + init: function( editor ) { + var fileTools = CKEDITOR.fileTools; + + fileTools.addUploadWidget( editor, 'filereader', { + onLoaded: function( upload ) { + var data = upload.data; + if ( data && data.indexOf( ',' ) >= 0 && data.indexOf( ',' ) < data.length - 1 ) { + this.replaceWith( atob( upload.data.split( ',' )[ 1 ] ) ); + } else { + editor.widgets.del( this ); + } + } + } ); + + editor.on( 'paste', function( evt ) { + var data = evt.data, + dataTransfer = data.dataTransfer, + filesCount = dataTransfer.getFilesCount(), + file, i; + + if ( data.dataValue || !filesCount ) { + return; + } + + for ( i = 0; i < filesCount; i++ ) { + file = dataTransfer.getFile( i ); + + if ( fileTools.isTypeSupported( file, /text\/(plain|html)/ ) ) { + var el = new CKEDITOR.dom.element( 'span' ), + loader = editor.uploadRepository.create( file ); + + el.setText( '...' ); + + loader.load(); + + fileTools.markElement( el, 'filereader', loader.id ); + + fileTools.bindNotifications( editor, loader ); + + data.dataValue += el.getOuterHtml(); + } + } + } ); + } + } ); +} )(); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/upload.html b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/upload.html new file mode 100644 index 00000000..decd2041 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/dev/upload.html @@ -0,0 +1,97 @@ + + + + + + Upload image dev sample + + + + + + +

    Upload image dev sample

    + + +
    +

    Saturn V carrying Apollo 11 Apollo 11

    + +

    Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.

    + +

    Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.

    + +

    Broadcasting and quotes

    + +

    Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:

    + +
    +

    One small step for [a] man, one giant leap for mankind.

    +
    + +

    Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:

    + +
    +

    [...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.

    +
    + +

    Technical details

    + +
    + + + + + + + + + + + + + + + + + + + + + +
    Mission crew
    PositionAstronaut
    CommanderNeil A. Armstrong
    Command Module PilotMichael Collins
    Lunar Module PilotEdwin "Buzz" E. Aldrin, Jr.
    + +

    Launched by a Saturn V rocket from Kennedy Space Center in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of NASA's Apollo program. The Apollo spacecraft had three parts:

    + +
      +
    1. Command Module with a cabin for the three astronauts which was the only part which landed back on Earth
    2. +
    3. Service Module which supported the Command Module with propulsion, electrical power, oxygen and water
    4. +
    5. Lunar Module for landing on the Moon.
    6. +
    + +

    After being sent to the Moon by the Saturn V's upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the Sea of Tranquility. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the Pacific Ocean on July 24.

    + +
    +

    Source: Wikipedia.org

    +
    + + + + diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/az.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/az.js new file mode 100644 index 00000000..2cee9258 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/az.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'az', { + abort: 'Serverə yükləmə istifadəçi tərəfindən dayandırılıb', + doneOne: 'Fayl müvəffəqiyyətlə yüklənib', + doneMany: '%1 fayllar müvəffəqiyyətlə yüklənib', + uploadOne: 'Faylın yüklənməsi ({percentage}%)', + uploadMany: 'Faylların yüklənməsi, {max}-dan {current} hazır ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/bg.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/bg.js new file mode 100644 index 00000000..afef1538 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/bg.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'bg', { + abort: 'Качването е прекратено от потребителя.', + doneOne: 'Файлът е качен успешно.', + doneMany: 'Успешно са качени %1 файла.', + uploadOne: 'Качване на файл ({percentage}%)...', + uploadMany: 'Качване на файлове, {current} от {max} качени ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ca.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ca.js new file mode 100644 index 00000000..60de61c9 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ca.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ca', { + abort: 'Pujada cancel·lada per l\'usuari.', + doneOne: 'Fitxer pujat correctament.', + doneMany: '%1 fitxers pujats correctament.', + uploadOne: 'Pujant fitxer ({percentage}%)...', + uploadMany: 'Pujant fitxers, {current} de {max} finalitzats ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/cs.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/cs.js new file mode 100644 index 00000000..83fd832d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/cs.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'cs', { + abort: 'Nahrávání zrušeno uživatelem.', + doneOne: 'Soubor úspěšně nahrán.', + doneMany: 'Úspěšně nahráno %1 souborů.', + uploadOne: 'Nahrávání souboru ({percentage}%)...', + uploadMany: 'Nahrávání souborů, {current} z {max} hotovo ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/da.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/da.js new file mode 100644 index 00000000..4010694f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/da.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'da', { + abort: 'Upload er afbrudt af brugen.', + doneOne: 'Filen er uploadet.', + doneMany: 'Du har uploadet %1 filer.', + uploadOne: 'Uploader fil ({percentage}%)...', + uploadMany: 'Uploader filer, {current} af {max} er uploadet ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de-ch.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de-ch.js new file mode 100644 index 00000000..3fc93b62 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de-ch.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'de-ch', { + abort: 'Hochladen durch den Benutzer abgebrochen.', + doneOne: 'Datei erfolgreich hochgeladen.', + doneMany: '%1 Dateien erfolgreich hochgeladen.', + uploadOne: 'Datei wird hochgeladen ({percentage}%)...', + uploadMany: 'Dateien werden hochgeladen, {current} von {max} fertig ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de.js new file mode 100644 index 00000000..9bcb0fb1 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/de.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'de', { + abort: 'Hochladen durch den Benutzer abgebrochen.', + doneOne: 'Datei erfolgreich hochgeladen.', + doneMany: '%1 Dateien erfolgreich hochgeladen.', + uploadOne: 'Datei wird hochgeladen ({percentage}%)...', + uploadMany: 'Dateien werden hochgeladen, {current} von {max} fertig ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/el.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/el.js new file mode 100644 index 00000000..6c1e3370 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/el.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'el', { + abort: 'Αποστολή ακυρώθηκε απο χρήστη.', + doneOne: 'Αρχείο εστάλη επιτυχώς.', + doneMany: 'Επιτυχής αποστολή %1 αρχείων.', + uploadOne: 'Αποστολή αρχείου ({percentage}%)…', + uploadMany: 'Αποστολή αρχείων, {current} από {max} ολοκληρωμένα ({percentage}%)…' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en-au.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en-au.js new file mode 100644 index 00000000..16c024cd --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en-au.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'en-au', { + abort: 'Upload aborted by the user.', + doneOne: 'File successfully uploaded.', + doneMany: 'Successfully uploaded %1 files.', + uploadOne: 'Uploading file ({percentage}%)...', + uploadMany: 'Uploading files, {current} of {max} done ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en.js new file mode 100644 index 00000000..67c4a86f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/en.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'en', { + abort: 'Upload aborted by the user.', + doneOne: 'File successfully uploaded.', + doneMany: 'Successfully uploaded %1 files.', + uploadOne: 'Uploading file ({percentage}%)...', + uploadMany: 'Uploading files, {current} of {max} done ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eo.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eo.js new file mode 100644 index 00000000..5686fb57 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eo.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'eo', { + abort: 'Alŝuto ĉesigita de la uzanto', + doneOne: 'Dosiero sukcese alŝutita.', + doneMany: 'Sukcese alŝutitaj %1 dosieroj.', + uploadOne: 'alŝutata dosiero ({percentage}%)...', + uploadMany: 'Alŝutataj dosieroj, {current} el {max} faritaj ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es-mx.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es-mx.js new file mode 100644 index 00000000..d820f2c3 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es-mx.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'es-mx', { + abort: 'La carga ha sido abortada por el usuario.', + doneOne: 'El archivo ha sido cargado completamente.', + doneMany: '%1 archivos cargados completamente.', + uploadOne: 'Cargando archivo ({percentage}%)...', + uploadMany: 'Cargando archivos, {current} de {max} listo ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es.js new file mode 100644 index 00000000..2567ee7f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/es.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'es', { + abort: 'Carga abortada por el usuario.', + doneOne: 'Archivo cargado exitósamente.', + doneMany: '%1 archivos exitósamente cargados.', + uploadOne: 'Cargando archivo ({percentage}%)...', + uploadMany: 'Cargando archivos, {current} de {max} hecho ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/et.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/et.js new file mode 100644 index 00000000..ab706c5a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/et.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'et', { + abort: 'Upload aborted by the user.', // MISSING + doneOne: 'Fail on üles laaditud.', + doneMany: 'Successfully uploaded %1 files.', // MISSING + uploadOne: 'Uploading file ({percentage}%)...', // MISSING + uploadMany: 'Uploading files, {current} of {max} done ({percentage}%)...' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eu.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eu.js new file mode 100644 index 00000000..8da38b91 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/eu.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'eu', { + abort: 'Karga erabiltzaileak bertan behera utzita.', + doneOne: 'Fitxategia behar bezala kargatu da.', + doneMany: 'Behar bezala kargatu dira %1 fitxategi.', + uploadOne: 'Fitxategia kargatzen ({percentage}%)...', + uploadMany: 'Fitxategiak kargatzen, {current} / {max} eginda ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fa.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fa.js new file mode 100644 index 00000000..47e08c67 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fa.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'fa', { + abort: 'بارگذاری توسط کاربر لغو شد.', + doneOne: 'فایل با موفقیت بارگذاری شد.', + doneMany: '%1 از فایل​ها با موفقیت بارگذاری شد.', + uploadOne: 'بارگذاری فایل ({percentage}%)...', + uploadMany: 'بارگذاری فایل​ها, {current} از {max} انجام شده ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fr.js new file mode 100644 index 00000000..552f15b3 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/fr.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'fr', { + abort: 'Téléversement interrompu par l\'utilisateur.', + doneOne: 'Fichier téléversé avec succès.', + doneMany: '%1 fichiers téléversés avec succès.', + uploadOne: 'Téléversement du fichier en cours ({percentage} %)…', + uploadMany: 'Téléversement des fichiers en cours, {current} sur {max} effectués ({percentage} %)…' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/gl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/gl.js new file mode 100644 index 00000000..f944fa5a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/gl.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'gl', { + abort: 'Envío interrompido polo usuario.', + doneOne: 'Ficheiro enviado satisfactoriamente.', + doneMany: '%1 ficheiros enviados satisfactoriamente.', + uploadOne: 'Enviando o ficheiro ({percentage}%)...', + uploadMany: 'Enviando ficheiros, {current} de {max} feito o ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hr.js new file mode 100644 index 00000000..689e5ce8 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hr.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'hr', { + abort: 'Slanje prekinuto od strane korisnika', + doneOne: 'Datoteka uspješno poslana.', + doneMany: 'Uspješno poslano %1 datoteka.', + uploadOne: 'Slanje datoteke ({percentage}%)...', + uploadMany: 'Slanje datoteka, {current} od {max} gotovo ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hu.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hu.js new file mode 100644 index 00000000..6be65329 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/hu.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'hu', { + abort: 'A feltöltést a felhasználó megszakította.', + doneOne: 'A fájl sikeresen feltöltve.', + doneMany: '%1 fájl sikeresen feltöltve.', + uploadOne: 'Fájl feltöltése ({percentage}%)...', + uploadMany: 'Fájlok feltöltése, {current}/{max} kész ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/id.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/id.js new file mode 100644 index 00000000..5825975e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/id.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'id', { + abort: 'Pengunggahan dibatalkan oleh pengguna', + doneOne: 'Berkas telah berhasil diunggah', + doneMany: 'Pengunggahan berkas %1 berhasil', + uploadOne: 'Mengunggah berkas ({percentage}%)...', + uploadMany: 'Pengunggahan berkas {current} dari {max} berhasil ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/it.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/it.js new file mode 100644 index 00000000..f0f39ffc --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/it.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'it', { + abort: 'Caricamento interrotto dall\'utente.', + doneOne: 'Il file è stato caricato correttamente.', + doneMany: '%1 file sono stati caricati correttamente.', + uploadOne: 'Caricamento del file ({percentage}%)...', + uploadMany: 'Caricamento dei file, {current} di {max} completati ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ja.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ja.js new file mode 100644 index 00000000..e63dec99 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ja.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ja', { + abort: 'アップロードを中止しました。', + doneOne: 'ファイルのアップロードに成功しました。', + doneMany: '%1個のファイルのアップロードに成功しました。', + uploadOne: 'ファイルのアップロード中 ({percentage}%)...', + uploadMany: '{max} 個中 {current} 個のファイルをアップロードしました。 ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/km.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/km.js new file mode 100644 index 00000000..a4d208b3 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/km.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'km', { + abort: 'បាន​ផ្ដាច់​ការផ្ទុកឡើង​ដោយ​អ្នក​ប្រើប្រាស់។', + doneOne: 'បាន​ផ្ទុកឡើង​នូវ​ឯកសារ​ដោយ​ជោគជ័យ។', + doneMany: 'បាន​ផ្ទុក​ឡើង​នូវ​ឯកសារ %1 ដោយ​ជោគជ័យ។', + uploadOne: 'កំពុង​ផ្ទុកឡើង​ឯកសារ ({percentage}%)...', + uploadMany: 'កំពុង​ផ្ទុកឡើង​ឯកសារ, រួចរាល់ {current} នៃ {max} ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ko.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ko.js new file mode 100644 index 00000000..519acdc4 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ko.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ko', { + abort: '사용자가 업로드를 중단했습니다.', + doneOne: '파일이 성공적으로 업로드되었습니다.', + doneMany: '파일 %1개를 성공적으로 업로드하였습니다.', + uploadOne: '파일 업로드중 ({percentage}%)...', + uploadMany: '파일 {max} 개 중 {current} 번째 파일 업로드 중 ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ku.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ku.js new file mode 100644 index 00000000..d03fc085 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ku.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ku', { + abort: 'بارکردنەکە بڕدرا لەلایەن بەکارهێنەر.', + doneOne: 'پەڕگەکە بەسەرکەوتووانە بارکرا.', + doneMany: 'بەسەرکەوتووانە بارکرا %1 پەڕگە.', + uploadOne: 'پەڕگە باردەکرێت ({percentage}%)...', + uploadMany: 'پەڕگە باردەکرێت, {current} لە {max} ئەنجامدراوە ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/lv.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/lv.js new file mode 100644 index 00000000..816af763 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/lv.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'lv', { + abort: 'Augšupielādi atcēla lietotājs.', + doneOne: 'Fails veiksmīgi ielādēts.', + doneMany: 'Veiksmīgi ielādēts %1 fails.', + uploadOne: 'Ielādāju failu ({percentage}%)...', + uploadMany: 'Ielādēju failus, {curent} no {max} izpildīts ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nb.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nb.js new file mode 100644 index 00000000..b05e7f5b --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nb.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'nb', { + abort: 'Opplasting ble avbrutt av brukeren.', + doneOne: 'Filen har blitt lastet opp.', + doneMany: 'Fullført opplasting av %1 filer.', + uploadOne: 'Laster opp fil ({percentage}%)...', + uploadMany: 'Laster opp filer, {current} av {max} fullført ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nl.js new file mode 100644 index 00000000..c779591d --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/nl.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'nl', { + abort: 'Upload gestopt door de gebruiker.', + doneOne: 'Bestand succesvol geüpload.', + doneMany: 'Succesvol %1 bestanden geüpload.', + uploadOne: 'Uploaden bestand ({percentage}%)…', + uploadMany: 'Bestanden aan het uploaden, {current} van {max} klaar ({percentage}%)…' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/no.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/no.js new file mode 100644 index 00000000..098666c2 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/no.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'no', { + abort: 'Opplasting ble avbrutt av brukeren.', + doneOne: 'Filen har blitt lastet opp.', + doneMany: 'Fullført opplasting av %1 filer.', + uploadOne: 'Laster opp fil ({percentage}%)...', + uploadMany: 'Laster opp filer, {current} av {max} fullført ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/oc.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/oc.js new file mode 100644 index 00000000..287d3a6a --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/oc.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'oc', { + abort: 'Mandadís interromput per l\'utilizaire', + doneOne: 'Fichièr mandat amb succès.', + doneMany: '%1 fichièrs mandats amb succès.', + uploadOne: 'Mandadís del fichièr en cors ({percentage} %)…', + uploadMany: 'Mandadís dels fichièrs en cors, {current} sus {max} efectuats ({percentage} %)…' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pl.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pl.js new file mode 100644 index 00000000..1caf6480 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pl.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'pl', { + abort: 'Wysyłanie przerwane przez użytkownika.', + doneOne: 'Plik został pomyślnie wysłany.', + doneMany: 'Pomyślnie wysłane pliki: %1.', + uploadOne: 'Wysyłanie pliku ({percentage}%)...', + uploadMany: 'Wysyłanie plików, gotowe {current} z {max} ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt-br.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt-br.js new file mode 100644 index 00000000..084fef78 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt-br.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'pt-br', { + abort: 'Envio cancelado pelo usuário.', + doneOne: 'Arquivo enviado com sucesso.', + doneMany: 'Enviados %1 arquivos com sucesso.', + uploadOne: 'Enviando arquivo({percentage}%)...', + uploadMany: 'Enviando arquivos, {current} de {max} completos ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt.js new file mode 100644 index 00000000..d95e5ed4 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/pt.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'pt', { + abort: 'Carregamento cancelado pelo utilizador.', + doneOne: 'Ficheiro carregado com sucesso.', + doneMany: 'Successfully uploaded %1 files.', // MISSING + uploadOne: 'Uploading file ({percentage}%)...', // MISSING + uploadMany: 'Uploading files, {current} of {max} done ({percentage}%)...' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ro.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ro.js new file mode 100644 index 00000000..33b24629 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ro.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ro', { + abort: 'Încărcare întreruptă de utilizator.', + doneOne: 'Fișier încărcat cu succes.', + doneMany: '%1 fișiere încărcate cu succes.', + uploadOne: 'Încărcare fișier ({percentage}%)...', + uploadMany: 'Încărcare fișiere, {current} din {max} realizat ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ru.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ru.js new file mode 100644 index 00000000..64f6b995 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ru.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ru', { + abort: 'Загрузка отменена пользователем', + doneOne: 'Файл успешно загружен', + doneMany: 'Успешно загружено файлов: %1', + uploadOne: 'Загрузка файла ({percentage}%)', + uploadMany: 'Загрузка файлов, {current} из {max} загружено ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sk.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sk.js new file mode 100644 index 00000000..27d4f423 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sk.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'sk', { + abort: 'Nahrávanie zrušené používateľom.', + doneOne: 'Súbor úspešne nahraný.', + doneMany: 'Úspešne nahraných %1 súborov.', + uploadOne: 'Nahrávanie súboru ({percentage}%)...', + uploadMany: 'Nahrávanie súborov, {current} z {max} hotovo ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sq.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sq.js new file mode 100644 index 00000000..84ec2332 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sq.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'sq', { + abort: 'Ngarkimi u ndërpre nga përdoruesi.', + doneOne: 'Skeda u ngarkua me sukses.', + doneMany: 'Me sukses u ngarkuan %1 skeda.', + uploadOne: 'Duke ngarkuar skedën ({percentage}%)...', + uploadMany: 'Duke ngarkuar skedat, {current} nga {max} , ngarkuar ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sv.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sv.js new file mode 100644 index 00000000..31783f88 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/sv.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'sv', { + abort: 'Uppladdning avbruten av användaren.', + doneOne: 'Filuppladdning lyckades.', + doneMany: 'Uppladdning av %1 filer lyckades.', + uploadOne: 'Laddar upp fil ({percentage}%)...', + uploadMany: 'Laddar upp filer, {current} av {max} färdiga ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/tr.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/tr.js new file mode 100644 index 00000000..1863c85e --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/tr.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'tr', { + abort: 'Gönderme işlemi kullanıcı tarafından durduruldu.', + doneOne: 'Gönderim işlemi başarılı şekilde tamamlandı.', + doneMany: '%1 dosya başarılı şekilde gönderildi.', + uploadOne: 'Dosyanın ({percentage}%) gönderildi...', + uploadMany: 'Toplam {current} / {max} dosyanın ({percentage}%) gönderildi...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ug.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ug.js new file mode 100644 index 00000000..0302401f --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/ug.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'ug', { + abort: 'يۈكلەشنى ئىشلەتكۈچى ئۈزۈۋەتتى.', + doneOne: 'ھۆججەت مۇۋەپپەقىيەتلىك يۈكلەندى.', + doneMany: 'مۇۋەپپەقىيەتلىك ھالدا %1 ھۆججەت يۈكلەندى.', + uploadOne: 'Uploading file ({percentage}%)...', // MISSING + uploadMany: 'Uploading files, {current} of {max} done ({percentage}%)...' // MISSING +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/uk.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/uk.js new file mode 100644 index 00000000..820f12a6 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/uk.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'uk', { + abort: 'Завантаження перервано користувачем.', + doneOne: 'Файл цілком завантажено.', + doneMany: 'Цілком завантажено %1 файлів.', + uploadOne: 'Завантаження файлу ({percentage}%)...', + uploadMany: 'Завантажено {current} із {max} файлів завершено на ({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh-cn.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh-cn.js new file mode 100644 index 00000000..fba45570 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh-cn.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'zh-cn', { + abort: '上传已被用户中止', + doneOne: '文件上传成功', + doneMany: '成功上传了 %1 个文件', + uploadOne: '正在上传文件({percentage}%)...', + uploadMany: '正在上传文件,{max} 中的 {current}({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh.js new file mode 100644 index 00000000..b190fc08 --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/lang/zh.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +CKEDITOR.plugins.setLang( 'uploadwidget', 'zh', { + abort: '上傳由使用者放棄。', + doneOne: '檔案成功上傳。', + doneMany: '成功上傳 %1 檔案。', + uploadOne: '正在上傳檔案({percentage}%)...', + uploadMany: '正在上傳檔案,{max} 中的 {current} 已完成({percentage}%)...' +} ); diff --git a/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/plugin.js b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/plugin.js new file mode 100644 index 00000000..ed82d2ce --- /dev/null +++ b/gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/uploadwidget/plugin.js @@ -0,0 +1,569 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +'use strict'; + +( function() { + CKEDITOR.plugins.add( 'uploadwidget', { + lang: 'az,bg,ca,cs,da,de,de-ch,el,en,en-au,eo,es,es-mx,et,eu,fa,fr,gl,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,no,oc,pl,pt,pt-br,ro,ru,sk,sq,sv,tr,ug,uk,zh,zh-cn', // %REMOVE_LINE_CORE% + requires: 'widget,clipboard,filetools,notificationaggregator', + + init: function( editor ) { + // Images which should be changed into upload widget needs to be marked with `data-widget` on paste, + // because otherwise wrong widget may handle upload placeholder element (e.g. image2 plugin would handle image). + // `data-widget` attribute is allowed only in the elements which has also `data-cke-upload-id` attribute. + editor.filter.allow( '*[!data-widget,!data-cke-upload-id]' ); + } + } ); + + /** + * This function creates an upload widget — a placeholder to show the progress of an upload. The upload widget + * is based on its {@link CKEDITOR.fileTools.uploadWidgetDefinition definition}. The `addUploadWidget` method also + * creates a `paste` event, if the {@link CKEDITOR.fileTools.uploadWidgetDefinition#fileToElement fileToElement} method + * is defined. This event helps in handling pasted files, as it will automatically check if the files were pasted and + * mark them to be uploaded. + * + * The upload widget helps to handle content that is uploaded asynchronously inside the editor. It solves issues such as: + * editing during upload, undo manager integration, getting data, removing or copying uploaded element. + * + * To create an upload widget you need to define two transformation methods: + * + * * The {@link CKEDITOR.fileTools.uploadWidgetDefinition#fileToElement fileToElement} method which will be called on + * `paste` and transform a file into a placeholder. + * * The {@link CKEDITOR.fileTools.uploadWidgetDefinition#onUploaded onUploaded} method with + * the {@link CKEDITOR.fileTools.uploadWidgetDefinition#replaceWith replaceWith} method which will be called to replace + * the upload placeholder with the final HTML when the upload is done. + * If you want to show more information about the progress you can also define + * the {@link CKEDITOR.fileTools.uploadWidgetDefinition#onLoading onLoading} and + * {@link CKEDITOR.fileTools.uploadWidgetDefinition#onUploading onUploading} methods. + * + * The simplest uploading widget which uploads a file and creates a link to it may look like this: + * + * CKEDITOR.fileTools.addUploadWidget( editor, 'uploadfile', { + * uploadUrl: CKEDITOR.fileTools.getUploadUrl( editor.config ), + * + * fileToElement: function( file ) { + * var a = new CKEDITOR.dom.element( 'a' ); + * a.setText( file.name ); + * a.setAttribute( 'href', '#' ); + * return a; + * }, + * + * onUploaded: function( upload ) { + * this.replaceWith( '' + upload.fileName + '' ); + * } + * } ); + * + * The upload widget uses {@link CKEDITOR.fileTools.fileLoader} as a helper to upload the file. A + * {@link CKEDITOR.fileTools.fileLoader} instance is created when the file is pasted and a proper method is + * called — by default it is the {@link CKEDITOR.fileTools.fileLoader#loadAndUpload} method. If you want + * to only use the `load` or `upload`, you can use the {@link CKEDITOR.fileTools.uploadWidgetDefinition#loadMethod loadMethod} + * property. + * + * Note that if you want to handle a big file, e.g. a video, you may need to use `upload` instead of + * `loadAndUpload` because the file may be too big to load it to memory at once. + * + * If you do not upload the file, you need to define {@link CKEDITOR.fileTools.uploadWidgetDefinition#onLoaded onLoaded} + * instead of {@link CKEDITOR.fileTools.uploadWidgetDefinition#onUploaded onUploaded}. + * For example, if you want to read the content of the file: + * + * CKEDITOR.fileTools.addUploadWidget( editor, 'fileReader', { + * loadMethod: 'load', + * supportedTypes: /text\/(plain|html)/, + * + * fileToElement: function( file ) { + * var el = new CKEDITOR.dom.element( 'span' ); + * el.setText( '...' ); + * return el; + * }, + * + * onLoaded: function( loader ) { + * this.replaceWith( atob( loader.data.split( ',' )[ 1 ] ) ); + * } + * } ); + * + * If you need to pass additional data to the request, you can do this using the + * {@link CKEDITOR.fileTools.uploadWidgetDefinition#additionalRequestParameters additionalRequestParameters} property. + * That data is then passed to the upload method defined by {@link CKEDITOR.fileTools.uploadWidgetDefinition#loadMethod}, + * and to the {@link CKEDITOR.editor#fileUploadRequest} event (as part of the `requestData` property). + * The syntax of that parameter is compatible with the {@link CKEDITOR.editor#fileUploadRequest} `requestData` property. + * + * CKEDITOR.fileTools.addUploadWidget( editor, 'uploadFile', { + * additionalRequestParameters: { + * foo: 'bar' + * }, + * + * fileToElement: function( file ) { + * var el = new CKEDITOR.dom.element( 'span' ); + * el.setText( '...' ); + * return el; + * }, + * + * onUploaded: function( upload ) { + * this.replaceWith( '' + upload.fileName + '' ); + * } + * } ); + * + * If you need custom `paste` handling, you need to mark the pasted element to be changed into an upload widget + * using {@link CKEDITOR.fileTools#markElement markElement}. For example, instead of the `fileToElement` helper from the + * example above, a `paste` listener can be created manually: + * + * editor.on( 'paste', function( evt ) { + * var file, i, el, loader; + * + * for ( i = 0; i < evt.data.dataTransfer.getFilesCount(); i++ ) { + * file = evt.data.dataTransfer.getFile( i ); + * + * if ( CKEDITOR.fileTools.isTypeSupported( file, /text\/(plain|html)/ ) ) { + * el = new CKEDITOR.dom.element( 'span' ), + * loader = editor.uploadRepository.create( file ); + * + * el.setText( '...' ); + * + * loader.load(); + * + * CKEDITOR.fileTools.markElement( el, 'filereader', loader.id ); + * + * evt.data.dataValue += el.getOuterHtml(); + * } + * } + * } ); + * + * Note that you can bind notifications to the upload widget on paste using + * the {@link CKEDITOR.fileTools#bindNotifications} method, so notifications will automatically + * show the progress of the upload. Because this method shows notifications about upload, do not use it if you only + * {@link CKEDITOR.fileTools.fileLoader#load load} (and not upload) a file. + * + * editor.on( 'paste', function( evt ) { + * var file, i, el, loader; + * + * for ( i = 0; i < evt.data.dataTransfer.getFilesCount(); i++ ) { + * file = evt.data.dataTransfer.getFile( i ); + * + * if ( CKEDITOR.fileTools.isTypeSupported( file, /text\/pdf/ ) ) { + * el = new CKEDITOR.dom.element( 'span' ), + * loader = editor.uploadRepository.create( file ); + * + * el.setText( '...' ); + * + * loader.upload(); + * + * CKEDITOR.fileTools.markElement( el, 'pdfuploader', loader.id ); + * + * CKEDITOR.fileTools.bindNotifications( editor, loader ); + * + * evt.data.dataValue += el.getOuterHtml(); + * } + * } + * } ); + * + * @member CKEDITOR.fileTools + * @param {CKEDITOR.editor} editor The editor instance. + * @param {String} name The name of the upload widget. + * @param {CKEDITOR.fileTools.uploadWidgetDefinition} def Upload widget definition. + */ + function addUploadWidget( editor, name, def ) { + var fileTools = CKEDITOR.fileTools, + uploads = editor.uploadRepository, + // Plugins which support all file type has lower priority than plugins which support specific types. + priority = def.supportedTypes ? 10 : 20; + + if ( def.fileToElement ) { + editor.on( 'paste', function( evt ) { + var data = evt.data, + // Fetch runtime widget definition as it might get changed in editor#widgetDefinition event. + def = editor.widgets.registered[ name ], + dataTransfer = data.dataTransfer, + filesCount = dataTransfer.getFilesCount(), + loadMethod = def.loadMethod || 'loadAndUpload', + file, i; + + if ( data.dataValue || !filesCount ) { + return; + } + + for ( i = 0; i < filesCount; i++ ) { + file = dataTransfer.getFile( i ); + + // No def.supportedTypes means all types are supported. + if ( !def.supportedTypes || fileTools.isTypeSupported( file, def.supportedTypes ) ) { + var el = def.fileToElement( file ), + loader = uploads.create( file, undefined, def.loaderType ); + + if ( el ) { + loader[ loadMethod ]( def.uploadUrl, def.additionalRequestParameters ); + + CKEDITOR.fileTools.markElement( el, name, loader.id ); + + if ( ( loadMethod == 'loadAndUpload' || loadMethod == 'upload' ) && !def.skipNotifications ) { + CKEDITOR.fileTools.bindNotifications( editor, loader ); + } + + data.dataValue += el.getOuterHtml(); + } + } + } + }, null, null, priority ); + } + + /** + * This is an abstract class that describes a definition of an upload widget. + * It is a type of {@link CKEDITOR.fileTools#addUploadWidget} method's second argument. + * + * Note that because the upload widget is a type of a widget, this definition extends + * {@link CKEDITOR.plugins.widget.definition}. + * It adds several new properties and callbacks and implements the {@link CKEDITOR.plugins.widget.definition#downcast} + * and {@link CKEDITOR.plugins.widget.definition#init} callbacks. These two properties + * should not be overwritten. + * + * Also, the upload widget definition defines a few properties ({@link #fileToElement}, {@link #supportedTypes}, + * {@link #loadMethod loadMethod}, {@link #uploadUrl} and {@link #additionalRequestParameters}) used in the + * {@link CKEDITOR.editor#paste} listener which is registered by {@link CKEDITOR.fileTools#addUploadWidget} + * if the upload widget definition contains the {@link #fileToElement} callback. + * + * @abstract + * @class CKEDITOR.fileTools.uploadWidgetDefinition + * @mixins CKEDITOR.plugins.widget.definition + */ + CKEDITOR.tools.extend( def, { + /** + * Upload widget definition overwrites the {@link CKEDITOR.plugins.widget.definition#downcast} property. + * This should not be changed. + * + * @property {String/Function} + */ + downcast: function() { + return new CKEDITOR.htmlParser.text( '' ); + }, + + /** + * Upload widget definition overwrites the {@link CKEDITOR.plugins.widget.definition#init} property. + * If you want to add some code in the `init` callback remember to call the base function. + * + * @property {Function} + */ + init: function() { + var widget = this, + id = this.wrapper.findOne( '[data-cke-upload-id]' ).data( 'cke-upload-id' ), + loader = uploads.loaders[ id ], + capitalize = CKEDITOR.tools.capitalize, + oldStyle, newStyle; + + loader.on( 'update', function( evt ) { + // Abort if widget was removed. + if ( !widget.wrapper || !widget.wrapper.getParent() ) { + // Uploading should be aborted if the editor is already destroyed (#966) or the upload widget was removed. + if ( !CKEDITOR.instances[ editor.name ] || !editor.editable().find( '[data-cke-upload-id="' + id + '"]' ).count() ) { + loader.abort(); + } + evt.removeListener(); + return; + } + + editor.fire( 'lockSnapshot' ); + + // Call users method, eg. if the status is `uploaded` then + // `onUploaded` method will be called, if exists. + var methodName = 'on' + capitalize( loader.status ); + + if ( typeof widget[ methodName ] === 'function' ) { + if ( widget[ methodName ]( loader ) === false ) { + editor.fire( 'unlockSnapshot' ); + return; + } + } + + // Set style to the wrapper if it still exists. + newStyle = 'cke_upload_' + loader.status; + if ( widget.wrapper && newStyle != oldStyle ) { + oldStyle && widget.wrapper.removeClass( oldStyle ); + widget.wrapper.addClass( newStyle ); + oldStyle = newStyle; + } + + // Remove widget on error or abort. + if ( loader.status == 'error' || loader.status == 'abort' ) { + editor.widgets.del( widget ); + } + + editor.fire( 'unlockSnapshot' ); + } ); + + loader.update(); + }, + + /** + * Replaces the upload widget with the final HTML. This method should be called when the upload is done, + * usually in the {@link #onUploaded} callback. + * + * @property {Function} + * @param {String} data HTML to replace the upload widget. + * @param {String} [mode='html'] See {@link CKEDITOR.editor#method-insertHtml}'s modes. + */ + replaceWith: function( data, mode ) { + if ( data.trim() === '' ) { + editor.widgets.del( this ); + return; + } + + var wasSelected = ( this == editor.widgets.focused ), + editable = editor.editable(), + range = editor.createRange(), + bookmark, bookmarks; + + if ( !wasSelected ) { + bookmarks = editor.getSelection().createBookmarks(); + } + + range.setStartBefore( this.wrapper ); + range.setEndAfter( this.wrapper ); + + if ( wasSelected ) { + bookmark = range.createBookmark(); + } + + editable.insertHtmlIntoRange( data, range, mode ); + + editor.widgets.checkWidgets( { initOnlyNew: true } ); + + // Ensure that old widgets instance will be removed. + // If replaceWith is called in init, because of paste then checkWidgets will not remove it. + editor.widgets.destroy( this, true ); + + if ( wasSelected ) { + range.moveToBookmark( bookmark ); + range.select(); + } else { + editor.getSelection().selectBookmarks( bookmarks ); + } + }, + + /** + * @private + * @returns {CKEDITOR.fileTools.fileLoader/null} The loader associated with this widget instance or `null` if not found. + */ + _getLoader: function() { + var marker = this.wrapper.findOne( '[data-cke-upload-id]' ); + return marker ? this.editor.uploadRepository.loaders[ marker.data( 'cke-upload-id' ) ] : null; + } + + /** + * If this property is defined, paste listener is created to transform the pasted file into an HTML element. + * It creates an HTML element which will be then transformed into an upload widget. + * It is only called for {@link #supportedTypes supported files}. + * If multiple files were pasted, this function will be called for each file of a supported type. + * + * @property {Function} fileToElement + * @param {Blob} file A pasted file to load or upload. + * @returns {CKEDITOR.dom.element} An element which will be transformed into the upload widget. + */ + + /** + * Regular expression to check if the file type is supported by this widget. + * If not defined, all files will be handled. + * + * @property {String} [supportedTypes] + */ + + /** + * The URL to which the file will be uploaded. It should be taken from the configuration using + * {@link CKEDITOR.fileTools#getUploadUrl}. + * + * @property {String} [uploadUrl] + */ + + /** + * Loader type that should be used for creating file tools requests. + * + * @property {Function} [loaderType] + */ + + /** + * An object containing additional data that should be passed to the function defined by {@link #loadMethod}. + * + * @property {Object} [additionalRequestParameters] + */ + + /** + * The type of loading operation that should be executed as a result of pasting a file. Possible options are: + * + * * `'loadAndUpload'` – Default behavior. The {@link CKEDITOR.fileTools.fileLoader#loadAndUpload} method will be + * executed, the file will be loaded first and uploaded immediately after loading is done. + * * `'load'` – The {@link CKEDITOR.fileTools.fileLoader#load} method will be executed. This loading type should + * be used if you only want to load file data without uploading it. + * * `'upload'` – The {@link CKEDITOR.fileTools.fileLoader#upload} method will be executed, the file will be uploaded + * without loading it to memory. This loading type should be used if you want to upload a big file, + * otherwise you can get an "out of memory" error. + * + * @property {String} [loadMethod=loadAndUpload] + */ + + /** + * Indicates whether default notification handling should be skipped. + * + * By default upload widget will use [Notification](https://ckeditor.com/cke4/addon/notification) plugin to provide + * feedback for upload progress and eventual success / error message. + * + * @since 4.8.0 + * @property {Boolean} [skipNotifications=false] + */ + + /** + * A function called when the {@link CKEDITOR.fileTools.fileLoader#status status} of the upload changes to `loading`. + * + * @property {Function} [onLoading] + * @param {CKEDITOR.fileTools.fileLoader} loader Loader instance. + * @returns {Boolean} + */ + + /** + * A function called when the {@link CKEDITOR.fileTools.fileLoader#status status} of the upload changes to `loaded`. + * + * @property {Function} [onLoaded] + * @param {CKEDITOR.fileTools.fileLoader} loader Loader instance. + * @returns {Boolean} + */ + + /** + * A function called when the {@link CKEDITOR.fileTools.fileLoader#status status} of the upload changes to `uploading`. + * + * @property {Function} [onUploading] + * @param {CKEDITOR.fileTools.fileLoader} loader Loader instance. + * @returns {Boolean} + */ + + /** + * A function called when the {@link CKEDITOR.fileTools.fileLoader#status status} of the upload changes to `uploaded`. + * At that point the upload is done and the upload widget should be replaced with the final HTML using + * the {@link #replaceWith} method. + * + * @property {Function} [onUploaded] + * @param {CKEDITOR.fileTools.fileLoader} loader Loader instance. + * @returns {Boolean} + */ + + /** + * A function called when the {@link CKEDITOR.fileTools.fileLoader#status status} of the upload changes to `error`. + * The default behavior is to remove the widget and it can be canceled if this function returns `false`. + * + * @property {Function} [onError] + * @param {CKEDITOR.fileTools.fileLoader} loader Loader instance. + * @returns {Boolean} If `false`, the default behavior (remove widget) will be canceled. + */ + + /** + * A function called when the {@link CKEDITOR.fileTools.fileLoader#status status} of the upload changes to `abort`. + * The default behavior is to remove the widget and it can be canceled if this function returns `false`. + * + * @property {Function} [onAbort] + * @param {CKEDITOR.fileTools.fileLoader} loader Loader instance. + * @returns {Boolean} If `false`, the default behavior (remove widget) will be canceled. + */ + } ); + + editor.widgets.add( name, def ); + } + + /** + * Marks an element which should be transformed into an upload widget. + * + * @see CKEDITOR.fileTools.addUploadWidget + * + * @member CKEDITOR.fileTools + * @param {CKEDITOR.dom.element} element Element to be marked. + * @param {String} widgetName The name of the upload widget. + * @param {Number} loaderId The ID of a related {@link CKEDITOR.fileTools.fileLoader}. + */ + function markElement( element, widgetName, loaderId ) { + element.setAttributes( { + 'data-cke-upload-id': loaderId, + 'data-widget': widgetName + } ); + } + + /** + * Binds a notification to the {@link CKEDITOR.fileTools.fileLoader file loader} so the upload widget will use + * the notification to show the status and progress. + * This function uses {@link CKEDITOR.plugins.notificationAggregator}, so even if multiple files are uploading + * only one notification is shown. Warnings are an exception, because they are shown in separate notifications. + * This notification shows only the progress of the upload, so this method should not be used if + * the {@link CKEDITOR.fileTools.fileLoader#load loader.load} method was called. It works with + * {@link CKEDITOR.fileTools.fileLoader#upload upload} and {@link CKEDITOR.fileTools.fileLoader#loadAndUpload loadAndUpload}. + * + * @member CKEDITOR.fileTools + * @param {CKEDITOR.editor} editor The editor instance. + * @param {CKEDITOR.fileTools.fileLoader} loader The file loader instance. + */ + function bindNotifications( editor, loader ) { + var aggregator, + task = null; + + loader.on( 'update', function() { + // Value of uploadTotal is known after upload start. Task will be created when uploadTotal is present. + if ( !task && loader.uploadTotal ) { + createAggregator(); + task = aggregator.createTask( { weight: loader.uploadTotal } ); + } + + if ( task && loader.status == 'uploading' ) { + task.update( loader.uploaded ); + } + } ); + + loader.on( 'uploaded', function() { + task && task.done(); + } ); + + loader.on( 'error', function() { + task && task.cancel(); + editor.showNotification( loader.message, 'warning' ); + } ); + + loader.on( 'abort', function() { + task && task.cancel(); + // Editor could be already destroyed (#966). + if ( CKEDITOR.instances[ editor.name ] ) { + editor.showNotification( editor.lang.uploadwidget.abort, 'info' ); + } + } ); + + function createAggregator() { + aggregator = editor._.uploadWidgetNotificaionAggregator; + + // Create one notification aggregator for all types of upload widgets for the editor. + if ( !aggregator || aggregator.isFinished() ) { + aggregator = editor._.uploadWidgetNotificaionAggregator = new CKEDITOR.plugins.notificationAggregator( + editor, editor.lang.uploadwidget.uploadMany, editor.lang.uploadwidget.uploadOne ); + + aggregator.once( 'finished', function() { + var tasks = aggregator.getTaskCount(); + + if ( tasks === 0 ) { + aggregator.notification.hide(); + } else { + aggregator.notification.update( { + message: tasks == 1 ? + editor.lang.uploadwidget.doneOne : + editor.lang.uploadwidget.doneMany.replace( '%1', tasks ), + type: 'success', + important: 1 + } ); + } + } ); + } + } + } + + // Two plugins extend this object. + if ( !CKEDITOR.fileTools ) { + CKEDITOR.fileTools = {}; + } + + CKEDITOR.tools.extend( CKEDITOR.fileTools, { + addUploadWidget: addUploadWidget, + markElement: markElement, + bindNotifications: bindNotifications + } ); +} )(); diff --git a/gsoc/urls.py b/gsoc/urls.py index 6a706bb7..1994c2a7 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -78,3 +78,8 @@ url(r'^article/publish/(?P[0-9]+)/', gsoc.views.publish_article, name='publish_article') ] + +# Upload images +urlpatterns += [ + url(r'^upload/', gsoc.views.upload_file) +] \ No newline at end of file diff --git a/gsoc/views.py b/gsoc/views.py index 636927eb..7b259fe2 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -18,6 +18,8 @@ from django.core.exceptions import ValidationError from django.shortcuts import redirect from django.urls import reverse +from django.conf import settings +from django.views.decorators.csrf import csrf_exempt from aldryn_newsblog.models import Article @@ -29,6 +31,28 @@ from profanityfilter import ProfanityFilter +# handle file upload + +@csrf_exempt +def upload_file(request): + file = request.FILES['upload'] + filename = str(uuid.uuid4()) + '.' + file.name.split('.')[-1] + filepath = os.path.join('media/uploads', filename) + fileurl = os.path.join('/', filepath) + abspath = os.path.join(settings.BASE_DIR, filepath) + + with open(abspath, 'wb+') as destination: + for chunk in file.chunks(): + destination.write(chunk) + + return JsonResponse({ + 'uploaded': 1, + 'fileName': filename, + 'url': fileurl + }) + +# handle redirect to blogs + def redirect_blogs_list(request): return HttpResponseRedirect(f'/') From 08a6c2233b0bb7aa9c107f9f74214820ff25fb0e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 21 Jul 2019 11:07:44 +0530 Subject: [PATCH 0430/1137] Fix pep8 warnings --- gsoc/urls.py | 2 +- gsoc/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/urls.py b/gsoc/urls.py index 1994c2a7..662befc0 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -82,4 +82,4 @@ # Upload images urlpatterns += [ url(r'^upload/', gsoc.views.upload_file) -] \ No newline at end of file +] diff --git a/gsoc/views.py b/gsoc/views.py index 7b259fe2..97340812 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -40,11 +40,11 @@ def upload_file(request): filepath = os.path.join('media/uploads', filename) fileurl = os.path.join('/', filepath) abspath = os.path.join(settings.BASE_DIR, filepath) - + with open(abspath, 'wb+') as destination: for chunk in file.chunks(): destination.write(chunk) - + return JsonResponse({ 'uploaded': 1, 'fileName': filename, From 8ab6efd8153ff307744096574bf83e1fe0f4e040 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 21 Jul 2019 11:28:15 +0530 Subject: [PATCH 0431/1137] Autocreate dir --- gsoc/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gsoc/views.py b/gsoc/views.py index 97340812..b9dd52c2 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -40,6 +40,8 @@ def upload_file(request): filepath = os.path.join('media/uploads', filename) fileurl = os.path.join('/', filepath) abspath = os.path.join(settings.BASE_DIR, filepath) + if not os.path.exists(os.path.dirname(abspath)): + os.makedirs(os.path.dirname(abspath)) with open(abspath, 'wb+') as destination: for chunk in file.chunks(): From 8d451b9bbc19fc8f603dc7ba5df5e774730d1f3f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 21 Jul 2019 12:01:09 +0530 Subject: [PATCH 0432/1137] Reglink notif not sent when suborg accepted --- .../0048_reglink_send_notifications.py | 18 ++++++++++++++++++ gsoc/models.py | 8 +++++--- gsoc/templates/email/invite.html | 2 +- project.db | Bin 1531904 -> 1531904 bytes 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 gsoc/migrations/0048_reglink_send_notifications.py diff --git a/gsoc/migrations/0048_reglink_send_notifications.py b/gsoc/migrations/0048_reglink_send_notifications.py new file mode 100644 index 00000000..2186429b --- /dev/null +++ b/gsoc/migrations/0048_reglink_send_notifications.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.10 on 2019-07-21 06:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0047_sendemail_activation_date'), + ] + + operations = [ + migrations.AddField( + model_name='reglink', + name='send_notifications', + field=models.BooleanField(default=True), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index eb8b17d6..ff4b198c 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -272,7 +272,8 @@ def accept(self): RegLink.objects.create(user_role=1, user_suborg=self.suborg, user_gsoc_year=self.gsoc_year, - email=self.suborg_admin_email) + email=self.suborg_admin_email, + send_notifications=False) s = Scheduler.objects.filter(command='update_site_template', data=json.dumps({'template': 'index.html'}), @@ -693,6 +694,7 @@ class RegLink(models.Model): blank=True, on_delete=models.CASCADE, editable=False) reminder = models.ForeignKey(Scheduler, null=True, related_name='reglinks', blank=True, on_delete=models.CASCADE, editable=False) + send_notifications = models.BooleanField(default=True) @property def has_scheduler(self): @@ -1067,14 +1069,14 @@ def due_date_delete_from_calendar(sender, instance, **kwargs): # Add Send RegLink Schedulers when RegLink is created @receiver(models.signals.post_save, sender=RegLink) def create_send_reglink_schedulers(sender, instance, **kwargs): - if instance.scheduler is None: + if instance.scheduler is None and instance.send_notifications: instance.create_scheduler() # Add Send RegLink Reminder Schedulers when RegLink is created @receiver(models.signals.post_save, sender=RegLink) def create_send_reg_reminder_schedulers(sender, instance, **kwargs): - if instance.reminder is None: + if instance.reminder is None and instance.send_notifications: instance.create_reminder() diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index d8563e41..520f4feb 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,7 +1,7 @@ Welcome to GSoC with the Python Software Foundation!

    {% if role is not 3 %} -You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% else %}Other{% endif %} with {suborg} for Google Summer of Code {gsoc_year} with the Python Software Foundation.
    +You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% else %}Other{% endif %} with {{suborg}} for Google Summer of Code {{gsoc_year}} with the Python Software Foundation.

    You will need to register using the link below to be invited by Google.
    {% else %} diff --git a/project.db b/project.db index e3986cc6b0111c20f4b6ab607485a0e9011598e1..cde83d3a454a546c30683bd14c1e9a673ac00eb0 100644 GIT binary patch delta 809 zcmZY5S!feM7y#gz*(q)!Qo zf|k`Z*!4lIs8mp2N~q&Qg$jZog4JRlw3na{K8Se{F(Ud`5CwPmW`6b`X6FCnvu*L& zw##{rIfU>qXhbj}BxaLdKc1U9Zjyxx_;XZO(=qwGJr&V%S+QH}PCMcyCf2lq+{t<+ z^|a`^$|EEv`WTPtZq$k>&9w~NXB7I4vN`h{)j&k7mf4;mTrUo1D942?V zuyg~)Z_Wg~=L2302w(sUIKTq|n1Bc*UDVEk-$wMb4Yq9 z`NeypW;)AF{X3wN0A{V@%p^oN8HmvBlp}tFI$kRCgg#qPoE#xxNQTIG9mC#XSCu}d znCA^)zw-d$dC1)70y`4NIHWe!uT}5!l~?}hK>I*f^hA3!HrNyD>FgNniA4vt`nFV+ z`6|k`Zqa<(tNp%ef5qnVZT>1>C2cz5oEG!{b_fTc`3;~J6ap1)doMrebK*=1Y?IMt zB=_4s*@BiQ=9qL)Y!K%7H1~qdGFgU_N01g*aT(OpMHjPb88HrZs9B>ltnR$i8dNbI z?Nt{DZS7RYcp8kV6N!9j5t#{##KP@b zvOb?)h`446o%x{#f(6z(WI>fk_$IXQqufU}!F)h(QKcNRy|UF@zLUV5!mV~W+D39H^>n3t4GL~k~(Eh$%s-emZfl`&D@E4UAP kNTSxrlqhcF!?JQ3OBB}$KMQVVw8=WgJ~``(|6U>f1-1?9`v3p{ delta 718 zcmW;Je@Ih7902gUd-wLwo>1_TIz0uk7N1Z2Q~9XLQ9P=E?F-~>9z=Rv<~@@~0f9w}Hy?7sX@ zJ}Z5bPS{=wpR^|Rg}P0-sYqCd!*irfY)I>g5-L`cZdiC#QGplO7|>uCLMcA}#`T&w zE`^gZ$>a5sYofInGK+DI)zR&C@q%V^jPU-qE<+@^5?-MU(>^8HWj$y!uajh_wKrxC zlVpcA7c+-QGH#t}H*e6sl4QHJCuELf7hE(h_-wV7RPSzp@*g6vJ?x%f*?>fyoW|Yj z%N1+^skjkoo$8VrmiOD1#98`Ws8fP@&5k6gvsdCqIA#mD3L2DS>q*#H9bJtWn9oLy zAse;FP%T`gMhXlFHqW6dt3zi6R(v{NwBARI5(+j6BgHQ&75BcP}5eyfJlZc@nq+U_R@~)yD z#|Ot|d#_~L0AwBIf1+lMNaYLYDQq9}&k>UnsvX2@89nhE^RF$Ylkl}wBe&5x3rUae itT?j)ftHTafK(F From aefe22a76bb708330a9a6b4121281ef6e9aa6510 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 21 Jul 2019 20:57:44 +0530 Subject: [PATCH 0433/1137] Add titles and meta tags --- blogs_list/templates/list_view.html | 4 ++++ gsoc/templates/base.html | 5 ++--- gsoc/templates/registration/login.html | 4 ++++ .../registration/password_reset_complete.html | 4 ++++ .../registration/password_reset_confirm.html | 4 ++++ .../registration/password_reset_form.html | 4 +++- gsoc/templates/registration/register.html | 4 ++++ project.db | Bin 1531904 -> 1531904 bytes 8 files changed, 25 insertions(+), 4 deletions(-) diff --git a/blogs_list/templates/list_view.html b/blogs_list/templates/list_view.html index 47c825ef..6e496fa1 100644 --- a/blogs_list/templates/list_view.html +++ b/blogs_list/templates/list_view.html @@ -1,5 +1,9 @@ {% extends CMS_TEMPLATE %} +{% block title %} +Blogs +{% endblock %} + {% block content %} {% for blogset in blogsets %}
    diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 563928f3..26649b0a 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -2,11 +2,10 @@ - Python-GSOC - {% block title %}{% endblock %} + Python-GSoC - {% block title %}{% endblock %} - - Python GSoC – Splash + diff --git a/gsoc/templates/registration/login.html b/gsoc/templates/registration/login.html index a27d214d..bff5de33 100644 --- a/gsoc/templates/registration/login.html +++ b/gsoc/templates/registration/login.html @@ -1,5 +1,9 @@ {% extends "base.html" %} +{% block title %} +Login +{% endblock %} + {% block content %} {% if form.errors %} diff --git a/gsoc/templates/registration/password_reset_complete.html b/gsoc/templates/registration/password_reset_complete.html index 85c495df..37ae2a34 100644 --- a/gsoc/templates/registration/password_reset_complete.html +++ b/gsoc/templates/registration/password_reset_complete.html @@ -1,5 +1,9 @@ {% extends "base.html" %} +{% block title %} +Password Reset +{% endblock %} + {% block content %}

    The password has been changed!

    diff --git a/gsoc/templates/registration/password_reset_confirm.html b/gsoc/templates/registration/password_reset_confirm.html index eb0a7dc5..67918d5f 100644 --- a/gsoc/templates/registration/password_reset_confirm.html +++ b/gsoc/templates/registration/password_reset_confirm.html @@ -1,5 +1,9 @@ {% extends "base.html" %} +{% block title %} +Password Reset +{% endblock %} + {% block content %} {% if validlink %}

    Please enter (and confirm) your new password.

    diff --git a/gsoc/templates/registration/password_reset_form.html b/gsoc/templates/registration/password_reset_form.html index a2254d17..6ce021be 100644 --- a/gsoc/templates/registration/password_reset_form.html +++ b/gsoc/templates/registration/password_reset_form.html @@ -1,6 +1,8 @@ {% extends "base.html" %} -{% block title %} Forgot your Password? {% endblock %} +{% block title %} +Password Reset +{% endblock %} {% block content %}
    diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index d8701953..37a65739 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -1,5 +1,9 @@ {% extends "base.html" %} +{% block title %} +Register +{% endblock %} + {% block content %}
    {% if warning %} diff --git a/project.db b/project.db index cde83d3a454a546c30683bd14c1e9a673ac00eb0..9123964189b70de64e684ee026975986912a50d0 100644 GIT binary patch delta 1502 zcmbVMTTB#J7@jjTJ7;EL<}kpr3ks~ZR2LYS%g*kCR(5TRHp<0DAE0c@%6$Q`UJz+a zpf!ues33{Qq_s;eY1#)_8!Yo?v1uA(AH+(u8oVW@Nz=4dA8Jz4rf2b*)TT)%IVWfS z`Tp$BF#>pZW&KhJHL%?Ux*)k@T;*|g>lTjtU*|u4{ zA@xqm12f{}jpGIY8%O1VT7Ga55UD{=X!M%5r+~#urlgYlwup9? z5kNS(H4K%X0c6ou`5ETISuCWn%i$rkA00!Z1iAs?{iSRcj#sd4L)*_F3RZ)g+i3TQ0647!C%8<&+GeREq+f>! zvGX~^kxvQn`A@i=8H}d-&_t~!gdc_oU+dKG^%+XMsskl2cFB8ANKgsz6uavt|*EQ z2fk1Wv+(Uwm`~Sc;K3r8i~lTynl)Dl&(jd?qij@dI=)p%;vW@}_`CuS0+#Ruh@f8g zD*6c%Plb3M2tv!2wZueQBW*3jO!!tQTx$QFa-milT|%E*sVY2+qX42SU9-xa9x`_I$ z)JC}rCCk`H%Gg9J3hwWbm8q>Vo3<2*mnrElabAi_rzN*^7`=cj={0l&&5_BuDqcow zNmEXV1ursHpl^G-9x&Q_ca^m^ckT50{X06EdwaTeH}7iF+B>!xvNeBD7Sb7%9P2ST zmtGiZyM${dWV<7vsNUosBWYi~MDquH0iQfXK9QAf)l6i38Da_ngW=tb4Gb^=zIadg ztW^yQR7lPjfdHO61*YGx8@dtd>T<8{>S^0f(|W@~hWw8VZNcZJX?RWVGZ}A&X?m#R zUWV0iFY|+6GkK<%O4)0#>#g@z$hNAzw$OixQNO0L zyH06t$E$7?%&j3^=jwty6(#Es%U3lbw76h j;>gD6C&vz)_|f6Z-J-tyu&ON=&U$?eNH?}p2H|8d3^G8_|$FzHxwla_zOVV^{O-z$& zZBbN6P*SCgwib2-ovmdkBDPAU_eDx4L*{%5uCO`E9Jm)1WqZ+K)pebUB08;a6b(1&FD7Li2@%r%|^S47qhZ9?;{#*lSl7$dv`)yka0@w96pENAZ9n*2RG&Y(o0f1?b9>RSUdp3psCNHGOEkrLJkM{ zu@mkmCJ*euea<_o#3Uv1ODEjZIB}6l4?GY0XwK`s_H}kU+hhJoH9e-$;GinjDnvYy8t6JcIS7226Xs$FgI-U;s zyZwRmXs$GoE|pK@#?!e{T91!SWQ+MBzo!AF$mO!SSKN`-##Fx9t)Oj2`5SF31?6+a zt<2!P*ia7Rx9}DoRldW$^xrNaWBa5kw{VK`I30eqjcPuH1eR6P)bVANt`_6?vgP9> z*9r0j-mvsjKm}9zn-(K~)B4V@Es!rTO|P3Ck=}++nZFdy@dZj8 Date: Sun, 21 Jul 2019 09:48:31 -0600 Subject: [PATCH 0434/1137] Update base.html --- gsoc/templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 26649b0a..240c9e60 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -6,6 +6,7 @@ + From 7853fc04af54d98e87260383a272e759c7a2ca8d Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 21 Jul 2019 09:50:10 -0600 Subject: [PATCH 0435/1137] Update base.html --- gsoc/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 240c9e60..e19751e5 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -2,7 +2,7 @@ - Python-GSoC - {% block title %}{% endblock %} + Python GSoC - {% block title %}{% endblock %} From 54d15fac427feb3caab2be81cb3d87c2443d59bf Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 21 Jul 2019 09:54:33 -0600 Subject: [PATCH 0436/1137] Update index.html --- gsoc/templates/site/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index e194a3d0..b6100119 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -5,7 +5,7 @@ - Python GSoC – Splash + Python GSoC – Home From fe09111b32d62a02fcc2e4ac26c0f561a9d8143b Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 24 Jul 2019 13:21:40 +0530 Subject: [PATCH 0438/1137] Fix sitemaps --- gsoc/sitemaps.py | 29 +++++++++++++++++++++++++++++ gsoc/urls.py | 11 ++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 gsoc/sitemaps.py diff --git a/gsoc/sitemaps.py b/gsoc/sitemaps.py new file mode 100644 index 00000000..e9b17c96 --- /dev/null +++ b/gsoc/sitemaps.py @@ -0,0 +1,29 @@ +import urllib.parse + +from django.contrib.sitemaps import Sitemap +from django.conf import settings + +from aldryn_newsblog.cms_appconfig import NewsBlogConfig +from aldryn_newsblog.models import Article + +from cms.models import Page + + +class BlogListSitemap(Sitemap): + priority = 0.5 + + def items(self): + urls = ['/'] + blogs = NewsBlogConfig.objects.all() + for blog in blogs: + p = Page.objects.get(application_namespace=blog.namespace, publisher_is_draft=False) + urls.append(p.get_absolute_url()) + articles = Article.objects.filter(app_config=blog).all() + for i in range(len(articles) // 5): + urls.append(f'{p.get_absolute_url()}?page={i + 2}') + for article in articles: + urls.append(f'{p.get_absolute_url()}{article.slug}/') + return urls + + def location(self, obj): + return obj diff --git a/gsoc/urls.py b/gsoc/urls.py index 662befc0..c2daba4c 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -14,15 +14,16 @@ from django.views.generic.base import RedirectView import gsoc.views +import gsoc.sitemaps as sitemaps admin.autodiscover() urlpatterns = [ - url(r'^sitemap\.xml', sitemap, - {'sitemaps': { - 'cmspages': CMSSitemap, - } - }), + url(r'^sitemap.xml', sitemap, { + 'sitemaps': { + 'blog_sitemaps': sitemaps.BlogListSitemap, + } + }), url(r'^robots.txt', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots_file"), url(r'^favicon.ico', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), From 33270a837e86460cb3885573b74a42d83e338ffb Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 24 Jul 2019 13:28:10 +0530 Subject: [PATCH 0439/1137] Fix pep8 warnings --- gsoc/sitemaps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/sitemaps.py b/gsoc/sitemaps.py index e9b17c96..f949bfaf 100644 --- a/gsoc/sitemaps.py +++ b/gsoc/sitemaps.py @@ -24,6 +24,6 @@ def items(self): for article in articles: urls.append(f'{p.get_absolute_url()}{article.slug}/') return urls - + def location(self, obj): return obj From e929fc96ea9d322ac1205f6449ed76eacd983e2f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 24 Jul 2019 14:16:01 +0530 Subject: [PATCH 0440/1137] Handle exception on sitemap --- gsoc/sitemaps.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gsoc/sitemaps.py b/gsoc/sitemaps.py index f949bfaf..94b7da8a 100644 --- a/gsoc/sitemaps.py +++ b/gsoc/sitemaps.py @@ -16,13 +16,16 @@ def items(self): urls = ['/'] blogs = NewsBlogConfig.objects.all() for blog in blogs: - p = Page.objects.get(application_namespace=blog.namespace, publisher_is_draft=False) - urls.append(p.get_absolute_url()) - articles = Article.objects.filter(app_config=blog).all() - for i in range(len(articles) // 5): - urls.append(f'{p.get_absolute_url()}?page={i + 2}') - for article in articles: - urls.append(f'{p.get_absolute_url()}{article.slug}/') + try: + p = Page.objects.get(application_namespace=blog.namespace, publisher_is_draft=False) + urls.append(p.get_absolute_url()) + articles = Article.objects.filter(app_config=blog).all() + for i in range(len(articles) // 5): + urls.append(f'{p.get_absolute_url()}?page={i + 2}') + for article in articles: + urls.append(f'{p.get_absolute_url()}{article.slug}/') + except Exception as e: + continue return urls def location(self, obj): From fbcb1160679d30bfbd0a5eb00ca9aade49a19858 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 26 Jul 2019 09:56:23 +0530 Subject: [PATCH 0441/1137] Change deadlines site template --- gsoc/templates/site/deadlines.html | 52 +++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index 3d1d95b9..a82ed29f 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -28,6 +28,36 @@ + + @@ -90,7 +120,13 @@

    Blogging schedule (Student Deadlines)

    {% endfor %} - iCal Link +
    + +
    +
    + +
    + iCal Link

    Please note Google's GSoC dates and deadlines.

    @@ -99,6 +135,20 @@

    Blogging schedule (Student Deadlines)

    + + + From 653e9e11e3e92c7617fe0da47e1d70b77aeb3274 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 26 Jul 2019 13:43:42 +0530 Subject: [PATCH 0442/1137] Combine js and css --- gsoc/static/css/article.css | 62 ++++++ gsoc/static/css/comments.css | 61 ------ gsoc/static/css/python-gsoc.css | 260 ++++++++++++++++++++++- gsoc/static/css/side-menu.css | 256 ---------------------- gsoc/static/js/gsoc.js | 9 - gsoc/static/js/menu.js | 46 ---- gsoc/static/js/python-gsoc.js | 56 +++++ gsoc/templates/aldryn_newsblog/base.html | 1 - gsoc/templates/base.html | 8 +- 9 files changed, 379 insertions(+), 380 deletions(-) delete mode 100644 gsoc/static/css/comments.css delete mode 100644 gsoc/static/css/side-menu.css delete mode 100644 gsoc/static/js/gsoc.js delete mode 100644 gsoc/static/js/menu.js create mode 100644 gsoc/static/js/python-gsoc.js diff --git a/gsoc/static/css/article.css b/gsoc/static/css/article.css index 0a6af621..322f813a 100644 --- a/gsoc/static/css/article.css +++ b/gsoc/static/css/article.css @@ -111,3 +111,65 @@ article > heading { text-decoration: none; color: white; } + +.aldryn-newsblog-comments { + margin: 16px 0; + padding: 16px 24px; +} + +.aldryn-newsblog-comments form { + display: none; +} + +.aldryn-newsblog-comments #form-root { + display: block; +} + +.aldryn-newsblog-subcomments { + padding-left: 24px; + border-left: 0.1px solid #eee; +} + +.comment { + padding: 5px 10px; + margin: 5px 0px; + background: #eee; +} + +.comment.selected { + background: #ccc; +} + +.comment-container .c-username { + color: #489EBA; + font-size: 12px; + font-weight: bold; +} + +.comment-container .c-actions { + font-size: 12px; +} + +.comment-container .c-actions span { + margin-right: 10px; +} + +.comment-container .c-actions .reply { + cursor: pointer; +} + +.comment-container .c-actions .share { + cursor: pointer; +} + +.comment-container .c-actions .delete { + cursor: pointer; +} + +.comment-container .c-content { + font-size: 14px; +} + +form textarea { + border-radius: 0 0 4px 4px !important; +} diff --git a/gsoc/static/css/comments.css b/gsoc/static/css/comments.css deleted file mode 100644 index 55077fc1..00000000 --- a/gsoc/static/css/comments.css +++ /dev/null @@ -1,61 +0,0 @@ -.aldryn-newsblog-comments { - margin: 16px 0; - padding: 16px 24px; -} - -.aldryn-newsblog-comments form { - display: none; -} - -.aldryn-newsblog-comments #form-root { - display: block; -} - -.aldryn-newsblog-subcomments { - padding-left: 24px; - border-left: 0.1px solid #eee; -} - -.comment { - padding: 5px 10px; - margin: 5px 0px; - background: #eee; -} - -.comment.selected { - background: #ccc; -} - -.comment-container .c-username { - color: #489EBA; - font-size: 12px; - font-weight: bold; -} - -.comment-container .c-actions { - font-size: 12px; -} - -.comment-container .c-actions span { - margin-right: 10px; -} - -.comment-container .c-actions .reply { - cursor: pointer; -} - -.comment-container .c-actions .share { - cursor: pointer; -} - -.comment-container .c-actions .delete { - cursor: pointer; -} - -.comment-container .c-content { - font-size: 14px; -} - -form textarea { - border-radius: 0 0 4px 4px !important; -} \ No newline at end of file diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index 601c41d2..05185b22 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -1,3 +1,260 @@ +body { + color: #777; +} + +.pure-img-responsive { + max-width: 100%; + height: auto; +} + +/* +Add transition to containers so they can push in and out. +*/ +#layout, +#menu, +.menu-link { + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} + +/* +This is the parent `
    ` that contains the menu and the content area. +*/ +#layout { + position: relative; + left: 0; + padding-left: 0; +} + #layout.active #menu { + left: 150px; + width: 150px; + } + + #layout.active .menu-link { + left: 150px; + } +/* +The content `
    ` is where all your content goes. +*/ +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.content-blog { + margin: 0 auto; + padding: 0 2em; + max-width: 1600px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; + } + .header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; + } + .header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; + } + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + + + +/* +The `#menu` `
    ` is the parent `
    ` that contains the `.pure-menu` that +appears on the left side of the page. +*/ + +#menu { + margin-left: -150px; /* "#menu" width */ + width: 150px; + position: fixed; + top: 46px; + left: 0; + bottom: 0; + z-index: 1000; /* so the menu or its navicon stays above all content */ + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + /* + All anchors inside the menu should be styled like this. + */ + #menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; + } + + /* + Remove all background/borders, since we are applying them to #menu. + */ + #menu .pure-menu, + #menu .pure-menu ul { + border: none; + background: transparent; + } + + /* + Add that light border to separate items into groups. + */ + #menu .pure-menu ul, + #menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; + } + /* + Change color of the anchor links on hover/focus. + */ + #menu .pure-menu li a:hover, + #menu .pure-menu li a:focus { + background: #333; + } + + /* + This styles the selected menu item `
  • `. + */ + #menu .pure-menu-selected, + #menu .pure-menu-heading { + background: #1f8dd6; + } + /* + This styles a link within a selected menu item `
  • `. + */ + #menu .pure-menu-selected a { + color: #fff; + } + + /* + This styles the menu heading. + */ + #menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; + } + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +/* +The button to open/close the Menu is custom-made and not part of Pure. Here's +how it works: +*/ + +/* +`.menu-link` represents the responsive menu toggle that shows/hides on +small screens. +*/ +.menu-link { + position: fixed; + display: block; /* show this only on small screens */ + top: 46px; + left: 0; /* "#menu width" */ + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; /* change this value to increase/decrease button size */ + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + + .menu-link:hover, + .menu-link:focus { + background: #000; + } + + .menu-link span { + position: relative; + display: block; + } + + .menu-link span, + .menu-link span:before, + .menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; + } + + .menu-link span:before, + .menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; + } + + .menu-link span:after { + margin-top: 0.6em; + } + + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +/* +Hides the menu at `48em`, but modify this based on your app's needs. +*/ +@media (min-width: 50em) { + + .header, + .content { + padding-left: 2em; + padding-right: 2em; + } + + #layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; + } + #menu { + left: 150px; + } + + .menu-link { + position: fixed; + left: 150px; + display: none; + } + + #layout.active .menu-link { + left: 150px; + } +} + +@media (max-width: 48em) { + /* Only apply this when the window is small. Otherwise, the following + case results in extra padding on the left: + * Make the window small. + * Tap the menu to trigger the active state. + * Make the window large again. + */ + #layout.active { + position: relative; + left: 150px; + } +} + h1, h2, h3, @@ -256,8 +513,9 @@ div.problem { .grecaptcha-badge { display: none; } + .center { display: flex; justify-content: center; align-items: center; - } \ No newline at end of file +} diff --git a/gsoc/static/css/side-menu.css b/gsoc/static/css/side-menu.css deleted file mode 100644 index aeac3be3..00000000 --- a/gsoc/static/css/side-menu.css +++ /dev/null @@ -1,256 +0,0 @@ -body { - color: #777; -} - -.pure-img-responsive { - max-width: 100%; - height: auto; -} - -/* -Add transition to containers so they can push in and out. -*/ -#layout, -#menu, -.menu-link { - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - -ms-transition: all 0.2s ease-out; - -o-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; -} - -/* -This is the parent `
    ` that contains the menu and the content area. -*/ -#layout { - position: relative; - left: 0; - padding-left: 0; -} - #layout.active #menu { - left: 150px; - width: 150px; - } - - #layout.active .menu-link { - left: 150px; - } -/* -The content `
    ` is where all your content goes. -*/ -.content { - margin: 0 auto; - padding: 0 2em; - max-width: 800px; - margin-bottom: 50px; - line-height: 1.6em; -} - -.content-blog { - margin: 0 auto; - padding: 0 2em; - max-width: 1600px; - margin-bottom: 50px; - line-height: 1.6em; -} - -.header { - margin: 0; - color: #333; - text-align: center; - padding: 2.5em 2em 0; - border-bottom: 1px solid #eee; - } - .header h1 { - margin: 0.2em 0; - font-size: 3em; - font-weight: 300; - } - .header h2 { - font-weight: 300; - color: #ccc; - padding: 0; - margin-top: 0; - } - -.content-subhead { - margin: 50px 0 20px 0; - font-weight: 300; - color: #888; -} - - - -/* -The `#menu` `
    ` is the parent `
    ` that contains the `.pure-menu` that -appears on the left side of the page. -*/ - -#menu { - margin-left: -150px; /* "#menu" width */ - width: 150px; - position: fixed; - top: 46px; - left: 0; - bottom: 0; - z-index: 1000; /* so the menu or its navicon stays above all content */ - background: #191818; - overflow-y: auto; - -webkit-overflow-scrolling: touch; -} - /* - All anchors inside the menu should be styled like this. - */ - #menu a { - color: #999; - border: none; - padding: 0.6em 0 0.6em 0.6em; - } - - /* - Remove all background/borders, since we are applying them to #menu. - */ - #menu .pure-menu, - #menu .pure-menu ul { - border: none; - background: transparent; - } - - /* - Add that light border to separate items into groups. - */ - #menu .pure-menu ul, - #menu .pure-menu .menu-item-divided { - border-top: 1px solid #333; - } - /* - Change color of the anchor links on hover/focus. - */ - #menu .pure-menu li a:hover, - #menu .pure-menu li a:focus { - background: #333; - } - - /* - This styles the selected menu item `
  • `. - */ - #menu .pure-menu-selected, - #menu .pure-menu-heading { - background: #1f8dd6; - } - /* - This styles a link within a selected menu item `
  • `. - */ - #menu .pure-menu-selected a { - color: #fff; - } - - /* - This styles the menu heading. - */ - #menu .pure-menu-heading { - font-size: 110%; - color: #fff; - margin: 0; - } - -/* -- Dynamic Button For Responsive Menu -------------------------------------*/ - -/* -The button to open/close the Menu is custom-made and not part of Pure. Here's -how it works: -*/ - -/* -`.menu-link` represents the responsive menu toggle that shows/hides on -small screens. -*/ -.menu-link { - position: fixed; - display: block; /* show this only on small screens */ - top: 46px; - left: 0; /* "#menu width" */ - background: #000; - background: rgba(0,0,0,0.7); - font-size: 10px; /* change this value to increase/decrease button size */ - z-index: 10; - width: 2em; - height: auto; - padding: 2.1em 1.6em; -} - - .menu-link:hover, - .menu-link:focus { - background: #000; - } - - .menu-link span { - position: relative; - display: block; - } - - .menu-link span, - .menu-link span:before, - .menu-link span:after { - background-color: #fff; - width: 100%; - height: 0.2em; - } - - .menu-link span:before, - .menu-link span:after { - position: absolute; - margin-top: -0.6em; - content: " "; - } - - .menu-link span:after { - margin-top: 0.6em; - } - - -/* -- Responsive Styles (Media Queries) ------------------------------------- */ - -/* -Hides the menu at `48em`, but modify this based on your app's needs. -*/ -@media (min-width: 50em) { - - .header, - .content { - padding-left: 2em; - padding-right: 2em; - } - - #layout { - padding-left: 150px; /* left col width "#menu" */ - left: 0; - } - #menu { - left: 150px; - } - - .menu-link { - position: fixed; - left: 150px; - display: none; - } - - #layout.active .menu-link { - left: 150px; - } -} - -@media (max-width: 48em) { - /* Only apply this when the window is small. Otherwise, the following - case results in extra padding on the left: - * Make the window small. - * Tap the menu to trigger the active state. - * Make the window large again. - */ - #layout.active { - position: relative; - left: 150px; - } -} \ No newline at end of file diff --git a/gsoc/static/js/gsoc.js b/gsoc/static/js/gsoc.js deleted file mode 100644 index 200e408b..00000000 --- a/gsoc/static/js/gsoc.js +++ /dev/null @@ -1,9 +0,0 @@ -//js for register button -$(function() { - var chk = $('#check'); - var btn = $('#btncheck'); - - chk.on('change', function() { - btn.prop("disabled", !this.checked);//true: disabled, false: enabled - }).trigger('change'); //page load trigger event -}); diff --git a/gsoc/static/js/menu.js b/gsoc/static/js/menu.js deleted file mode 100644 index 7ace2710..00000000 --- a/gsoc/static/js/menu.js +++ /dev/null @@ -1,46 +0,0 @@ -(function (window, document) { - - var layout = document.getElementById('layout'), - menu = document.getElementById('menu'), - menuLink = document.getElementById('menuLink'), - content = document.getElementById('main'); - - function toggleClass(element, className) { - var classes = element.className.split(/\s+/), - length = classes.length, - i = 0; - - for(; i < length; i++) { - if (classes[i] === className) { - classes.splice(i, 1); - break; - } - } - // The className is not found - if (length === classes.length) { - classes.push(className); - } - - element.className = classes.join(' '); - } - - function toggleAll(e) { - var active = 'active'; - - e.preventDefault(); - toggleClass(layout, active); - toggleClass(menu, active); - toggleClass(menuLink, active); - } - - menuLink.onclick = function (e) { - toggleAll(e); - }; - - content.onclick = function(e) { - if (menu.className.indexOf('active') !== -1) { - toggleAll(e); - } - }; - -}(this, this.document)); \ No newline at end of file diff --git a/gsoc/static/js/python-gsoc.js b/gsoc/static/js/python-gsoc.js new file mode 100644 index 00000000..b9b69d83 --- /dev/null +++ b/gsoc/static/js/python-gsoc.js @@ -0,0 +1,56 @@ +//js for register button +$(function() { + var chk = $('#check'); + var btn = $('#btncheck'); + + chk.on('change', function() { + btn.prop("disabled", !this.checked);//true: disabled, false: enabled + }).trigger('change'); //page load trigger event +}); + +(function (window, document) { + + var layout = document.getElementById('layout'), + menu = document.getElementById('menu'), + menuLink = document.getElementById('menuLink'), + content = document.getElementById('main'); + + function toggleClass(element, className) { + var classes = element.className.split(/\s+/), + length = classes.length, + i = 0; + + for(; i < length; i++) { + if (classes[i] === className) { + classes.splice(i, 1); + break; + } + } + // The className is not found + if (length === classes.length) { + classes.push(className); + } + + element.className = classes.join(' '); + } + + function toggleAll(e) { + var active = 'active'; + + e.preventDefault(); + toggleClass(layout, active); + toggleClass(menu, active); + toggleClass(menuLink, active); + } + + menuLink.onclick = function (e) { + toggleAll(e); + }; + + content.onclick = function(e) { + if (menu.className.indexOf('active') !== -1) { + toggleAll(e); + } + }; + +}(this, this.document)); diff --git a/gsoc/templates/aldryn_newsblog/base.html b/gsoc/templates/aldryn_newsblog/base.html index 36151cbe..e0ecef50 100644 --- a/gsoc/templates/aldryn_newsblog/base.html +++ b/gsoc/templates/aldryn_newsblog/base.html @@ -2,7 +2,6 @@ {% block head %} - {% endblock head %} diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index bce302b0..cc287fc1 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -13,9 +13,7 @@ - - {% load static %} @@ -48,7 +46,7 @@ {% if not user.is_authenticated %}
    - Python-GSoC + Python-GSoC @@ -115,9 +113,7 @@
    {% render_block 'js' %} - - - + {% block js %}{% endblock %} From f95193f50e33bec4f847945a2c97ddd99de54288 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 26 Jul 2019 14:43:39 +0530 Subject: [PATCH 0443/1137] use cloudfare cdn for all libraries --- gsoc/templates/base.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index cc287fc1..8f024d5e 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -7,11 +7,11 @@ - + - + - From 2d7ec4c4c57cacb236893e312ef088e04d7e0967 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 26 Jul 2019 15:32:39 +0530 Subject: [PATCH 0444/1137] fix contrast --- gsoc/static/css/python-gsoc.css | 5 +++-- gsoc/templates/base.html | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index 05185b22..0a6a1f2d 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -431,7 +431,7 @@ div.problem { #topnav .pure-menu-link.pure-menu-heading { margin-top: 6px; - color: #aaa; + color: #fff; } #topnav .pure-menu-link.pure-menu-heading:hover { @@ -464,6 +464,7 @@ div.problem { .blog-list .card-container .card .title { margin: 0.5em 0; + color: #23505D; } .blog-list .card-container .card .suborg { @@ -507,7 +508,7 @@ div.problem { .card .no-proposal { font-size: 12px; - color: red; + color: #990000; } .grecaptcha-badge { diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index 8f024d5e..c7d0d1d2 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -1,6 +1,6 @@ {% load cms_tags menu_tags sekizai_tags %} - + Python GSoC - {% block title %}{% endblock %} From eefdd7dac41c88d536f0fe8c7bd161a204b7e430 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 26 Jul 2019 15:40:35 +0530 Subject: [PATCH 0445/1137] remove stale code (function to clean html in blogs) --- gsoc/models.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index ff4b198c..e918c2b6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -112,34 +112,6 @@ def get_root_comments(self): Article.add_to_class('get_root_comments', get_root_comments) -def is_unclean(self): - unclean_texts = ( - '
    ',
    -        '
    ', - '<', - '>', - ) - for _ in unclean_texts: - if _ in self.lead_in: - return True - return False - - -Article.add_to_class('is_unclean', is_unclean) - - -def clean_article_html(self): - self.lead_in = re.sub(r'
    ', '', self.lead_in)
    -    self.lead_in = re.sub(r'<\/pre>', '', self.lead_in)
    -    self.lead_in = re.sub(r'<', '<', self.lead_in)
    -    self.lead_in = re.sub(r'>', '>', self.lead_in)
    -    self.lead_in = mark_safe(self.lead_in)
    -    self.save()
    -
    -
    -Article.add_to_class('clean_article_html', clean_article_html)
    -
    -
     # Models
     
     class SubOrg(models.Model):
    @@ -1098,13 +1070,6 @@ def decrease_blog_counter(sender, instance, **kwargs):
                 up.save()
     
     
    -# Clean lead_in HTML when new Article is created
    -# @receiver(models.signals.post_save, sender=Article)
    -# def clean_html(sender, instance, **kwargs):
    -#     if instance.is_unclean():
    -#         instance.clean_article_html()
    -
    -
     # Add ArticleReveiw object when new Article is created
     @receiver(models.signals.post_save, sender=Article)
     def add_review(sender, instance, **kwargs):
    
    From 2bb3df6bab3b94c618edd0290ee9301c369abb49 Mon Sep 17 00:00:00 2001
    From: Sounak Pradhan 
    Date: Fri, 26 Jul 2019 15:43:15 +0530
    Subject: [PATCH 0446/1137] Wrap text in pre formatted text in blogs
    
    ---
     gsoc/static/css/python-gsoc.css | 8 ++++++++
     1 file changed, 8 insertions(+)
    
    diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css
    index 0a6a1f2d..f3a8c7be 100644
    --- a/gsoc/static/css/python-gsoc.css
    +++ b/gsoc/static/css/python-gsoc.css
    @@ -520,3 +520,11 @@ div.problem {
         justify-content: center;
         align-items: center;
     }
    +
    +pre {
    +    white-space: pre-wrap;       /* css-3 */
    +    white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
    +    white-space: -pre-wrap;      /* Opera 4-6 */
    +    white-space: -o-pre-wrap;    /* Opera 7 */
    +    word-wrap: break-word;       /* Internet Explorer 5.5+ */
    +}
    \ No newline at end of file
    
    From f08702253a762127ddf0a91b08b6ca44005e65e8 Mon Sep 17 00:00:00 2001
    From: Sounak Pradhan 
    Date: Fri, 26 Jul 2019 17:32:31 +0530
    Subject: [PATCH 0447/1137] Add menu items for admin
    
    ---
     gsoc/cms_toolbars.py | 23 +++++++++++++++++------
     1 file changed, 17 insertions(+), 6 deletions(-)
    
    diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py
    index 2badc6bb..f95de1ca 100644
    --- a/gsoc/cms_toolbars.py
    +++ b/gsoc/cms_toolbars.py
    @@ -53,17 +53,28 @@ def add_admin_menu(self):
             if user and user.is_superuser:
                 self._admin_menu.add_sideframe_item(_('Schedulers'),
                                                     url=admin_reverse('gsoc_scheduler_changelist'))
    -        self._admin_menu.add_break(ADMINISTRATION_BREAK)
    -
    -        # cms users settings
    -        self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change'))
    -        self._admin_menu.add_break(USER_SETTINGS_BREAK)
    -        if user and user.is_superuser:
    +            self._admin_menu.add_sideframe_item(_('Builders'),
    +                                                url=admin_reverse('gsoc_builder_changelist'))
    +            self._admin_menu.add_sideframe_item(_('Review Article'),
    +                                                url=admin_reverse('gsoc_articlereview_changelist'))
    +            self._admin_menu.add_sideframe_item(_('Timeline'),
    +                                                url=admin_reverse('gsoc_timeline_changelist'))
    +            self._admin_menu.add_sideframe_item(_('Send Email'),
    +                                                url=admin_reverse('gsoc_sendemail_add'))
    +            self._admin_menu.add_sideframe_item(_('Suborg Applications'),
    +                                                url=admin_reverse('gsoc_suborgdetails_changelist'))
                 self._admin_menu.add_modal_item(
                     name='Add Users',
                     url=admin_reverse('gsoc_adduserlog_add'),
                     on_close=None,
                     )
    +
    +        
    +        self._admin_menu.add_break(ADMINISTRATION_BREAK)
    +
    +        # cms users settings
    +        self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change'))
    +        self._admin_menu.add_break(USER_SETTINGS_BREAK)
             # clipboard
             if self.toolbar.edit_mode_active:
                 # True if the clipboard exists and there's plugins in it.
    
    From 846fce9c281783ba03573d1cdb33430eec00d923 Mon Sep 17 00:00:00 2001
    From: Sounak Pradhan 
    Date: Fri, 26 Jul 2019 17:41:35 +0530
    Subject: [PATCH 0448/1137] Add menu items for suborg
    
    ---
     gsoc/cms_toolbars.py | 7 ++++++-
     1 file changed, 6 insertions(+), 1 deletion(-)
    
    diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py
    index f95de1ca..2f1ee237 100644
    --- a/gsoc/cms_toolbars.py
    +++ b/gsoc/cms_toolbars.py
    @@ -68,7 +68,12 @@ def add_admin_menu(self):
                     url=admin_reverse('gsoc_adduserlog_add'),
                     on_close=None,
                     )
    -
    +        
    +        if user:
    +            self._admin_menu.add_link_item(_('New Suborg Application'),
    +                                           reverse('suborg:register_suborg'))
    +            self._admin_menu.add_link_item(_('Manage Suborg Application'),
    +                                           reverse('suborg:application_list'))
             
             self._admin_menu.add_break(ADMINISTRATION_BREAK)
     
    
    From eb7cee36a9506db57d48c9bc7ea67d1807df20c0 Mon Sep 17 00:00:00 2001
    From: Sounak Pradhan 
    Date: Fri, 26 Jul 2019 17:43:55 +0530
    Subject: [PATCH 0449/1137] Fix pep8 warnings
    
    ---
     gsoc/cms_toolbars.py | 7 ++++---
     1 file changed, 4 insertions(+), 3 deletions(-)
    
    diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py
    index 2f1ee237..e9d1253c 100644
    --- a/gsoc/cms_toolbars.py
    +++ b/gsoc/cms_toolbars.py
    @@ -68,17 +68,18 @@ def add_admin_menu(self):
                     url=admin_reverse('gsoc_adduserlog_add'),
                     on_close=None,
                     )
    -        
    +
             if user:
                 self._admin_menu.add_link_item(_('New Suborg Application'),
                                                reverse('suborg:register_suborg'))
                 self._admin_menu.add_link_item(_('Manage Suborg Application'),
                                                reverse('suborg:application_list'))
    -        
    +
             self._admin_menu.add_break(ADMINISTRATION_BREAK)
     
             # cms users settings
    -        self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change'))
    +        self._admin_menu.add_sideframe_item(_('User settings'),
    +                                            url=admin_reverse('cms_usersettings_change'))
             self._admin_menu.add_break(USER_SETTINGS_BREAK)
             # clipboard
             if self.toolbar.edit_mode_active:
    
    From 966b9edd54d3f98e616887e7b3aa373206633874 Mon Sep 17 00:00:00 2001
    From: Sounak Pradhan 
    Date: Sat, 27 Jul 2019 18:03:19 +0530
    Subject: [PATCH 0450/1137] Update robots.txt
    
    ---
     gsoc/common/utils/tools.py | 11 +++++++++--
     1 file changed, 9 insertions(+), 2 deletions(-)
    
    diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py
    index 5e91b780..0eedd6eb 100644
    --- a/gsoc/common/utils/tools.py
    +++ b/gsoc/common/utils/tools.py
    @@ -85,23 +85,30 @@ def is_year(file_name):
             return False
     
     
    -def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md']):
    +def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md', 'favicon.ico', 'robots.txt']):
         contents = repo.get_contents('')
         files = []
         while contents:
             file_content = contents.pop(0)
             if not (file_content.path in except_files or is_year(file_content.path)):
    -            if file_content.type == "dir":
    +            if file_content.type == 'dir':
                     contents.extend(repo.get_contents(file_content.path))
                 else:
                     files.append(file_content)
         return files
     
     
    +def update_robots_file(repo, current_year):
    +    c = repo.get_contents('robots.txt')
    +    new_content = c.decoded_content.strip() + f'\nDisallow: /{current_year}/\n'.encode()
    +    repo.update_file(c.path, 'Update robots.txt', new_content, c.sha)
    +
    +
     def archive_current_gsoc_files(current_year):
         g = Github(settings.GITHUB_ACCESS_TOKEN)
         repo = g.get_repo(settings.STATIC_SITE_REPO)
         files = get_files(repo)
    +    update_robots_file(repo, current_year)
         for file in files:
             try:
                 repo.create_file(f'{current_year}/{file.path}',
    
    From cbd05f45f671e4a610359a79d010296fac7b3a4e Mon Sep 17 00:00:00 2001
    From: Sounak Pradhan 
    Date: Sat, 27 Jul 2019 19:46:09 +0530
    Subject: [PATCH 0451/1137] Use recaptcha v2 checkbox instead of v3
    
    ---
     gsoc/templates/aldryn_newsblog/includes/comments.html | 2 ++
     gsoc/templates/base.html                              | 3 ++-
     gsoc/views.py                                         | 6 ++----
     settings_local.py.template                            | 5 ++---
     4 files changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html
    index a8456625..52fe9822 100644
    --- a/gsoc/templates/aldryn_newsblog/includes/comments.html
    +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html
    @@ -70,5 +70,7 @@
             1000 characters left
         
     
    +    
    + \ No newline at end of file diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index c7d0d1d2..fdc6c8a5 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -15,7 +15,8 @@ crossorigin="anonymous" > - + + {% load static %} {% block head %}{% endblock %} diff --git a/gsoc/views.py b/gsoc/views.py index b9dd52c2..2e36fcf5 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -268,6 +268,7 @@ def new_comment(request): # verification and delete the variable to enable recaptcha verification disable_recaptcha = os.getenv('DISABLE_RECAPTCHA', None) + flag = True if not disable_recaptcha: recaptcha_response = request.POST.get('g-recaptcha-response') url = 'https://www.google.com/recaptcha/api/siteverify' @@ -281,10 +282,7 @@ def new_comment(request): response = urllib.request.urlopen(req) result = json.loads(response.read().decode()) - flag = True - if not disable_recaptcha: - flag = (result['success'] and result['action'] == 'comment' - and result['score'] >= settings.RECAPTCHA_THRESHOLD) + flag = result['success'] if flag: # if score greater than threshold allow to add diff --git a/settings_local.py.template b/settings_local.py.template index c7df19a8..43bf63cc 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -51,9 +51,8 @@ ADMIN_EMAIL = 'gsoc-admins@python.org' # reCAPTCHA settings # update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually -RECAPTCHA_PRIVATE_KEY = '6LdAVqIUAAAAACv_tNPudwx_pD9dbjtwvr3WwQ9Y' -RECAPTCHA_PUBLIC_KEY = '6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj' -RECAPTCHA_THRESHOLD = 0.1 +RECAPTCHA_PRIVATE_KEY = '6LcL0q8UAAAAAFPz31u0Ce9gnbEjhFou19c4MhnQ' +RECAPTCHA_PUBLIC_KEY = '6LcL0q8UAAAAALYynEklThsKgSVZ2B1kubc-Y6br' # google oauth client_config GOOGLE_API_SCOPES = [ From 9ea7055a792edbe81bda3d9e3042181eabb3c6af Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 28 Jul 2019 07:36:33 +0530 Subject: [PATCH 0452/1137] Add Change Password functionality --- gsoc/templates/aldryn_newsblog/base.html | 16 +---------- gsoc/templates/base.html | 2 -- gsoc/templates/myprofile.html | 12 ++------- .../registration/change_password.html | 18 +++++++++++++ gsoc/urls.py | 3 ++- gsoc/views.py | 27 ++++++++++++++++--- 6 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 gsoc/templates/registration/change_password.html diff --git a/gsoc/templates/aldryn_newsblog/base.html b/gsoc/templates/aldryn_newsblog/base.html index e0ecef50..2e96c5ac 100644 --- a/gsoc/templates/aldryn_newsblog/base.html +++ b/gsoc/templates/aldryn_newsblog/base.html @@ -1,6 +1,7 @@ {% extends CMS_TEMPLATE %} {% block head %} + {% endblock head %} @@ -13,19 +14,4 @@ {% block js %} - {% endblock %} \ No newline at end of file diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index fdc6c8a5..ad7b23d3 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -15,8 +15,6 @@ crossorigin="anonymous" > - - {% load static %} {% block head %}{% endblock %} diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index 5227da0e..c8c6ba48 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -6,11 +6,11 @@ {% block content %}
    -

    Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!

    +

    Welcome{% if not user.is_anonymous %} {{ user.username }}{% endif %}!

    {% if user.is_anonymous %}
    Please login here.
    {% endif %} - + {% if user %} {% if user.is_current_year_student %} - - + {% endif %} +
    {% endblock %} diff --git a/gsoc/views.py b/gsoc/views.py index 843d1cc3..404e569d 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -116,9 +116,7 @@ def scan_proposal(file): @decorators.login_required def after_login_view(request): user = request.user - if user.is_current_year_student() and not user.has_proposal(): - return shortcuts.redirect('/myprofile') - return shortcuts.redirect('/') + return shortcuts.redirect('/myprofile') @decorators.login_required From 56d528c613edb3ffbf4513171112370c4a4659be Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 28 Jul 2019 15:12:08 +0530 Subject: [PATCH 0454/1137] Remove cached content when adding comment so that the comment gets rendered --- gsoc/common/utils/memcached_stats.py | 47 ++++++++++++++++++++++++++++ gsoc/views.py | 9 ++++++ requirements.txt | 4 +-- 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 gsoc/common/utils/memcached_stats.py diff --git a/gsoc/common/utils/memcached_stats.py b/gsoc/common/utils/memcached_stats.py new file mode 100644 index 00000000..7018cf45 --- /dev/null +++ b/gsoc/common/utils/memcached_stats.py @@ -0,0 +1,47 @@ +import re, telnetlib, sys + +class MemcachedStats: + + _client = None + _key_regex = re.compile(r'ITEM (.*) \[(.*); (.*)\]') + _slab_regex = re.compile(r'STAT items:(.*):number') + _stat_regex = re.compile(r"STAT (.*) (.*)\r") + + def __init__(self, host='localhost', port='11211', timeout=None): + self._host = host + self._port = port + self._timeout = timeout + + @property + def client(self): + if self._client is None: + self._client = telnetlib.Telnet(self._host, self._port, + self._timeout) + return self._client + + def command(self, cmd): + ' Write a command to telnet and return the response ' + self.client.write(("%s\n" % cmd).encode('ascii')) + return self.client.read_until(b'END').decode('ascii') + + def key_details(self, sort=True, limit=100): + ' Return a list of tuples containing keys and details ' + cmd = 'stats cachedump %s %s' + keys = [key for id in self.slab_ids() + for key in self._key_regex.findall(self.command(cmd % (id, limit)))] + if sort: + return sorted(keys) + else: + return keys + + def keys(self, sort=True, limit=100): + ' Return a list of keys in use ' + return [key[0] for key in self.key_details(sort=sort, limit=limit)] + + def slab_ids(self): + ' Return a list of slab ids in use ' + return self._slab_regex.findall(self.command('stats items')) + + def stats(self): + ' Return a dict containing memcached stats ' + return dict(self._stat_regex.findall(self.command('stats'))) diff --git a/gsoc/views.py b/gsoc/views.py index 404e569d..129a313e 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,5 +1,6 @@ from gsoc import settings +from .common.utils.memcached_stats import MemcachedStats from .forms import ProposalUploadForm from .models import (RegLink, ProposalTextValidator, Comment, ArticleReview, GsocYear) @@ -17,6 +18,7 @@ from django import shortcuts from django.http import JsonResponse, HttpResponseRedirect from django.core.exceptions import ValidationError +from django.core.cache import cache from django.shortcuts import redirect from django.urls import reverse from django.conf import settings @@ -340,6 +342,13 @@ def new_comment(request): redirect_path = request.POST.get('redirect') + mem = MemcachedStats() + keys = [_[3:] for _ in mem.keys()] + for key in keys: + if 'cache_page' in key or 'cache_header' in key: + print(key, cache.get(key)) + cache.delete(key) + if redirect_path: return redirect(redirect_path) else: diff --git a/requirements.txt b/requirements.txt index 21e0da73..3d31a7dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,5 +41,5 @@ google-auth-oauthlib>=0.3.0 #github api for pushing html pages PyGithub>=1.43.7 -#memcached -#pylibmc>=1.6.0 +python-memcached +pylibmc>=1.6.0 From da79a48f951c5f4e2ce22be1c1410c47523db1c4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 28 Jul 2019 16:41:05 +0530 Subject: [PATCH 0455/1137] Add blog history --- gsoc/admin.py | 17 ++++++++++++++++- gsoc/cms_toolbars.py | 4 ++++ gsoc/migrations/0049_blogposthistory.py | 24 ++++++++++++++++++++++++ gsoc/models.py | 25 +++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 gsoc/migrations/0049_blogposthistory.py diff --git a/gsoc/admin.py b/gsoc/admin.py index 21005a36..003773f3 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,6 +1,6 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, - GsocEndDate, Comment, SendEmail) + GsocEndDate, Comment, SendEmail, BlogPostHistory) from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, GsocEndDateForm) @@ -544,3 +544,18 @@ def has_change_permission(self, request, obj=None): admin.site.register(SendEmail, SendEmailAdmin) + + +class BlogPostHistoryAdmin(admin.ModelAdmin): + list_display = ('article', 'timestamp') + list_filter = ('article', ) + fields = ('article', 'content_safe', 'timestamp') + + def has_change_permission(self, request, obj=None): + return False + + def content_safe(self, obj): + return mark_safe(obj.content) + + +admin.site.register(BlogPostHistory, BlogPostHistoryAdmin) \ No newline at end of file diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index e9d1253c..fd21112a 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -242,6 +242,10 @@ def populate(self): side=self.toolbar.RIGHT) except ArticleReview.DoesNotExist: pass + + if user.is_superuser: + url = f"{admin_reverse('gsoc_blogposthistory_changelist')}?article__id__exact={article.id}" + self.toolbar.add_sideframe_item(_('View History'), url=url) NewsBlogToolbar.populate = populate diff --git a/gsoc/migrations/0049_blogposthistory.py b/gsoc/migrations/0049_blogposthistory.py new file mode 100644 index 00000000..b10bacee --- /dev/null +++ b/gsoc/migrations/0049_blogposthistory.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.10 on 2019-07-28 09:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('aldryn_newsblog', '0016_auto_20180329_1417'), + ('gsoc', '0048_reglink_send_notifications'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPostHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('content', models.TextField(blank=True, null=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), + ], + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 8a92f592..9c5cd169 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -469,6 +469,12 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) +class BlogPostHistory(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + timestamp = models.DateTimeField(auto_now_add=True) + content = models.TextField(null=True, blank=True) + + class BlogPostDueDate(models.Model): categories = ( (0, 'Weekly Check-In'), @@ -1090,3 +1096,22 @@ def add_review(sender, instance, **kwargs): ar = ar.first() ar.is_reviewed = False ar.save() + + +# Add ArticleReveiw object when new Article is created +@receiver(models.signals.post_save, sender=Article) +def add_review(sender, instance, **kwargs): + ar = ArticleReview.objects.filter(article=instance).all() + if not ar: + ArticleReview.objects.create(article=instance) + + if ar: + ar = ar.first() + ar.is_reviewed = False + ar.save() + + +# Add BlogPostHistory object when new Article is created +@receiver(models.signals.post_save, sender=Article) +def add_history(sender, instance, **kwargs): + BlogPostHistory.objects.create(article=instance, content=instance.lead_in) From bb564a6f5e0e0a75f05bd1e0014342c7ba54dfd3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 28 Jul 2019 16:41:38 +0530 Subject: [PATCH 0456/1137] Migrate db --- project.db | Bin 1531904 -> 1540096 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/project.db b/project.db index 9123964189b70de64e684ee026975986912a50d0..52e1857fc135ee6748b13c4598f58919c39d4ab6 100644 GIT binary patch delta 1270 zcmZ{iPi)gx9LN2hpZ(&rwi_C!K!I5~AX?huwsD)nw52KqQAA6dhyzwBX<|Z9o1~2e zsrb{R95SS>B8pU>*pZnYizcPmHQxTttmBT-(Dmg1*v>C zF*}z{<+6F>YC3OZ7nd~an}J&#*`2B4$TP@hEMM^nv#(7e$-Ff2d4Ci#FG>0wb9}I*F_feZJ7p_MXlQ2FzxFA;L*2?? zcVAfU?LF?ip`LyvJlGqy*$e({?s){GmNLru=`6Eh;+$0TuwAUcY%v=o!M|v^9RU?; z*VFoq-A9AiK?H`Nmel8TLvM&i>(Rv3cqY{to!XB;Kh)yMWP@;0LZAa)e4jFJN3Tmy zmjGuoThgCi2oI5}_CtGnNAe~PVN{|-S%CO8KF5B`{6nT;@rv+=aDsoxKd1}`5FbHj zh+-7krDWiR2fV2Iia*;s+tM2Vyf%p7w_kj}CI1kiTh<~pk_G-GCH^L^iD_{_q=gOP zk#I*iFC6Co+E3#51x}y6jwp>~x*{q3yyeQ6d`}{fuFb zux_QBbgC|O+)Omu>1gXlH){Jz-C~eaY`x{$QD#HYiVw%R#pjg7T5zr)0s7l_RU;xJ^~z-322)JEyy8+}6Np zbHVVpnqS@WdyrF!`8Wl_oROZ_^G19=XD%$FHuJ}8fMtWSp%)BuE9cLE4*TJJ-~zP6 Omjh=I@LHhwR(uDiH>cYG delta 448 zcmXZXQ7B|l7{Kv!?!D(8_ZsK8%$?Dia!XCAS)&NGsTqvPpr*)!A|l&R+dNIH(3LW_ zcI9?!zN-4ZaNs@_a-*z9o&8tcHww(H%hkswE^V#;4eKxt>X#7D4=Rgv{ zg^*TpBJB`cJ1oLW=o6p)-@iK94%e_F$2Y&2Z!F$BoUouyZ*_SYUJBtyFA*8Sc*1i& zM?Vh-n-RNz0E^AQpE{*;64Eu_B`GJ)ma+;<*db1}%+E><7 z#eAxWfdpir00LA%K^4$|4yu6<)BpxJs09Y76RBeT$#=O$Rd;CGKMKzu5uuLzD_RIP z`ZzcNYxIL2&zt$z^uOj@T+ovoQQaXCizZcy!(YahZ;; From 8d87796ffa38f2c975a8754ebdeac51293bceb32 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 09:54:27 +0530 Subject: [PATCH 0457/1137] Fix pep8 warnings --- gsoc/admin.py | 3 ++- gsoc/cms_toolbars.py | 5 +++-- gsoc/common/utils/memcached_stats.py | 7 +++++-- gsoc/views.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 003773f3..770b6a65 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -368,6 +368,7 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs): class RegLinkInline(admin.TabularInline): model = RegLink form = RegLinkForm + extra = 1 class AddUserLogAdmin(admin.ModelAdmin): @@ -558,4 +559,4 @@ def content_safe(self, obj): return mark_safe(obj.content) -admin.site.register(BlogPostHistory, BlogPostHistoryAdmin) \ No newline at end of file +admin.site.register(BlogPostHistory, BlogPostHistoryAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index fd21112a..b09a0c0f 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -242,9 +242,10 @@ def populate(self): side=self.toolbar.RIGHT) except ArticleReview.DoesNotExist: pass - + if user.is_superuser: - url = f"{admin_reverse('gsoc_blogposthistory_changelist')}?article__id__exact={article.id}" + url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" + f"?article__id__exact={article.id}") self.toolbar.add_sideframe_item(_('View History'), url=url) diff --git a/gsoc/common/utils/memcached_stats.py b/gsoc/common/utils/memcached_stats.py index 7018cf45..9b7d267b 100644 --- a/gsoc/common/utils/memcached_stats.py +++ b/gsoc/common/utils/memcached_stats.py @@ -1,4 +1,7 @@ -import re, telnetlib, sys +import re +import telnetlib +import sys + class MemcachedStats: @@ -28,7 +31,7 @@ def key_details(self, sort=True, limit=100): ' Return a list of tuples containing keys and details ' cmd = 'stats cachedump %s %s' keys = [key for id in self.slab_ids() - for key in self._key_regex.findall(self.command(cmd % (id, limit)))] + for key in self._key_regex.findall(self.command(cmd % (id, limit)))] if sort: return sorted(keys) else: diff --git a/gsoc/views.py b/gsoc/views.py index 129a313e..acedd9ab 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -348,7 +348,7 @@ def new_comment(request): if 'cache_page' in key or 'cache_header' in key: print(key, cache.get(key)) cache.delete(key) - + if redirect_path: return redirect(redirect_path) else: From b847a8b11ece55e3605bd933901f0a124e7321a7 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 10:22:33 +0530 Subject: [PATCH 0458/1137] Add email log handler --- gsoc/settings.py | 3 ++- settings_local.py.template | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index e359fb5b..81213d20 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -296,7 +296,8 @@ def gettext(s): return s }, 'mail_admins': { 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler' + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': True }, }, 'loggers': { diff --git a/settings_local.py.template b/settings_local.py.template index 43bf63cc..fdce8409 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -49,6 +49,9 @@ EMAIL_PORT = 25 REPLY_EMAIL = 'gsoc-admins@python.org' ADMIN_EMAIL = 'gsoc-admins@python.org' +# Admins +ADMINS = ['matthew.lagoe@gmail.com', 'sounak.98@gmail.com'] + # reCAPTCHA settings # update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually RECAPTCHA_PRIVATE_KEY = '6LcL0q8UAAAAAFPz31u0Ce9gnbEjhFou19c4MhnQ' From edafd5e0f236cf6087510e6ab935759be4eacc40 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 10:33:41 +0530 Subject: [PATCH 0459/1137] change ADMIN_EMAIL to ADMINS --- gsoc/management/commands/runcron.py | 4 ++-- settings_local.py.template | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index 6d0f285c..15ee89f5 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -75,7 +75,7 @@ def build_items(self, options): builder.built = False builder.last_error = err builder.save() - send_mail(settings.ADMIN_EMAIL, + send_mail(settings.ADMINS, 'Exception on runcron build_items', 'cron_error.html', { @@ -107,7 +107,7 @@ def handle_process(self, scheduler): scheduler.success = False scheduler.last_error = err scheduler.save() - send_mail(settings.ADMIN_EMAIL, + send_mail(settings.ADMINS, 'Exception on runcron process_items', 'cron_error.html', { diff --git a/settings_local.py.template b/settings_local.py.template index fdce8409..f82bd527 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -47,10 +47,9 @@ EMAIL_PORT = 25 #EMAIL_HOST_USER = "" #EMAIL_HOST_PASSWORD = "" REPLY_EMAIL = 'gsoc-admins@python.org' -ADMIN_EMAIL = 'gsoc-admins@python.org' # Admins -ADMINS = ['matthew.lagoe@gmail.com', 'sounak.98@gmail.com'] +ADMINS = 'gsoc-admins@python.org' # reCAPTCHA settings # update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually From 7a7f55a069c8cf8e70db842642a641a6b1841c33 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 10:39:01 +0530 Subject: [PATCH 0460/1137] Add default log handler --- gsoc/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 81213d20..9f4f0e48 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -282,7 +282,7 @@ def gettext(s): return s 'filename': os.path.join(BASE_DIR, 'logs/pygsoc.log'), 'formatter': 'verbose', 'when': 'midnight', - 'backupCount': 60, + 'backupCount': 5, 'encoding': 'utf-8', }, 'access_logs': { From 2fce014056f47b2b0794f35fae7c45cf9ebface3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 14:32:35 +0530 Subject: [PATCH 0461/1137] Enhance user invite feature --- gsoc/admin.py | 12 +++- .../admin/adduserlog_change_form.html | 63 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 gsoc/templates/admin/adduserlog_change_form.html diff --git a/gsoc/admin.py b/gsoc/admin.py index 770b6a65..a8e45189 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,6 +1,6 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, - GsocEndDate, Comment, SendEmail, BlogPostHistory) + GsocEndDate, Comment, SendEmail, BlogPostHistory, GsocYear, SubOrg) from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, GsocEndDateForm) @@ -375,6 +375,16 @@ class AddUserLogAdmin(admin.ModelAdmin): list_display = ('log_id', 'used_stat') readonly_fields = ('log_id', ) inlines = (RegLinkInline, ) + change_form_template = 'admin/adduserlog_change_form.html' + + def changeform_view(self, request, object_id, form_url='', extra_context=None): + extra_context = extra_context or {} + extra_context['years'] = GsocYear.objects.all() + extra_context['suborgs'] = SubOrg.objects.all() + extra_context['roles'] = [(0, 'Others'), (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student')] + return super().changeform_view( + request, object_id, form_url, extra_context=extra_context, + ) def used_stat(self, obj): _all = len(RegLink.objects.filter(adduserlog=obj)) diff --git a/gsoc/templates/admin/adduserlog_change_form.html b/gsoc/templates/admin/adduserlog_change_form.html new file mode 100644 index 00000000..dcc40270 --- /dev/null +++ b/gsoc/templates/admin/adduserlog_change_form.html @@ -0,0 +1,63 @@ +{% extends "admin/change_form.html" %} +{% load i18n admin_urls static admin_modify %} +{% load static %} + +{% block submit_buttons_bottom %} +
    +

    Set default role, suborg, gsoc year

    + + + + + + + + + +
    + {% submit_row %} + +{% endblock %} \ No newline at end of file From 4ec9c63ae5775044f06d24b5a7f663e53c3ed2f6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 20:54:02 +0530 Subject: [PATCH 0462/1137] try except for rendering button --- gsoc/cms_toolbars.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index b09a0c0f..a8d4c314 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -243,10 +243,13 @@ def populate(self): except ArticleReview.DoesNotExist: pass - if user.is_superuser: - url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" - f"?article__id__exact={article.id}") - self.toolbar.add_sideframe_item(_('View History'), url=url) + try: + if user.is_superuser: + url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" + f"?article__id__exact={article.id}") + self.toolbar.add_sideframe_item(_('View History'), url=url) + except Article.DoesNotExist: + pass NewsBlogToolbar.populate = populate From 68935fcd0aadffcf0f642b917ea88973ed1f11d4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 31 Jul 2019 20:55:58 +0530 Subject: [PATCH 0463/1137] Fix pep8 warnings --- gsoc/cms_toolbars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index a8d4c314..fb5d237f 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -246,7 +246,7 @@ def populate(self): try: if user.is_superuser: url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" - f"?article__id__exact={article.id}") + f"?article__id__exact={article.id}") self.toolbar.add_sideframe_item(_('View History'), url=url) except Article.DoesNotExist: pass From 793c48ab88c4263cd1412a4fde1ef2eef59e14fc Mon Sep 17 00:00:00 2001 From: root Date: Thu, 1 Aug 2019 19:44:20 -0700 Subject: [PATCH 0464/1137] fix 500 and recaptcha --- gsoc/cms_toolbars.py | 2 +- gsoc/settings.py | 8 -------- gsoc/templates/aldryn_newsblog/includes/comments.html | 4 ++-- settings_local.py.template | 9 --------- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index fb5d237f..9fae7dd7 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -248,7 +248,7 @@ def populate(self): url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" f"?article__id__exact={article.id}") self.toolbar.add_sideframe_item(_('View History'), url=url) - except Article.DoesNotExist: + except Exception as e: pass diff --git a/gsoc/settings.py b/gsoc/settings.py index 9f4f0e48..e7cbe319 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -204,14 +204,6 @@ def gettext(s): return s CMS_PLACEHOLDER_CONF = {} -DATABASE_APPS_MAPPING = { - 'auth': 'auth_db', - 'admin': 'auth_db', - 'sessions': 'auth_db', -} - -DATABASE_ROUTERS = ['gsoc.router.DatabaseAppsRouter'] - MIGRATION_MODULES = { } diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index 52fe9822..46b1324e 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -70,7 +70,7 @@ 1000 characters left -
    +
    - \ No newline at end of file + diff --git a/settings_local.py.template b/settings_local.py.template index f82bd527..1ecbf08b 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -21,15 +21,6 @@ DATABASES = { 'PORT': '', # Set to empty string for default. Not used with sqlite3. 'USER': '' # Not used with sqlite3. }, - # 'auth_db': { - # 'CONN_MAX_AGE': 0, - # 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - # 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - # 'NAME': 'users.db', # Or path to database file if using sqlite3. - # 'PASSWORD': '', # Not used with sqlite3. - # 'PORT': '', # Set to empty string for default. Not used with sqlite3. - # 'USER': '' # Not used with sqlite3. - # }, } # EMAIL CONFIGURATION From 940a3f33d659d431fdb2ed3d635b5b3779c264be Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 5 Aug 2019 13:10:40 +0530 Subject: [PATCH 0465/1137] Add never_cache to new_comment view --- gsoc/views.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index acedd9ab..8a9f5a06 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -23,6 +23,7 @@ from django.urls import reverse from django.conf import settings from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.cache import never_cache from aldryn_newsblog.models import Article @@ -284,6 +285,7 @@ def change_password(request): }) +@never_cache def new_comment(request): if request.method == 'POST': # set environment variable `DISABLE_RECAPTCHA` to disable recaptcha @@ -342,12 +344,12 @@ def new_comment(request): redirect_path = request.POST.get('redirect') - mem = MemcachedStats() - keys = [_[3:] for _ in mem.keys()] - for key in keys: - if 'cache_page' in key or 'cache_header' in key: - print(key, cache.get(key)) - cache.delete(key) + # mem = MemcachedStats() + # keys = [_[3:] for _ in mem.keys()] + # for key in keys: + # if 'cache_page' in key or 'cache_header' in key: + # print(key, cache.get(key)) + # cache.delete(key) if redirect_path: return redirect(redirect_path) From 1e48ac3a1769d7255a049fc7d438e545ab623c34 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 5 Aug 2019 01:05:02 -0700 Subject: [PATCH 0466/1137] fix cache --- gsoc/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gsoc/views.py b/gsoc/views.py index 8a9f5a06..83c09b87 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -344,6 +344,8 @@ def new_comment(request): redirect_path = request.POST.get('redirect') + cache.clear() + # mem = MemcachedStats() # keys = [_[3:] for _ in mem.keys()] # for key in keys: From 4f2f862965c26ac0346543b147512019afdb69b1 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 6 Aug 2019 07:49:08 +0530 Subject: [PATCH 0467/1137] Add sanitizer --- gsoc/models.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/gsoc/models.py b/gsoc/models.py index 9c5cd169..fefc583a 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -121,6 +121,35 @@ def get_root_comments(self): Article.add_to_class('get_root_comments', get_root_comments) +def is_unsafe(self): + unclean_texts = ( + '', + '', + ) + for _ in unclean_texts: + if _ in self.lead_in: + print('Unclean', _) + return True + return False + + +Article.add_to_class('is_unsafe', is_unsafe) + + +def clean_article_html(self): + self.lead_in = re.sub(r'', '</iframe>', self.lead_in) + self.lead_in = re.sub(r'', '</script>', self.lead_in) + + self.save() + + +Article.add_to_class('clean_article_html', clean_article_html) + + # Models class SubOrg(models.Model): @@ -1073,6 +1102,13 @@ def send_comment_notification(sender, instance, **kwargs): instance.send_notifications() +# Clean lead_in HTML when new Article is created +@receiver(models.signals.post_save, sender=Article) +def clean_html(sender, instance, **kwargs): + if instance.is_unsafe(): + instance.clean_article_html() + + # Decrease Blog Counter when new Article is created @receiver(models.signals.pre_save, sender=Article) def decrease_blog_counter(sender, instance, **kwargs): From 1a0422b8e84d4164d963c3869fa902ee88907099 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 6 Aug 2019 11:23:09 +0530 Subject: [PATCH 0468/1137] Submit button enable only on recaptcha verification --- gsoc/static/js/comments.js | 2 +- gsoc/templates/aldryn_newsblog/includes/comments.html | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index 4ff014ff..d44399d3 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -56,4 +56,4 @@ function updateCharCount(formId) { len = textAreaEl.value.length; var remaining = 1000 - len; remainingCharEl.innerHTML = `${remaining} characters left`; -} \ No newline at end of file +} diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index 46b1324e..c0fda9d6 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -70,7 +70,14 @@ 1000 characters left -
    +
    - + + + \ No newline at end of file From 65ec7de27f9a92c497a2ae48065cab302c6422a2 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 6 Aug 2019 11:51:29 +0530 Subject: [PATCH 0469/1137] add django-bleach --- gsoc/models.py | 36 ------------------------------------ gsoc/settings.py | 7 ++++++- requirements.txt | 1 + 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index fefc583a..9c5cd169 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -121,35 +121,6 @@ def get_root_comments(self): Article.add_to_class('get_root_comments', get_root_comments) -def is_unsafe(self): - unclean_texts = ( - '', - '', - ) - for _ in unclean_texts: - if _ in self.lead_in: - print('Unclean', _) - return True - return False - - -Article.add_to_class('is_unsafe', is_unsafe) - - -def clean_article_html(self): - self.lead_in = re.sub(r'', '</iframe>', self.lead_in) - self.lead_in = re.sub(r'', '</script>', self.lead_in) - - self.save() - - -Article.add_to_class('clean_article_html', clean_article_html) - - # Models class SubOrg(models.Model): @@ -1102,13 +1073,6 @@ def send_comment_notification(sender, instance, **kwargs): instance.send_notifications() -# Clean lead_in HTML when new Article is created -@receiver(models.signals.post_save, sender=Article) -def clean_html(sender, instance, **kwargs): - if instance.is_unsafe(): - instance.clean_article_html() - - # Decrease Blog Counter when new Article is created @receiver(models.signals.pre_save, sender=Article) def decrease_blog_counter(sender, instance, **kwargs): diff --git a/gsoc/settings.py b/gsoc/settings.py index e7cbe319..a68d99b6 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -151,7 +151,8 @@ def gettext(s): return s 'gsoc', 'blogs_list', 'suborg', - 'debug_toolbar' + 'debug_toolbar', + 'django_bleach' ) THUMBNAIL_PROCESSORS = ( 'easy_thumbnails.processors.colorspace', @@ -371,3 +372,7 @@ def gettext(s): return s CMS_PAGE_CACHE = False ALDRYN_NEWSBLOG_DEFAULT_PUBLISHED = True + +BLEACH_ALLOWED_TAGS = ['p', 'b', 'i', 'u', 'em', 'strong', 'a', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5'] +BLEACH_STRIP_TAGS = False +BLEACH_DEFAULT_WIDGET = 'djangocms_text_ckeditor.widgets.TextEditorWidget' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3d31a7dd..fd5fee31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ djangocms-video>=2.1.1 djangocms-audio>=1.1.0 djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 +django-bleach>=0.5.3 #gsoc irc requirements fredirc==0.3.0 From 63a8461bab74ac1462f9f0b6a60d233c603dfdf6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 6 Aug 2019 11:53:51 +0530 Subject: [PATCH 0470/1137] fix pep8 warnings --- gsoc/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index a68d99b6..d9ea2906 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -375,4 +375,4 @@ def gettext(s): return s BLEACH_ALLOWED_TAGS = ['p', 'b', 'i', 'u', 'em', 'strong', 'a', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5'] BLEACH_STRIP_TAGS = False -BLEACH_DEFAULT_WIDGET = 'djangocms_text_ckeditor.widgets.TextEditorWidget' \ No newline at end of file +BLEACH_DEFAULT_WIDGET = 'djangocms_text_ckeditor.widgets.TextEditorWidget' From 1cdcf98c9d06afee922c98b1c32e56a7d40ea46b Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 7 Aug 2019 09:32:49 +0530 Subject: [PATCH 0471/1137] Minor update --- gsoc/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index d9ea2906..fc6771ba 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -373,6 +373,9 @@ def gettext(s): return s ALDRYN_NEWSBLOG_DEFAULT_PUBLISHED = True -BLEACH_ALLOWED_TAGS = ['p', 'b', 'i', 'u', 'em', 'strong', 'a', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5'] +BLEACH_ALLOWED_TAGS = ['a', 'address', 'em', 'strong', 'b', 'i', 'big', 'small', 'sub', 'sup', + 'cite', 'code', 'img', 'ul', 'ol', 'li', 'dl', 'lh', 'dt', 'dd', 'br', + 'p', 'table', 'th', 'td', 'tr', 'pre', 'blockquote', 'nowiki', 'h1', 'h2', + 'h3', 'h4', 'h5', 'h6', 'hr', 'iframe'] BLEACH_STRIP_TAGS = False BLEACH_DEFAULT_WIDGET = 'djangocms_text_ckeditor.widgets.TextEditorWidget' From 4e7ef0c5b4c0724c0640e2bd51342d6a885f5e68 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 7 Aug 2019 13:53:31 +0530 Subject: [PATCH 0472/1137] Sanitization --- gsoc/models.py | 41 ++++++++++++++++++++++++++++++++++++++++- gsoc/settings.py | 7 ++----- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 9c5cd169..aae8c100 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -4,6 +4,9 @@ import uuid import json import pickle +import bleach + +from bs4 import BeautifulSoup from googleapiclient.discovery import build @@ -23,7 +26,7 @@ from aldryn_apphooks_config.fields import AppHookConfigField from aldryn_newsblog.cms_appconfig import NewsBlogConfig -from aldryn_newsblog.models import Article +from aldryn_newsblog.models import Article, Person from cms.models import Page, PagePermission from cms import api @@ -121,6 +124,42 @@ def get_root_comments(self): Article.add_to_class('get_root_comments', get_root_comments) +def save(self, *args, **kwargs): + tags = settings.BLEACH_ALLOWED_TAGS + attrs = bleach.sanitizer.ALLOWED_ATTRIBUTES + attrs['iframe'] = ['src', 'frameborder', 'allow', 'allowfullscreen'] + print(attrs) + self.lead_in = bleach.clean(self.lead_in, tags=tags, attributes=attrs) + print(self.lead_in) + soup = BeautifulSoup(self.lead_in, 'html5lib') + for iframe_tag in soup.find_all('iframe'): + _ = iframe_tag.attrs.get('src', None) + if not(_ and "https://www.youtube.com/embed" in _): + print(f"\n\n\n\n\n\n{_}{tags}\n\n\n\n\n\n\n") + iframe_text = str(iframe_tag) + self.lead_in = self.lead_in.replace(iframe_text, bleach.clean(iframe_text)) + + # Update the search index + if self.update_search_on_save: + self.search_data = self.get_search_data() + + # Ensure there is an owner. + if self.app_config.create_authors and self.author is None: + self.author = Person.objects.get_or_create( + user=self.owner, + defaults={ + 'name': ' '.join(( + self.owner.first_name, + self.owner.last_name, + )), + })[0] + # slug would be generated by TranslatedAutoSlugifyMixin + super(Article, self).save(*args, **kwargs) + + +Article.save = save + + # Models class SubOrg(models.Model): diff --git a/gsoc/settings.py b/gsoc/settings.py index fc6771ba..ac3b8c53 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -151,8 +151,7 @@ def gettext(s): return s 'gsoc', 'blogs_list', 'suborg', - 'debug_toolbar', - 'django_bleach' + 'debug_toolbar' ) THUMBNAIL_PROCESSORS = ( 'easy_thumbnails.processors.colorspace', @@ -376,6 +375,4 @@ def gettext(s): return s BLEACH_ALLOWED_TAGS = ['a', 'address', 'em', 'strong', 'b', 'i', 'big', 'small', 'sub', 'sup', 'cite', 'code', 'img', 'ul', 'ol', 'li', 'dl', 'lh', 'dt', 'dd', 'br', 'p', 'table', 'th', 'td', 'tr', 'pre', 'blockquote', 'nowiki', 'h1', 'h2', - 'h3', 'h4', 'h5', 'h6', 'hr', 'iframe'] -BLEACH_STRIP_TAGS = False -BLEACH_DEFAULT_WIDGET = 'djangocms_text_ckeditor.widgets.TextEditorWidget' + 'h3', 'h4', 'h5', 'h6', 'hr', 'iframe', 'div'] From e306845364fa7d11989224abaf6da951903bddf4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 7 Aug 2019 14:40:15 +0530 Subject: [PATCH 0473/1137] Fix sanitizer --- gsoc/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index aae8c100..a15b3e19 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -127,15 +127,12 @@ def get_root_comments(self): def save(self, *args, **kwargs): tags = settings.BLEACH_ALLOWED_TAGS attrs = bleach.sanitizer.ALLOWED_ATTRIBUTES - attrs['iframe'] = ['src', 'frameborder', 'allow', 'allowfullscreen'] - print(attrs) + attrs['iframe'] = ['src', 'frameborder', 'allow', 'allowfullscreen', 'width', 'height'] self.lead_in = bleach.clean(self.lead_in, tags=tags, attributes=attrs) - print(self.lead_in) soup = BeautifulSoup(self.lead_in, 'html5lib') for iframe_tag in soup.find_all('iframe'): _ = iframe_tag.attrs.get('src', None) if not(_ and "https://www.youtube.com/embed" in _): - print(f"\n\n\n\n\n\n{_}{tags}\n\n\n\n\n\n\n") iframe_text = str(iframe_tag) self.lead_in = self.lead_in.replace(iframe_text, bleach.clean(iframe_text)) From e610c9572d43463e0d4bf19c878469d32e6266be Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 7 Aug 2019 03:16:01 -0600 Subject: [PATCH 0474/1137] Update settings.py --- gsoc/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index ac3b8c53..a036d8c5 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -240,7 +240,7 @@ def gettext(s): return s if DEBUG: ERROR_LEVEL = 'INFO' else: - ERROR_LEVEL = 'WARNING' + ERROR_LEVEL = 'ERROR' ERROR_HANDLERS = ['file', 'mail_admins'] From 7b3cb12c96e18222606565fdf81cec1bde9ae633 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 7 Aug 2019 14:50:48 +0530 Subject: [PATCH 0475/1137] Update requirements --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fd5fee31..e849c873 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,6 @@ djangocms-video>=2.1.1 djangocms-audio>=1.1.0 djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 -django-bleach>=0.5.3 #gsoc irc requirements fredirc==0.3.0 @@ -44,3 +43,6 @@ PyGithub>=1.43.7 python-memcached pylibmc>=1.6.0 + +bleach +beautifulsoup4 \ No newline at end of file From a5f0d60d8e12c740d1d90adb0b598753cbc31d2e Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 7 Aug 2019 03:38:14 -0600 Subject: [PATCH 0476/1137] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e849c873..fdf505a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,5 +44,5 @@ PyGithub>=1.43.7 python-memcached pylibmc>=1.6.0 -bleach -beautifulsoup4 \ No newline at end of file +bleach>=3.1.0 +beautifulsoup4>=4.8.0 From 48ec5954539412482e135bf5eed3d01d344100ef Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 7 Aug 2019 16:00:19 +0530 Subject: [PATCH 0477/1137] Add allowed styles --- gsoc/models.py | 5 ++++- gsoc/settings.py | 28 +++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index a15b3e19..c17cb155 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -128,7 +128,10 @@ def save(self, *args, **kwargs): tags = settings.BLEACH_ALLOWED_TAGS attrs = bleach.sanitizer.ALLOWED_ATTRIBUTES attrs['iframe'] = ['src', 'frameborder', 'allow', 'allowfullscreen', 'width', 'height'] - self.lead_in = bleach.clean(self.lead_in, tags=tags, attributes=attrs) + attrs['img'] = ['src', 'alt'] + attrs['*'] = ['class', 'style'] + styles = settings.BLEACH_ALLOWED_STYLES + self.lead_in = bleach.clean(self.lead_in, tags=tags, attributes=attrs, styles=styles) soup = BeautifulSoup(self.lead_in, 'html5lib') for iframe_tag in soup.find_all('iframe'): _ = iframe_tag.attrs.get('src', None) diff --git a/gsoc/settings.py b/gsoc/settings.py index a036d8c5..c372e664 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -375,4 +375,30 @@ def gettext(s): return s BLEACH_ALLOWED_TAGS = ['a', 'address', 'em', 'strong', 'b', 'i', 'big', 'small', 'sub', 'sup', 'cite', 'code', 'img', 'ul', 'ol', 'li', 'dl', 'lh', 'dt', 'dd', 'br', 'p', 'table', 'th', 'td', 'tr', 'pre', 'blockquote', 'nowiki', 'h1', 'h2', - 'h3', 'h4', 'h5', 'h6', 'hr', 'iframe', 'div'] + 'h3', 'h4', 'h5', 'h6', 'hr', 'iframe', 'div', 's', 'u', 'span', 'tbody'] + +BLEACH_ALLOWED_STYLES = ['background', 'background-attachment', 'background-color', + 'background-image', + 'background-position', 'background-repeat', 'border', 'border-bottom', + 'border-bottom-color', 'border-bottom-style', 'border-bottom-width', + 'border-color', 'border-left', 'border-left-color', + 'border-left-style', 'border-left-width', 'border-right', + 'border-right-color', + 'border-right-style', 'border-right-width', 'border-style', 'border-top', + 'border-top-color', + 'border-top-style', 'border-top-width', 'border-width', 'clear', 'clip', + 'color', 'cursor', + 'display', 'filter', 'float', 'font', 'font-family', 'font-size', + 'font-variant', 'font-weight', + 'height', 'left', 'letter-spacing', 'line-height', 'list-style', + 'list-style-image', + 'list-style-position', 'list-style-type', 'margin', 'margin-bottom', + 'margin-left', 'margin-right', + 'margin-top', 'overflow', 'padding', 'padding-bottom', 'padding-left', + 'padding-right', + 'padding-top', 'page-break-after', 'page-break-before', 'position', + 'stroke-dasharray', + 'stroke-dashoffset', 'stroke-width', 'text-align', 'text-decoration', + 'text-indent', + 'text-transform', 'top', 'vertical-align', 'visibility', 'width', + 'z-index'] From 7cc7cbb71d30833ba92747b8cdd0b36743a877b3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 8 Aug 2019 06:41:19 +0530 Subject: [PATCH 0478/1137] Add recaptcha site key variable instead of value --- gsoc/templates/aldryn_newsblog/includes/comments.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index c0fda9d6..b6e12535 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -70,7 +70,7 @@ 1000 characters left -
    +
    From f84693d74748201d6636243c8377e33e690c39b3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 8 Aug 2019 08:10:56 +0530 Subject: [PATCH 0479/1137] Fix comment notification --- gsoc/models.py | 5 ++++- gsoc/templates/email/comment_notification.html | 2 +- gsoc/templates/email/comment_reply_notification.html | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index c17cb155..cb6859d6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -5,6 +5,7 @@ import json import pickle import bleach +from urllib.parse import urljoin from bs4 import BeautifulSoup @@ -866,7 +867,9 @@ def send_notifications(self): 'article': self.article.title, 'created_at': self.created_at.strftime('%I:%M %p, %d %B %Y'), 'username': self.username, - 'link': comment_link, + 'link': urljoin(settings.INETLOCATION, comment_link), + 'article_owner': self.article.owner.username, + 'parent_comment_owner': self.parent.user.username } scheduler_data = build_send_mail_json(self.article.owner.email, template='comment_notification.html', diff --git a/gsoc/templates/email/comment_notification.html b/gsoc/templates/email/comment_notification.html index 21c9b161..49720b42 100644 --- a/gsoc/templates/email/comment_notification.html +++ b/gsoc/templates/email/comment_notification.html @@ -1,4 +1,4 @@ -Hi {{ username }}
    +Hi {{ article_owner }}

    Your blog post {{ article }} has a new comment {{ link }}

    diff --git a/gsoc/templates/email/comment_reply_notification.html b/gsoc/templates/email/comment_reply_notification.html index e0bbe193..efddcbf3 100644 --- a/gsoc/templates/email/comment_reply_notification.html +++ b/gsoc/templates/email/comment_reply_notification.html @@ -1,6 +1,6 @@ -Hi {{ username }}
    +Hi {{ parent_comment_owner }}

    -Your blog comment {{ article }} has a new reply {{ link }}
    +Your blog comment {{ article }} has a new reply {{ link }} by {{ username }}

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From 1497adbdb8ec7d49a8a96f813d03e3201ca188d2 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 8 Aug 2019 16:31:30 +0530 Subject: [PATCH 0480/1137] Add site_key variable --- gsoc/context_processors.py | 2 +- gsoc/templates/aldryn_newsblog/article_detail.html | 2 +- gsoc/templates/aldryn_newsblog/includes/comments.html | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gsoc/context_processors.py b/gsoc/context_processors.py index 13013a74..42df4023 100644 --- a/gsoc/context_processors.py +++ b/gsoc/context_processors.py @@ -3,5 +3,5 @@ def recaptcha_site_key(request): return { - 'RECAPTCHA_SITE_KEY': RECAPTCHA_PUBLIC_KEY, + 'recaptcha_site_key': RECAPTCHA_PUBLIC_KEY, } diff --git a/gsoc/templates/aldryn_newsblog/article_detail.html b/gsoc/templates/aldryn_newsblog/article_detail.html index 9caeb10e..6f09f7bc 100644 --- a/gsoc/templates/aldryn_newsblog/article_detail.html +++ b/gsoc/templates/aldryn_newsblog/article_detail.html @@ -29,6 +29,6 @@
    - {% include "aldryn_newsblog/includes/comments.html" with comments=article.get_root_comments article=article user=user csrf_token=csrf_token only %} + {% include "aldryn_newsblog/includes/comments.html" with comments=article.get_root_comments article=article user=user csrf_token=csrf_token recaptcha_site_key=recaptcha_site_key only %}
    {% endblock %} \ No newline at end of file diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index b6e12535..08b00560 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -37,7 +37,7 @@
  • - {% include "aldryn_newsblog/includes/comments.html" with comments=comment.replies.all parent=comment article=article user=user csrf_token=csrf_token only %} + {% include "aldryn_newsblog/includes/comments.html" with comments=comment.replies.all parent=comment article=article user=user csrf_token=csrf_token recaptcha_site_key=recaptcha_site_key only %}
    {% endfor %} @@ -70,7 +70,7 @@ 1000 characters left -
    +
    From 272eff7d87aa8dcc906d8272334f6bb121577b8b Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 9 Aug 2019 10:38:34 +0530 Subject: [PATCH 0481/1137] Add cookie consent message --- gsoc/settings.py | 3 ++- gsoc/templates/base.html | 3 ++- project.db | Bin 1540096 -> 1544192 bytes requirements.txt | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index c372e664..69fb4a39 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -151,7 +151,8 @@ def gettext(s): return s 'gsoc', 'blogs_list', 'suborg', - 'debug_toolbar' + 'debug_toolbar', + 'django_simple_cookie_consent' ) THUMBNAIL_PROCESSORS = ( 'easy_thumbnails.processors.colorspace', diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index ad7b23d3..f6f4da8f 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -1,4 +1,5 @@ {% load cms_tags menu_tags sekizai_tags %} +{% load django_simple_cookie_consent_tags %} @@ -17,6 +18,7 @@ {% load static %} + {% display_cookie_consent %} {% block head %}{% endblock %} {% render_block "css" %} @@ -106,7 +108,6 @@ {% endfor %} {% endif %}
    - {% block content %}{% endblock %}
    diff --git a/project.db b/project.db index 52e1857fc135ee6748b13c4598f58919c39d4ab6..923e0c741f1513e01435382dea2e3fb853d4a56e 100644 GIT binary patch delta 1548 zcmb7EO>7%Q6yDk0@%m@i_HN@eOOS;t+c z@w&lWVobo|0HL+ea-&=jTq;G2v=5za^sf7k`Yf;LyT)u-hw5yLIev_o z1X{5sr1I5j?NYTU6c(0WTTv48vKlYQ`Gv%l#WH8zeuoWngx|{=)OGeT+{>c;e`O5* z221aYM7wv6kA`D>B+e(fCfY#YEdIv6_D94C*f6N`0~s7P(79vqxo2OOASND9_tP0^ zH=0v5RafOwgda_f@Z0hVW6@|hHkyj^vF5GJfORVqNDdPfc-Ekn0zWyS3D@BwJd0wz zOs*awK^m8?sLD#WEcO%v$Q~tuh)V^fr083vv)5==^zPC;gVb^^ai3e zeXHa^#%fmsd~whP?qi-c_ZjC0)GYla{E93RA#fk1+XhcIp61}>8chG+n^;qK10>)c z6yFNn4g7G2;OK}!-5fmZWC;3mx=Ke7y@OsIZ)70`(vy7znUJ+$N3Nj#Z;<)-7X`Pj zak?>sD8v`~D%K&f72QR%s_8vE(>6~(RXaTP{w|&#nr9jrM04ugUg39i?{J`3=@1N{ zw(!7(tkd5N-n=&Ch8Hkjfu8kPp2P09UB9_xXPj!n3rM&$TwQzo-SgD`%a`%Ds=7Q^ zQu>u{@B8nh@#rT&ZQ?~SJtYRG(#F{LeoyG({q5o5T4_3NgumtvxaX?6Ue*1q~>eJ`X5}tSbCFi5AIsel~iqU=ggd}Y05H+R4P}OjU#RUFxOo|w0iG4 b(+#)FbvuJ?*YDc?A*(sRzc%#oIN|>X(WMn+ delta 414 zcmZoz5ZBNUH$htPAp-+r1p^##{+Xy_%=mC)!V_)A+nZf=ycrpzH&4{nXJQPQd{Ez+ zIhrkhGP{BQ<_LoatdkWQEH~$yP2~iN=1pdI*uGh?VFT;tJhzBQ5p!K5Lj^-qD?=eh=sNrtP{SnkXeiM%yjku zkx-!H3Rv6Egou9i5#Zv?LZZ|?AzPZ#VaKMlaz{9 diff --git a/requirements.txt b/requirements.txt index fdf505a0..9859574c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,3 +46,5 @@ pylibmc>=1.6.0 bleach>=3.1.0 beautifulsoup4>=4.8.0 + +django-simple-cookie-consent>=0.1.1 \ No newline at end of file From f75019364efeb799749508b74390287bc230a4a4 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 9 Aug 2019 11:02:25 +0530 Subject: [PATCH 0482/1137] Remove fields in Suborg Application --- gsoc/admin.py | 17 +++------ gsoc/forms.py | 4 ++- gsoc/migrations/0050_auto_20190809_0529.py | 38 +++++++++++++++++++++ gsoc/models.py | 15 +++++--- project.db | Bin 1544192 -> 1548288 bytes 5 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 gsoc/migrations/0050_auto_20190809_0529.py diff --git a/gsoc/admin.py b/gsoc/admin.py index a8e45189..e9eecc63 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -487,9 +487,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): list_filter = ('gsoc_year', 'changed') # fields = ('last_message', ) readonly_fields = ( - 'gsoc_year', 'reason_for_participation', 'suborg_admin_email', - 'mentors_student_engagement', 'students_on_schedule', 'students_involvement_gsoc', - 'students_involvement_after', 'past_gsoc_experience', 'past_years', + 'gsoc_year', 'suborg_admin_email', 'past_gsoc_experience', 'past_years', 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', @@ -500,16 +498,11 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): ( 'Details', { 'fields': ( - 'gsoc_year', 'reason_for_participation', - 'suborg_admin_email', - 'mentors_student_engagement', 'students_on_schedule', - 'students_involvement_gsoc', - 'students_involvement_after', 'past_gsoc_experience', - 'past_years', + 'gsoc_year', 'suborg_admin_email', + 'past_gsoc_experience', 'past_years', 'suborg_in_past', 'applied_but_not_selected', - 'year_of_start', - 'source_code', 'docs', 'anything_else', 'suborg_name', - 'description', + 'year_of_start', 'source_code', 'docs', 'anything_else', + 'suborg_name', 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', 'blog_url', 'link', 'changed', 'accepted', diff --git a/gsoc/forms.py b/gsoc/forms.py index dc3c9e18..15dcf836 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -63,7 +63,9 @@ class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails exclude = ('accepted', 'last_message', 'changed', 'last_reviewed_at', 'last_reviewed_by', - 'created_at', 'updated_at') + 'created_at', 'updated_at', 'reason_for_participation', + 'mentors_student_engagement', 'students_on_schedule', + 'students_involvement_gsoc', 'students_involvement_after') widgets = { 'suborg_admin_email': forms.HiddenInput(), 'gsoc_year': forms.HiddenInput(), diff --git a/gsoc/migrations/0050_auto_20190809_0529.py b/gsoc/migrations/0050_auto_20190809_0529.py new file mode 100644 index 00000000..057396e4 --- /dev/null +++ b/gsoc/migrations/0050_auto_20190809_0529.py @@ -0,0 +1,38 @@ +# Generated by Django 2.1.10 on 2019-08-09 05:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0049_blogposthistory'), + ] + + operations = [ + migrations.AlterField( + model_name='suborgdetails', + name='mentors_student_engagement', + field=models.TextField(blank=True, null=True, verbose_name='How will you keep mentors engaged with their students?'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='reason_for_participation', + field=models.TextField(blank=True, null=True, verbose_name='Why does your org want to participate in Google Summer of Code?'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='students_involvement_after', + field=models.TextField(blank=True, null=True, verbose_name='How will you keep students involved with your community after GSoC?'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='students_involvement_gsoc', + field=models.TextField(blank=True, null=True, verbose_name='How will you get your students involved in your community during GSoC?'), + ), + migrations.AlterField( + model_name='suborgdetails', + name='students_on_schedule', + field=models.TextField(blank=True, null=True, verbose_name='How will you help your students stay on schedule to complete their projects?'), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index cb6859d6..a451fa04 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -189,23 +189,28 @@ class SubOrgDetails(models.Model): ) reason_for_participation = models.TextField( - verbose_name='Why does your org want to participate in Google Summer of Code?' + verbose_name='Why does your org want to participate in Google Summer of Code?', + null=True, blank=True ) suborg_admin_email = models.EmailField( verbose_name='Suborg admin email' ) mentors_student_engagement = models.TextField( - verbose_name='How will you keep mentors engaged with their students?' + verbose_name='How will you keep mentors engaged with their students?', + null=True, blank=True ) students_on_schedule = models.TextField( verbose_name='How will you help your students stay ' - 'on schedule to complete their projects?' + 'on schedule to complete their projects?', + null=True, blank=True ) students_involvement_gsoc = models.TextField( - verbose_name='How will you get your students involved in your community during GSoC?' + verbose_name='How will you get your students involved in your community during GSoC?', + null=True, blank=True ) students_involvement_after = models.TextField( - verbose_name='How will you keep students involved with your community after GSoC?' + verbose_name='How will you keep students involved with your community after GSoC?', + null=True, blank=True ) past_gsoc_experience = models.BooleanField( verbose_name='Has your org been accepted as a mentor org ' diff --git a/project.db b/project.db index 923e0c741f1513e01435382dea2e3fb853d4a56e..980c7f7f22d1f8c8c83d192be03a369f3b84f93f 100644 GIT binary patch delta 643 zcmWmBO=uHA6bJB`{m5=MNt4*@EE3x;6;VTKH>qut6sn;ty%f?yqaaPvq*=2I)nJ=Q zsT3MK1cHQy76wI7dZ;~C7F_UR?jAgN@*pZAl%mp$7g6+O;5RT|yvKjGpXax``JHg^ z1R=Z$3e^LqFd*J=MtZNS;df+R?Oetx&Ajs)e`~ zOKJ%%SKmmUyIJNYgKda;|xh4LJu{IsXJp z4u2|tkQh7-MN*imRr1;S{YQ4qbmk=qmmsGbAOol0{rjhZK_6E|9*ESPlOA|}Vnci_ zs=_1JFISmQa4onti=A7mio|lBuPEF}D^9bbyyqO|E*Zp5qfAWf@wSi${-if(xvD(n z5MuAc(=`?YE>I?43t2$hl{C##+Xd)g(5T8R4|6<>+?-WIty^FPCNoevj;2M|$ zldaKqQBYzrW>28+R%L~TP127y>AhO0pN^Wck7i7grH3Y2qze`aOT8qA)ta$UqZx}t z=$1vIbodS#a1Jc8#Zmz-aoM@K%TCTvA`GQK$zIg(_a+K`GvpiarTC@WH${Z-$vSwbg8GIeRu?-D&;1|DuU6y$-+7u740HsRa?O#IqH)(NZ*Ze{Z3%_1pf%i43QA$cH#>wSyup!H z+#fRQB{7Pps^zn1suw5A7pC&goL!u$DwCYD@_COf@*i#_BaLK)2Lcd*1Z3a>2wbl?F7@B$y`0e+T@1YYbC_sQ2O8V{u5I=f|jw0>IVl7n&j$swUeI~8EHJo4|RP4`?+Ph67856-l^*`4oP*+2zvzd1C6V zM Date: Fri, 9 Aug 2019 11:04:59 +0530 Subject: [PATCH 0483/1137] Add admin site for GsocYear --- gsoc/admin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gsoc/admin.py b/gsoc/admin.py index e9eecc63..d3762f77 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -563,3 +563,10 @@ def content_safe(self, obj): admin.site.register(BlogPostHistory, BlogPostHistoryAdmin) + + +class GsocYearAdmin(admin.ModelAdmin): + list_display = ('gsoc_year', ) + + +admin.site.register(GsocYear, GsocYearAdmin) From 1c94b9f54ae9da3ac5eac1e2a15ec7df9cf20ac8 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 10 Aug 2019 14:10:07 +0530 Subject: [PATCH 0484/1137] Add username in forgot password change email template --- gsoc/templates/registration/password_reset_email.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/registration/password_reset_email.html b/gsoc/templates/registration/password_reset_email.html index 6cd0a1fa..6849d8d6 100644 --- a/gsoc/templates/registration/password_reset_email.html +++ b/gsoc/templates/registration/password_reset_email.html @@ -1,3 +1,3 @@ -Someone asked for password reset for email {{ email }}. Follow the link below: +Someone asked for password reset for email {{ email }} with username {{ user.username }}. Follow the link below: {{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} From 6db1885f8ec4fcae480aceabd8209bf6f3b99e29 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 10 Aug 2019 14:15:50 +0530 Subject: [PATCH 0485/1137] Replace settings variable from ADMIN_EMAIL to ADMINS --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index a451fa04..cb4f8c21 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -319,7 +319,7 @@ def send_update_notification(self): template_data = { 'suborg_name': suborg_name } - scheduler_data = build_send_mail_json(settings.ADMIN_EMAIL, + scheduler_data = build_send_mail_json(settings.ADMINS, template='suborg_application_notification.html', subject='Review new/updated SubOrg Application', template_data=template_data) From 866781150350625d4b16bc5dedccb59d039278d0 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 14 Aug 2019 15:23:56 +0530 Subject: [PATCH 0486/1137] Add opt in for emails while registering users --- gsoc/models.py | 5 +++-- gsoc/templates/registration/register.html | 1 + gsoc/views.py | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index cb4f8c21..971d73a8 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -756,7 +756,7 @@ def is_usable(self): timenow = timezone.now() return (not self.is_used) and self.created_at < timenow - def create_user(self, *args, is_staff=True, **kwargs): + def create_user(self, *args, is_staff=True, reminder_disabled=False, **kwargs): namespace = str(uuid.uuid4()) email = kwargs.get('email', self.email) user, status = User.objects.get_or_create(*args, is_staff=is_staff, @@ -764,7 +764,8 @@ def create_user(self, *args, is_staff=True, **kwargs): role = {k: v for v, k in UserProfile.ROLES} profile = UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, - suborg_full_name=self.user_suborg) + suborg_full_name=self.user_suborg, + reminder_disabled=reminder_disabled) if self.user_role != role.get('Student', 3): return user diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 37a65739..7d78b056 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -25,6 +25,7 @@

    Sign up for a PSF GSoC account!



    I accept the Python Community Code of Conduct for the duration of the program.
    + Opt me in for notifications via email
    {% endif %} diff --git a/gsoc/views.py b/gsoc/views.py index 83c09b87..2b89d28d 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -222,6 +222,8 @@ def register_view(request): username = request.POST.get('username', '') password = request.POST.get('password', '') password2 = request.POST.get('password2', '') + email_opt_in = request.POST.get('email_opt_in') + reminder_disabled = False if email_opt_in == 'on' else True info_valid = True registration_success = True if password != password2: @@ -247,7 +249,7 @@ def register_view(request): info_valid = False if info_valid: - user = reglink.create_user(username=username) + user = reglink.create_user(username=username, reminder_disabled=reminder_disabled) user.set_password(password) user.save() else: From afa856ad3a5dc274eaee726a553b6987e9b15bbe Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 14 Aug 2019 20:25:38 +0530 Subject: [PATCH 0487/1137] Add readd user functionality --- gsoc/common/utils/build_tasks.py | 134 +++++++++++++-------- gsoc/migrations/0051_auto_20190814_1454.py | 29 +++++ gsoc/models.py | 10 ++ gsoc/templates/email/readd_email.html | 4 + gsoc/templates/readd.html | 15 +++ gsoc/urls.py | 11 +- gsoc/views.py | 24 +++- project.db | Bin 1548288 -> 1552384 bytes 8 files changed, 172 insertions(+), 55 deletions(-) create mode 100644 gsoc/migrations/0051_auto_20190814_1454.py create mode 100644 gsoc/templates/email/readd_email.html create mode 100644 gsoc/templates/readd.html diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 33a44d18..ee32dd76 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -1,38 +1,40 @@ import json +import uuid +import urllib.parse from django.utils import timezone +from django.conf import settings -from gsoc.models import UserProfile, GsocYear, BlogPostDueDate, Scheduler +from gsoc.models import UserProfile, GsocYear, BlogPostDueDate, Scheduler, ReaddUser from gsoc.common.utils.tools import build_send_mail_json def build_pre_blog_reminders(builder): try: data = json.loads(builder.data) - due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) + due_date = BlogPostDueDate.objects.get(pk=data["due_date_pk"]) gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() - categories = ( - (0, 'Weekly Check-In'), - (1, 'Blog Post'), - ) + categories = ((0, "Weekly Check-In"), (1, "Blog Post")) category = categories[due_date.category][1] for profile in profiles: - if profile.current_blog_count is not 0 and not (profile.hidden or - profile.reminder_disabled): + if profile.current_blog_count is not 0 and not ( + profile.hidden or profile.reminder_disabled + ): template_data = { - 'current_blog_count': profile.current_blog_count, - 'type': due_date.category, - 'due_date': due_date.date.strftime('%d %B %Y') - } + "current_blog_count": profile.current_blog_count, + "type": due_date.category, + "due_date": due_date.date.strftime("%d %B %Y"), + } - scheduler_data = build_send_mail_json(profile.user.email, - template='pre_blog_reminder.html', - subject=f'Reminder for {category}', - template_data=template_data) + scheduler_data = build_send_mail_json( + profile.user.email, + template="pre_blog_reminder.html", + subject=f"Reminder for {category}", + template_data=template_data, + ) - s = Scheduler.objects.create(command='send_email', - data=scheduler_data) + s = Scheduler.objects.create(command="send_email", data=scheduler_data) return None except Exception as e: return str(e) @@ -42,71 +44,73 @@ def build_post_blog_reminders(builder): try: data = json.loads(builder.data) last_due_date = BlogPostDueDate.objects.last() - due_date = BlogPostDueDate.objects.get(pk=data['due_date_pk']) + due_date = BlogPostDueDate.objects.get(pk=data["due_date_pk"]) if due_date == last_due_date: blogs_count = 0 else: blogs_count = 1 - categories = ( - (0, 'Weekly Check-In'), - (1, 'Blog Post'), - ) + categories = ((0, "Weekly Check-In"), (1, "Blog Post")) category = categories[due_date.category][1] gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() for profile in profiles: - if profile.current_blog_count > blogs_count and not (profile.hidden or - profile.reminder_disabled): + if profile.current_blog_count > blogs_count and not ( + profile.hidden or profile.reminder_disabled + ): suborg = profile.suborg_full_name mentors = UserProfile.objects.filter(suborg_full_name=suborg, role=2) - suborg_admins = UserProfile.objects.filter(suborg_full_name=suborg, role=1) + suborg_admins = UserProfile.objects.filter( + suborg_full_name=suborg, role=1 + ) activation_date = builder.activation_date.date() if activation_date - due_date.date == timezone.timedelta(days=1): - student_template = 'first_post_blog_reminder_student.html' + student_template = "first_post_blog_reminder_student.html" elif activation_date - due_date.date == timezone.timedelta(days=3): - student_template = 'second_post_blog_reminder_student.html' + student_template = "second_post_blog_reminder_student.html" - mentors_emails = ['gsoc-admins@python.org'] + mentors_emails = ["gsoc-admins@python.org"] mentors_emails.extend([_.user.email for _ in mentors]) mentors_emails.extend([_.user.email for _ in suborg_admins]) mentors_template_data = { - 'student_username': profile.user.username, - 'student_email': profile.user.email, - 'suborg_name': profile.suborg_full_name.suborg_name, - 'due_date': due_date.date.strftime('%d %B %Y'), - 'current_blog_count': profile.current_blog_count - } + "student_username": profile.user.username, + "student_email": profile.user.email, + "suborg_name": profile.suborg_full_name.suborg_name, + "due_date": due_date.date.strftime("%d %B %Y"), + "current_blog_count": profile.current_blog_count, + } scheduler_data_mentors = build_send_mail_json( mentors_emails, - template='post_blog_reminder_mentors.html', - subject=f'{category} missed by a Student of your Sub-Org', - template_data=mentors_template_data - ) + template="post_blog_reminder_mentors.html", + subject=f"{category} missed by a Student of your Sub-Org", + template_data=mentors_template_data, + ) - Scheduler.objects.create(command='send_email', - data=scheduler_data_mentors) + Scheduler.objects.create( + command="send_email", data=scheduler_data_mentors + ) student_template_data = { - 'current_blog_count': profile.current_blog_count, - 'due_date': due_date.date.strftime('%d %B %Y') - } + "current_blog_count": profile.current_blog_count, + "due_date": due_date.date.strftime("%d %B %Y"), + } scheduler_data_student = build_send_mail_json( profile.user.email, template=student_template, - subject=f'Reminder for {category}', - template_data=student_template_data - ) + subject=f"Reminder for {category}", + template_data=student_template_data, + ) - Scheduler.objects.create(command='send_email', - data=scheduler_data_student) + Scheduler.objects.create( + command="send_email", data=scheduler_data_student + ) return None except Exception as e: return str(e) @@ -117,7 +121,35 @@ def build_revoke_student_perms(builder): gsoc_year = GsocYear.objects.first() profiles = UserProfile.objects.filter(gsoc_year=gsoc_year, role=3).all() for profile in profiles: - Scheduler.objects.create(command='revoke_student_permissions', - data=profile.user.id) + Scheduler.objects.create( + command="revoke_student_permissions", data=profile.user.id + ) + except Exception as e: + return str(e) + + +def build_remove_user_details(builder): + try: + gsoc_year = GsocYear.objects.first() + profiles = UserProfile.objects.filter( + gsoc_year=gsoc_year, role__in=[1, 2, 3] + ).all() + for profile in profiles: + email = profile.user.email + profile.user.email = None + profile.user.save() + _uuid = uuid.uuid4() + ReaddUser.objects.create(user=profile.user, uuid=_uuid) + template_data = { + # TODO: change this after the view is created + "link": settings.INETLOCATION + "use reverse here" + } + scheduler_data = build_send_mail_json( + email, + template="readd_email.html", + subject="Your personal details have been removed from our database", + template_data=template_data, + ) + Scheduler.objects.create(command='send_email' data=scheduler_data) except Exception as e: return str(e) diff --git a/gsoc/migrations/0051_auto_20190814_1454.py b/gsoc/migrations/0051_auto_20190814_1454.py new file mode 100644 index 00000000..a94373a4 --- /dev/null +++ b/gsoc/migrations/0051_auto_20190814_1454.py @@ -0,0 +1,29 @@ +# Generated by Django 2.1.10 on 2019-08-14 14:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('gsoc', '0050_auto_20190809_0529'), + ] + + operations = [ + migrations.CreateModel( + name='ReaddUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.CharField(max_length=100)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AlterField( + model_name='builder', + name='category', + field=models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), ('build_remove_user_details', 'build_remove_user_details')], max_length=40), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 971d73a8..dcdd6091 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -350,6 +350,15 @@ def send_review(self): data=scheduler_data) +class ReaddUser(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + uuid = models.CharField(max_length=100) + + def readd_user_details(self, email): + self.user.email = email + self.user.save() + + class UserProfileManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(hidden=False) @@ -428,6 +437,7 @@ class Builder(models.Model): ('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), + ('build_remove_user_details', 'build_remove_user_details'), ) category = models.CharField(max_length=40, choices=categories) diff --git a/gsoc/templates/email/readd_email.html b/gsoc/templates/email/readd_email.html new file mode 100644 index 00000000..8208958f --- /dev/null +++ b/gsoc/templates/email/readd_email.html @@ -0,0 +1,4 @@ +Your sensitive information has been removed from our database.
    +
    +If you want to be subscribed for further notifications via email you +can subscribe using this link. \ No newline at end of file diff --git a/gsoc/templates/readd.html b/gsoc/templates/readd.html new file mode 100644 index 00000000..5e7ff311 --- /dev/null +++ b/gsoc/templates/readd.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %} +Page Not Found +{% endblock %} + +{% block content %} +
    + {% if success %} +

    You are subscribed

    + {% else %} +

    Subscribing failed

    + {% endif %} +
    +{% endblock %} \ No newline at end of file diff --git a/gsoc/urls.py b/gsoc/urls.py index 5e633740..3831c5ab 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -22,10 +22,10 @@ url(r'^sitemap.xml', sitemap, { 'sitemaps': { 'blog_sitemaps': sitemaps.BlogListSitemap, - } - }), + } + }), url(r'^robots.txt', TemplateView.as_view(template_name="robots.txt", - content_type="text/plain"), name="robots_file"), + content_type="text/plain"), name="robots_file"), url(r'^favicon.ico', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), ] @@ -85,3 +85,8 @@ urlpatterns += [ url(r'^upload/', gsoc.views.upload_file) ] + +# Readd user details +urlpatterns += [ + url(r'^readd/(?P[\w-]+)/', gsoc.views.readd_users, name='readd_users') +] diff --git a/gsoc/views.py b/gsoc/views.py index 2b89d28d..b6ccb3ae 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -3,7 +3,7 @@ from .common.utils.memcached_stats import MemcachedStats from .forms import ProposalUploadForm from .models import (RegLink, ProposalTextValidator, Comment, ArticleReview, - GsocYear) + GsocYear, ReaddUser) import io import os @@ -416,3 +416,25 @@ def publish_article(request, article_id): else: messages.error(request, 'User does not have permission to publish article') return redirect(reverse('{}:article-detail'.format(a.app_config.namespace), args=[a.slug])) + + +def readd_users(request, uuid): + if request.method == 'GET': + readds = ReaddUser.objects.filter(uuid=uuid) + email = request.GET.get('email') + context = { + "success": False + } + if len(readds) > 0: + readd = readds.first() + if email: + readd.readd_user_details(email) + context = { + "success": True + } + else: + messages.error('Please provide your email') + else: + messages.error('Incorrect token, please use the correct token') + + return shortcuts.render(request, "readd.html", context) \ No newline at end of file diff --git a/project.db b/project.db index 980c7f7f22d1f8c8c83d192be03a369f3b84f93f..c72985726284129b6850f5a49100798052f65f74 100644 GIT binary patch delta 1195 zcmZ{jUue@%6vlINZR8V|y4~poMf;guSB16HOR?4av_;GVjzH`oZa+6C- zgO}b7-e_{K5Ck|+)GoU?yK^^KSxn8hfVna)jyu^4>`Ue&dzvwrIcCmqk2NuEuF@p* zGs6?Pp=73*9Z|JBUn)Ym8ks0&a`|CZE2Og{6UqUkv6|EFI}KEc@Wh@(ClC?q_>*&R;TlZXz8mz5MUtU88rgo~ zoqH^Tnr#q}3P@C!f$GvgA}9F$GmZpVsQI;w6C)?ND=(rZp*tQh2>hQpnb)E0`iN2AfJu|BUE>*F8TeNHe^;Ke_*H()l4C@f+auT0FOX`R3@|9 z*<|e?KtezuNGhr}q7}4t@quc5=yZB?czwLzi$E)>;B#Cjj+=*yy!m9l3yODBjVJ{h zxhp!o)aYj%cJ2*#hr5Cu9OEvwP9-P;1a|*rq9;8nTDHWBEz$Zwc1#UmWw7HPW0tmR zwoct&RsU_*QdeV^xAgf9m=IrOBC7`oj$G z2j}(ig113m%kcNxO1T-076JkCvQS88Mzs0@^*f+d64{yw9lUCYz+zU_AH1dnU4BY! S(O0j-edgCQf~(znQNICW`FIZi delta 551 zcmZ9}KWGzC90&0C?!CKrcTMk-Yc8imX_rt939S_q$CP?Pk!lemsm7Y7J!&A5|0+VE z(V^%d4JY2HI20jMIq>07tl%VqrAsEU4q_<^!Lc~_1DzZ`Z~1=t{eJLP_vTl(=hvg5 z1BCE>;2?MqQlp*3X?(C**cisX%SLLN{99R3cIAELmh8*EAF9UXVfhO(+bwcRUT#zt zZ#G)RN_~0BX)ct?4W)>VcP5Xz`zOMN-gZdrZ>QKQm6x2luboU0{Mok?7ckEIo#dE+ zbMCvbDUz4Yx@`Qp|7v_ibVsVDn4)$XdtV*vm_L{5Do{wfEb|5qxGPF$@)X(Wf9d(| zBa&l=&vrHih_BIyMsLmNVITkkC=h`J7?6PiR4@PnU=V0P2SE@51~+;`zwVKQXnSln zJO|^y(KS08=62S6tB&eu9qD!Lr&bKSAE>C8m2+%I^2JZ|kQ@>&Y(cE-;iMa?I{fj1 znG|UI&J*gfvK7O1uht9?d6}}c#rdK+6Ce$7+GADoJ8Zd5`E<+VA3C$Dii_|ySHUG7 zdO?2ZFZ;pQg$OMWBQW8y?MMvzQb5i;$cYF~&UhDFW|U_?M!HiQ84~>8vw}s#B8nsN h28!!fGCO=ZJYD|zyFwT*9|F#vJ_yadwtf&A0 From acc30325a9e37c836c6acb1c19b402be4fb3328c Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 15 Aug 2019 16:19:28 +0530 Subject: [PATCH 0488/1137] Add github handle while registering --- gsoc/admin.py | 4 ++-- .../0052_userprofile_github_handle.py | 18 +++++++++++++++ gsoc/models.py | 21 +++++++++++++----- gsoc/templates/registration/register.html | 2 ++ gsoc/views.py | 4 +++- project.db | Bin 1552384 -> 1556480 bytes 6 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 gsoc/migrations/0052_userprofile_github_handle.py diff --git a/gsoc/admin.py b/gsoc/admin.py index d3762f77..347ea226 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -306,14 +306,14 @@ class HiddenUserProfileAdmin(admin.ModelAdmin): 'hidden', 'reminder_disabled', 'current_blog_count') list_filter = ('hidden', 'reminder_disabled') readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'blog_link', - 'proposal_confirmed', 'current_blog_count') + 'proposal_confirmed', 'current_blog_count', 'github_handle') fieldsets = ( ('Unhide', { 'fields': ('hidden', 'reminder_disabled') }), ('User Profile Details', { 'fields': ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'proposal_confirmed', - 'blog_link', 'current_blog_count') + 'blog_link', 'current_blog_count', 'github_handle') }) ) diff --git a/gsoc/migrations/0052_userprofile_github_handle.py b/gsoc/migrations/0052_userprofile_github_handle.py new file mode 100644 index 00000000..a355e43f --- /dev/null +++ b/gsoc/migrations/0052_userprofile_github_handle.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.10 on 2019-08-15 10:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0051_auto_20190814_1454'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='github_handle', + field=models.TextField(blank=True, max_length=100, null=True), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index dcdd6091..71dc813b 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -152,8 +152,8 @@ def save(self, *args, **kwargs): 'name': ' '.join(( self.owner.first_name, self.owner.last_name, - )), - })[0] + )), + })[0] # slug would be generated by TranslatedAutoSlugifyMixin super(Article, self).save(*args, **kwargs) @@ -384,6 +384,7 @@ class UserProfile(models.Model): hidden = models.BooleanField(name='hidden', default=False) reminder_disabled = models.BooleanField(default=False) current_blog_count = models.IntegerField(default=0) + github_handle = models.TextField(null=True, blank=True, max_length=100) objects = UserProfileManager() all_objects = models.Manager() @@ -766,16 +767,24 @@ def is_usable(self): timenow = timezone.now() return (not self.is_used) and self.created_at < timenow - def create_user(self, *args, is_staff=True, reminder_disabled=False, **kwargs): + def create_user(self, *args, is_staff=True, reminder_disabled=False, + github_handle=None, **kwargs): namespace = str(uuid.uuid4()) email = kwargs.get('email', self.email) user, status = User.objects.get_or_create(*args, is_staff=is_staff, email=email, **kwargs) + if not status: + profiles = user.userprofile_set.all() + for _ in profiles: + github_handle = profile.github_handle + role = {k: v for v, k in UserProfile.ROLES} profile = UserProfile.objects.create(user=user, role=self.user_role, gsoc_year=self.user_gsoc_year, suborg_full_name=self.user_suborg, - reminder_disabled=reminder_disabled) + reminder_disabled=reminder_disabled, + github_handle=github_handle + ) if self.user_role != role.get('Student', 3): return user @@ -928,7 +937,7 @@ class SendEmail(models.Model): ('suborg_admins', 'Suborg Admins'), ('admins', 'Admins'), ('all', 'All') - ) + ) to = models.CharField(null=True, blank=True, max_length=255, help_text='Separate email with a comma') @@ -967,7 +976,7 @@ def save(self, *args, **kwargs): subject=self.subject, template_data={ 'body': self.body - }) + }) self.scheduler = Scheduler.objects.create(command='send_email', data=scheduler_data, activation_date=self.activation_date) diff --git a/gsoc/templates/registration/register.html b/gsoc/templates/registration/register.html index 7d78b056..6af1aefb 100644 --- a/gsoc/templates/registration/register.html +++ b/gsoc/templates/registration/register.html @@ -24,6 +24,8 @@

    Sign up for a PSF GSoC account!




    + +
    I accept the Python Community Code of Conduct for the duration of the program.
    Opt me in for notifications via email
    diff --git a/gsoc/views.py b/gsoc/views.py index b6ccb3ae..93d0cfa5 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -222,6 +222,7 @@ def register_view(request): username = request.POST.get('username', '') password = request.POST.get('password', '') password2 = request.POST.get('password2', '') + github_handle = request.POST.get('github_handle', '') email_opt_in = request.POST.get('email_opt_in') reminder_disabled = False if email_opt_in == 'on' else True info_valid = True @@ -249,7 +250,8 @@ def register_view(request): info_valid = False if info_valid: - user = reglink.create_user(username=username, reminder_disabled=reminder_disabled) + user = reglink.create_user(username=username, reminder_disabled=reminder_disabled, + github_handle=github_handle) user.set_password(password) user.save() else: diff --git a/project.db b/project.db index c72985726284129b6850f5a49100798052f65f74..efc5104bc46578537ed42a27f57f8eaad0082401 100644 GIT binary patch delta 955 zcmY+BZD>4I}s#=l;yc#B`#cx5q&`;D6#Ao$RB4-$XiF7A+H}TnHw*{{00&Tj2LwO_BtQlfKm|0w0XP92Z~<<> z!|HXLS>Wwb={4{b`2 z;ia~_-&k!wJ7{?9tpi38`{s~wkx<7Nmj$~SjvfEmo9^RQ(rH`VZR{a*8sr$=92tu8 z$lMm0Hy#dGRlILLy0^W%t*0s4*0`@F8LtSJ*M`D1q4FxLJiKOA^_t3>mEqco>Y7zd zsyF^F4r$Mj6W1d3hH^$Om41~h;i<5PkJ4|+1HH@n+}Q+|h&+I;A#w$NS23GTnrARu zpE68^!lHUX?ZT{SpE-dUO&hbja=R5L*5S$lRU^P@=DtH_^ofG&`bLgg=O}*5Bu&rh zqBe~boI$oLW(Ek(d>_%CF=t!YyagF-5*7 zRQpFOR%X#S6b`b14qqEg@zVT7BFM@`0<59k_W@?bv*z#3WiX10Rv^7a`$T2xBIUIF zQf`#0#FKmj$nOPP5%V{QEYsrqiL!X3PvPu8629^CE+q^m&Tr&g*Khjpm!bKF{0pRi zrMDGo5$`Lg76UO&-I5e!4LX!4{;z2;lL6E;y9gfZrBw_ J@;i-y_6DqD4~hT) delta 842 zcmYk)ZHP=!7y#gN&OP`0&b>3{-nq=EtsRqlU;c(@+1$g5(XQP!R|w(Oow03OcI|9Q z#1pZ|_VyMs_@iPu!7sY&s#0}T7!V);3Pd0Q87M#n8qk3OJir7N@B$wg#p|m4*UwDw z^deatKzc;GqRmh*sAbAQap3;}t=3`c=@t56)+fLWoz3~;r}abinLV-_3Xya@%SGME zcs0xKu>Jf&)a@a-f**Y9Pwo~%qQc|`dlM|~r(9WYe+y25%k-ieWVM-hP1Dn=U(?pY zxuSGbS|O(BD&ZEnL#k0twl=_@By2}B4m9_aDsI=3Hqt$jt@-8z*~?D5nmrlzkLIg~ z{pX3^HL(ZD?xqmwTFLSeH^96R_b|!qXD1>qCz*ZhUbTNQJKpMsNoFsLP4zEeGo$Xh z{-oW|d(oAo=g1ndoNNvOji}OEXXZQw Date: Thu, 15 Aug 2019 17:11:31 +0530 Subject: [PATCH 0489/1137] Add more suborg admins --- gsoc/admin.py | 3 ++- gsoc/migrations/0053_auto_20190815_1130.py | 23 ++++++++++++++++ gsoc/models.py | 29 ++++++++++++++++++++- project.db | Bin 1556480 -> 1560576 bytes 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 gsoc/migrations/0053_auto_20190815_1130.py diff --git a/gsoc/admin.py b/gsoc/admin.py index 347ea226..4020debc 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -487,7 +487,8 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): list_filter = ('gsoc_year', 'changed') # fields = ('last_message', ) readonly_fields = ( - 'gsoc_year', 'suborg_admin_email', 'past_gsoc_experience', 'past_years', + 'gsoc_year', 'suborg_admin_email', 'suborg_admin_2_email', 'suborg_admin_3_email', + 'past_gsoc_experience', 'past_years', 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', diff --git a/gsoc/migrations/0053_auto_20190815_1130.py b/gsoc/migrations/0053_auto_20190815_1130.py new file mode 100644 index 00000000..60c49be2 --- /dev/null +++ b/gsoc/migrations/0053_auto_20190815_1130.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.10 on 2019-08-15 11:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0052_userprofile_github_handle'), + ] + + operations = [ + migrations.AddField( + model_name='suborgdetails', + name='suborg_admin_2_email', + field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Suborg admin 2 email'), + ), + migrations.AddField( + model_name='suborgdetails', + name='suborg_admin_3_email', + field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Suborg admin 3 email'), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 71dc813b..bb7c6603 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -190,11 +190,24 @@ class SubOrgDetails(models.Model): reason_for_participation = models.TextField( verbose_name='Why does your org want to participate in Google Summer of Code?', - null=True, blank=True + null=True, blank=True, ) suborg_admin_email = models.EmailField( verbose_name='Suborg admin email' ) + + suborg_admin_2_email = models.EmailField( + verbose_name='Suborg admin 2 email', + blank=True, null=True, + help_text='Fill this if there are other suborg admins other than you' + ) + + suborg_admin_3_email = models.EmailField( + verbose_name='Suborg admin 3 email', + blank=True, null=True, + help_text='Fill this if there are other suborg admins other than you' + ) + mentors_student_engagement = models.TextField( verbose_name='How will you keep mentors engaged with their students?', null=True, blank=True @@ -301,6 +314,20 @@ def accept(self): email=self.suborg_admin_email, send_notifications=False) + if self.suborg_admin_2_email: + RegLink.objects.create(user_role=1, + user_suborg=self.suborg, + user_gsoc_year=self.gsoc_year, + email=self.suborg_admin_2_email, + send_notifications=False) + + if self.suborg_admin_3_email: + RegLink.objects.create(user_role=1, + user_suborg=self.suborg, + user_gsoc_year=self.gsoc_year, + email=self.suborg_admin_3_email, + send_notifications=False) + s = Scheduler.objects.filter(command='update_site_template', data=json.dumps({'template': 'index.html'}), success=None).all() diff --git a/project.db b/project.db index efc5104bc46578537ed42a27f57f8eaad0082401..0127707b1f81e133c9409ce827a56f1e8080eaeb 100644 GIT binary patch delta 834 zcmZ{iZ%7ky7{`Bi_dD;lo4al9cKva5TEdK}yKQG`78z-PMhBQz3D_g3_^Oi!g%uSf3m zbjXS-%RWz~Y4LQl$ciM(UB1pPztY;S_&WVw{p}@t_tmws^t^|{(j!dl@dO8jRf;3A z#*~F+Z2U$f;9F5b$J@fL(+=0V{}4lPg%^PV-MUKsG#S)g3!Y;$HScl;|9X?5Hh@nMVzOH4qp5kJEXRKW^-*E z1xzov9WI0d;OORVQ^m`SD9hw>o34yFC_)Rw91h(baoF(S-g4DQbTXMzy=rU{mfR@K zM6zRvNY>?5{LRw9V1K_`YOMYS`%2$`mCca!79?Gux%{!>6eEUYGT*BOlfofwI3?P& zrj%Hx{YZ(8mA0GBG2%EVl2rwh7VEXo6~+BDD4-3pSw;+l;zCt1n-*QffR=w}tJ6|3 T!K}eR(7vR_llrfixI+E~;*98s delta 580 zcmYk%O=uHA7zW_ko!RVWcazjKtskR-<^L=_`ibuGPH$%hAzz)Pgi$Pr1z4-IlNTTPBKwb~i?M4I9-Nd_c*UrZUNNxiFc{O$V!G9Ql`&%IT@; zd^UG8RhTR(TgZh_t9z(~g>Uzw;aW5-01;q7fCOAX1`2Qk6=*;Q9^eHAFo6&FK_8EX z1IzpI5LsbzERcW|ZNgXLfzx*yG1V&=P{spoAA&jVTNA$z{y5%Yw(>wtk=X;ha(yaAtS3_)wnjO&bc?-Ddo6alM)dB zcOD$6r(_z5h>v7`=>x?Ad)B5pzuTspn2ojR9)3O7zqolAo@mlKGUDE*r>FnYu{NXJ zmw(6?T~DNS`1UL~1x_L)e}ZCdiRV}8g2=Dr=!8V7d+S+t&7tRnG^>8}_c6Oez3jF_ z{p_l Date: Fri, 16 Aug 2019 10:06:56 +0530 Subject: [PATCH 0490/1137] Lint code --- blogs_list/apps.py | 2 +- blogs_list/feeds.py | 18 +- blogs_list/urls.py | 4 +- blogs_list/views.py | 52 +- gsoc/admin.py | 473 +++++--- gsoc/cms_toolbars.py | 198 ++-- gsoc/cms_wizards.py | 26 +- gsoc/common/utils/build_tasks.py | 5 +- gsoc/common/utils/commands.py | 100 +- gsoc/common/utils/irc.py | 21 +- gsoc/common/utils/memcached_stats.py | 36 +- gsoc/common/utils/tools.py | 68 +- gsoc/context_processors.py | 4 +- gsoc/forms.py | 121 +- gsoc/management/commands/googleapiauth.py | 10 +- gsoc/management/commands/runcron.py | 183 +-- gsoc/migrations/0001_initial.py | 59 +- gsoc/migrations/0002_scheduler.py | 27 +- gsoc/migrations/0003_auto_20190219_0320.py | 20 +- gsoc/migrations/0004_auto_20190219_1215.py | 12 +- gsoc/migrations/0005_auto_20190219_1253.py | 10 +- gsoc/migrations/0006_auto_20190219_1301.py | 10 +- gsoc/migrations/0007_auto_20190305_0853.py | 54 +- gsoc/migrations/0008_auto_20190322_0318.py | 51 +- gsoc/migrations/0009_reglink.py | 29 +- gsoc/migrations/0010_auto_20190324_2216.py | 37 +- .../migrations/0011_userprofile_app_config.py | 19 +- gsoc/migrations/0012_auto_20190418_2214.py | 12 +- gsoc/migrations/0013_pagenotification.py | 46 +- gsoc/migrations/0014_auto_20190420_0140.py | 62 +- gsoc/migrations/0015_auto_20190423_1031.py | 12 +- gsoc/migrations/0016_comment.py | 56 +- .../0017_userprofile_proposal_confirmed.py | 10 +- gsoc/migrations/0018_auto_20190526_1519.py | 45 +- gsoc/migrations/0019_auto_20190529_1656.py | 112 +- gsoc/migrations/0020_auto_20190529_1759.py | 17 +- gsoc/migrations/0021_auto_20190530_0622.py | 54 +- gsoc/migrations/0022_articlereview.py | 39 +- gsoc/migrations/0023_auto_20190604_1310.py | 10 +- gsoc/migrations/0024_auto_20190607_0428.py | 35 +- gsoc/migrations/0025_auto_20190607_1004.py | 24 +- gsoc/migrations/0026_auto_20190608_0852.py | 36 +- gsoc/migrations/0027_auto_20190608_1421.py | 10 +- gsoc/migrations/0028_blogpostduedate_title.py | 12 +- gsoc/migrations/0029_suborgdetails.py | 165 ++- gsoc/migrations/0030_auto_20190614_0559.py | 30 +- gsoc/migrations/0031_auto_20190614_0630.py | 10 +- gsoc/migrations/0032_auto_20190621_0347.py | 37 +- gsoc/migrations/0033_auto_20190623_0751.py | 12 +- gsoc/migrations/0034_auto_20190623_1123.py | 12 +- gsoc/migrations/0035_auto_20190626_0437.py | 40 +- gsoc/migrations/0036_auto_20190628_0502.py | 12 +- gsoc/migrations/0037_auto_20190629_1627.py | 22 +- gsoc/migrations/0038_auto_20190702_0326.py | 22 +- gsoc/migrations/0039_gsocenddate.py | 27 +- gsoc/migrations/0040_auto_20190703_0617.py | 19 +- gsoc/migrations/0041_auto_20190707_0432.py | 16 +- gsoc/migrations/0042_auto_20190707_0505.py | 12 +- .../0043_blogpostduedate_category.py | 16 +- gsoc/migrations/0044_auto_20190716_0700.py | 23 +- gsoc/migrations/0045_auto_20190719_1851.py | 32 +- gsoc/migrations/0046_sendemail.py | 58 +- .../0047_sendemail_activation_date.py | 10 +- .../0048_reglink_send_notifications.py | 10 +- gsoc/migrations/0049_blogposthistory.py | 30 +- gsoc/migrations/0050_auto_20190809_0529.py | 54 +- gsoc/migrations/0051_auto_20190814_1454.py | 38 +- .../0052_userprofile_github_handle.py | 10 +- gsoc/migrations/0053_auto_20190815_1130.py | 26 +- gsoc/models.py | 1023 ++++++++++------- gsoc/router.py | 2 +- gsoc/settings.py | 650 ++++++----- gsoc/sitemaps.py | 10 +- gsoc/templatetags/app_tag.py | 6 +- gsoc/urls.py | 105 +- gsoc/views.py | 295 ++--- manage.py | 6 +- settings_local.py.template | 65 +- suborg/apps.py | 2 +- suborg/urls.py | 50 +- suborg/views.py | 83 +- 81 files changed, 3170 insertions(+), 2111 deletions(-) diff --git a/blogs_list/apps.py b/blogs_list/apps.py index 1592ec00..668a19d4 100644 --- a/blogs_list/apps.py +++ b/blogs_list/apps.py @@ -2,4 +2,4 @@ class BlogsListConfig(AppConfig): - name = 'blogs_list' + name = "blogs_list" diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py index 42b13c14..1a6016f2 100644 --- a/blogs_list/feeds.py +++ b/blogs_list/feeds.py @@ -21,7 +21,7 @@ def remove_control_characters(s): def get_request(language=None): request_factory = RequestFactory() - request = request_factory.get('/') + request = request_factory.get("/") request.session = {} request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE @@ -32,15 +32,15 @@ def get_request(language=None): class CorrectMimeTypeFeed(DefaultFeed): - content_type = 'application/xml; charset=utf-8' + content_type = "application/xml; charset=utf-8" class BlogsFeed(Feed): title = "GSoC@PSF Blogs" - link = '/blogs/' - feed_url = '/blogs/feed/' + link = "/blogs/" + feed_url = "/blogs/feed/" feed_type = CorrectMimeTypeFeed - description = 'Updates on different student blogs of GSoC@PSF' + description = "Updates on different student blogs of GSoC@PSF" def items(self): gsoc_year = GsocYear.objects.first() @@ -73,13 +73,15 @@ def item_pubdate(self, item): def item_guid(self, item): site = Site.objects.first() - return 'http://{}{}'.format(site.domain, self.item_link(item)) + return "http://{}{}".format(site.domain, self.item_link(item)) def item_guid_is_permalink(self, item): return True def item_link(self, item): section = item.app_config - p = Page.objects.get(application_namespace=section.namespace, publisher_is_draft=False) - url = '{}{}/'.format(p.get_absolute_url(), item.slug, '/') + p = Page.objects.get( + application_namespace=section.namespace, publisher_is_draft=False + ) + url = "{}{}/".format(p.get_absolute_url(), item.slug, "/") return url diff --git a/blogs_list/urls.py b/blogs_list/urls.py index b6ade03f..0ead8f7b 100644 --- a/blogs_list/urls.py +++ b/blogs_list/urls.py @@ -4,6 +4,6 @@ from .feeds import BlogsFeed urlpatterns = [ - url('^$', list_blogs, name='list_blogs'), - url('feed/', BlogsFeed(), name='feed') + url("^$", list_blogs, name="list_blogs"), + url("feed/", BlogsFeed(), name="feed"), ] diff --git a/blogs_list/views.py b/blogs_list/views.py index afe81e60..b2ae2a95 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -4,17 +4,14 @@ from django.shortcuts import render from django.contrib import messages -from gsoc.models import ( - GsocYear, - UserProfile -) +from gsoc.models import GsocYear, UserProfile from gsoc.settings import MEDIA_URL from cms.models import Page def list_blogs(request): - gsoc_years = GsocYear.objects.all().order_by('-gsoc_year') + gsoc_years = GsocYear.objects.all().order_by("-gsoc_year") blogsets = [] for year in gsoc_years: @@ -26,29 +23,38 @@ def list_blogs(request): if profile.app_config: flag = True ns = profile.app_config.namespace - page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) + page = Page.objects.get( + application_namespace=ns, publisher_is_draft=False + ) student_name = profile.user.get_full_name() student_username = profile.user.username - proposal_name = profile.accepted_proposal_pdf.name if profile.proposal_confirmed else None - proposal_path = os.path.join(MEDIA_URL, proposal_name) if proposal_name else None - - blogset.append({ - 'title': profile.app_config.app_title, - 'url': page.get_absolute_url(), - 'student': student_name if student_name else student_username, - 'suborg': profile.suborg_full_name.suborg_name, - 'color': random.choice(['umber', 'khaki', 'wine', 'straw']), - 'proposal': proposal_path if proposal_name else None, - }) + proposal_name = ( + profile.accepted_proposal_pdf.name + if profile.proposal_confirmed + else None + ) + proposal_path = ( + os.path.join(MEDIA_URL, proposal_name) if proposal_name else None + ) + + blogset.append( + { + "title": profile.app_config.app_title, + "url": page.get_absolute_url(), + "student": student_name if student_name else student_username, + "suborg": profile.suborg_full_name.suborg_name, + "color": random.choice(["umber", "khaki", "wine", "straw"]), + "proposal": proposal_path if proposal_name else None, + } + ) if flag: - blogset = sorted(blogset, key=lambda i: (i['title'])) + blogset = sorted(blogset, key=lambda i: (i["title"])) blogsets.append((year.gsoc_year, blogset)) if not blogsets: - messages.add_message(request, messages.ERROR, - 'No blogs currently! Please visit again later.') + messages.add_message( + request, messages.ERROR, "No blogs currently! Please visit again later." + ) - return render(request, 'list_view.html', { - 'blogsets': blogsets, - }) + return render(request, "list_view.html", {"blogsets": blogsets}) diff --git a/gsoc/admin.py b/gsoc/admin.py index 4020debc..15f022d8 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,8 +1,31 @@ -from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, - BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, - GsocEndDate, Comment, SendEmail, BlogPostHistory, GsocYear, SubOrg) -from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, - GsocEndDateForm) +from .models import ( + UserProfile, + RegLink, + UserDetails, + Scheduler, + PageNotification, + AddUserLog, + BlogPostDueDate, + Builder, + Timeline, + ArticleReview, + Event, + SubOrgDetails, + GsocEndDate, + Comment, + SendEmail, + BlogPostHistory, + GsocYear, + SubOrg, +) +from .forms import ( + UserProfileForm, + UserDetailsForm, + RegLinkForm, + BlogPostDueDateForm, + EventForm, + GsocEndDateForm, +) from django.contrib.auth.models import User @@ -51,9 +74,9 @@ def return_func(self, request, obj=None, **kwargs): nonlocal ori_readonly_fields, ori_fieldsets is_request_by_student = request.user.student_profile() is not None if ori_readonly_fields is None: - ori_readonly_fields = getattr(self, 'readonly_fields', ()) or () + ori_readonly_fields = getattr(self, "readonly_fields", ()) or () if ori_fieldsets is None: - ori_fieldsets = getattr(self, 'fieldsets', ()) or () + ori_fieldsets = getattr(self, "fieldsets", ()) or () self.readonly_fields = ori_readonly_fields self.fieldsets = ori_fieldsets # self.actions = None @@ -61,34 +84,40 @@ def return_func(self, request, obj=None, **kwargs): if is_request_by_student: self.actions = None self.fieldsets = ( - (None, { - 'fields': ( - 'title', - 'publishing_date', - 'is_published', - 'is_featured', - 'featured_image', - 'lead_in', - )}), + ( + None, + { + "fields": ( + "title", + "publishing_date", + "is_published", + "is_featured", + "featured_image", + "lead_in", + ) + }, + ), # (_('Meta Options'), # {'classes': ('collapse',), # 'fields':()}), - (_('Advanced Settings'), - {'classes': ('collapse',), - 'fields': ('app_config',)}), - ) + ( + _("Advanced Settings"), + {"classes": ("collapse",), "fields": ("app_config",)}, + ), + ) self.readonly_fields = ( - 'author', - 'publishing_date', - 'is_featured', - 'featured_image', - 'slug', - 'meta_title', - 'meta_description', - 'meta_keywords', - 'owner', - ) + "author", + "publishing_date", + "is_featured", + "featured_image", + "slug", + "meta_title", + "meta_description", + "meta_keywords", + "owner", + ) return form + return return_func @@ -99,20 +128,30 @@ def Article_change_view(self, request, object_id, *args, **kwargs): try: original_article = Article.objects.get(pk=object_id) except Article.DoesNotExist: - return super(ArticleAdmin, self).change_view(request, object_id, *args, **kwargs) + return super(ArticleAdmin, self).change_view( + request, object_id, *args, **kwargs + ) if is_student_request: timenow = original_article.publishing_date try: person = Person.objects.get(user=request.user) - post_data['author'] = person.pk + post_data["author"] = person.pk except Person.DoesNotExist: person = Person.objects.create(user=request.user) - post_data['author'] = person.pk - post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' - post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' - post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' - post_data['initial-publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' - post_data['owner'] = request.user.pk + post_data["author"] = person.pk + post_data[ + "publishing_date_0" + ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" + post_data[ + "initial-publishing_date_0" + ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" + post_data[ + "publishing_date_1" + ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" + post_data[ + "initial-publishing_date_1" + ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" + post_data["owner"] = request.user.pk request.GET = data request.POST = post_data return super(ArticleAdmin, self).change_view(request, object_id, *args, **kwargs) @@ -124,31 +163,39 @@ def Article_add_view(self, request, *args, **kwargs): post_data = request.POST.copy() try: person = Person.objects.get(user=request.user) - data['author'] = person.pk + data["author"] = person.pk except Person.DoesNotExist: pass if is_student_request: timenow = timezone.now() try: person = Person.objects.get(user=request.user) - post_data['author'] = person.pk + post_data["author"] = person.pk except Person.DoesNotExist: person = Person.objects.create(user=request.user) - post_data['author'] = person.pk - - post_data['publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' - post_data['initial-publishing_date_0'] = f'{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}' - post_data['publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' - post_data['initial-publishing_date_1'] = f'{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}' - post_data['owner'] = request.user.pk - post_data['slug'] = '' - post_data['meta_title'] = '' - post_data['meta_description'] = '' - post_data['meta_keywords'] = '' - post_data['tags'] = '' - post_data['related'] = '' - post_data['featured_image'] = '' - post_data['is_featured'] = '' + post_data["author"] = person.pk + + post_data[ + "publishing_date_0" + ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" + post_data[ + "initial-publishing_date_0" + ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" + post_data[ + "publishing_date_1" + ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" + post_data[ + "initial-publishing_date_1" + ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" + post_data["owner"] = request.user.pk + post_data["slug"] = "" + post_data["meta_title"] = "" + post_data["meta_description"] = "" + post_data["meta_keywords"] = "" + post_data["tags"] = "" + post_data["related"] = "" + post_data["featured_image"] = "" + post_data["is_featured"] = "" request.GET = data request.POST = post_data return super(ArticleAdmin, self).add_view(request, *args, **kwargs) @@ -233,45 +280,51 @@ def send_reminder(self, request, queryset): reglink.create_reminder(trigger_time=timezone.now()) -send_reminder.short_description = 'Send reminders' +send_reminder.short_description = "Send reminders" class RegLinkAdmin(admin.ModelAdmin): fieldsets = ( - (None, {'fields': ('url', 'is_sent', - 'adduserlog', 'has_scheduler', 'has_reminder')}), - ("Configure user to be registered", - {'fields': ( - "user_role", - "user_suborg", - "user_gsoc_year", - "email", - )}), - ) + ( + None, + { + "fields": ( + "url", + "is_sent", + "adduserlog", + "has_scheduler", + "has_reminder", + ) + }, + ), + ( + "Configure user to be registered", + {"fields": ("user_role", "user_suborg", "user_gsoc_year", "email")}, + ), + ) readonly_fields = ( - 'url', - 'adduserlog', - 'is_sent', - 'adduserlog', - 'has_scheduler', - 'has_reminder' - ) - list_display = ('reglink_id', 'email', 'is_used', 'is_sent', 'has_reminder', 'created_at') - list_filter = [ - 'is_used', - 'created_at', - ] + "url", + "adduserlog", + "is_sent", + "adduserlog", + "has_scheduler", + "has_reminder", + ) + list_display = ( + "reglink_id", + "email", + "is_used", + "is_sent", + "has_reminder", + "created_at", + ) + list_filter = ["is_used", "created_at"] actions = [send_reminder] def get_readonly_fields(self, request, obj=None): readonly_fields = self.readonly_fields if obj and obj.is_used: - readonly_fields += ( - "user_role", - "user_suborg", - "user_gsoc_year", - 'email', - ) + readonly_fields += ("user_role", "user_suborg", "user_gsoc_year", "email") return readonly_fields @@ -281,41 +334,65 @@ def get_readonly_fields(self, request, obj=None): def rerun_scheduler(self, request, queryset): for scheduler in queryset: - Scheduler.objects.create(command=scheduler.command, - data=scheduler.data) + Scheduler.objects.create(command=scheduler.command, data=scheduler.data) -rerun_scheduler.short_description = 'Rerun schedulers' +rerun_scheduler.short_description = "Rerun schedulers" class SchedulerAdmin(admin.ModelAdmin): - list_display = ('command', 'short_data', 'success', 'last_error', 'created') - list_filter = ('command', 'success') - sortable_by = ('created', 'last_error') + list_display = ("command", "short_data", "success", "last_error", "created") + list_filter = ("command", "success") + sortable_by = ("created", "last_error") actions = [rerun_scheduler] def short_data(self, obj): - return '{}...'.format(obj.data[:50]) + return "{}...".format(obj.data[:50]) admin.site.register(Scheduler, SchedulerAdmin) class HiddenUserProfileAdmin(admin.ModelAdmin): - list_display = ('user', 'email', 'gsoc_year', 'suborg_full_name', 'proposal_confirmed', - 'hidden', 'reminder_disabled', 'current_blog_count') - list_filter = ('hidden', 'reminder_disabled') - readonly_fields = ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'blog_link', - 'proposal_confirmed', 'current_blog_count', 'github_handle') + list_display = ( + "user", + "email", + "gsoc_year", + "suborg_full_name", + "proposal_confirmed", + "hidden", + "reminder_disabled", + "current_blog_count", + ) + list_filter = ("hidden", "reminder_disabled") + readonly_fields = ( + "user", + "role", + "gsoc_year", + "accepted_proposal_pdf", + "blog_link", + "proposal_confirmed", + "current_blog_count", + "github_handle", + ) fieldsets = ( - ('Unhide', { - 'fields': ('hidden', 'reminder_disabled') - }), - ('User Profile Details', { - 'fields': ('user', 'role', 'gsoc_year', 'accepted_proposal_pdf', 'proposal_confirmed', - 'blog_link', 'current_blog_count', 'github_handle') - }) - ) + ("Unhide", {"fields": ("hidden", "reminder_disabled")}), + ( + "User Profile Details", + { + "fields": ( + "user", + "role", + "gsoc_year", + "accepted_proposal_pdf", + "proposal_confirmed", + "blog_link", + "current_blog_count", + "github_handle", + ) + }, + ), + ) def blog_link(self, obj): ns = obj.app_config.namespace @@ -334,8 +411,8 @@ def get_queryset(self, request): class PageNotificationAdmin(admin.ModelAdmin): - list_display = ('message', 'user', 'page') - list_filter = ('user', 'page') + list_display = ("message", "user", "page") + list_filter = ("user", "page") def save_model(self, request, obj, form, change): obj.user = request.user @@ -343,22 +420,19 @@ def save_model(self, request, obj, form, change): def get_fieldsets(self, request, obj=None): if request.user.is_superuser: - fieldsets = ( - (None, { - 'fields': ('user', 'page', 'message') - }), ) + fieldsets = ((None, {"fields": ("user", "page", "message")}),) else: - fieldsets = ((None, {'fields': ('page', 'message')}), ) + fieldsets = ((None, {"fields": ("page", "message")}),) return fieldsets def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "page": - kwargs['queryset'] = Page.objects.filter(publisher_is_draft=True) + kwargs["queryset"] = Page.objects.filter(publisher_is_draft=True) if not request.user.is_superuser: pp = PagePermission.objects.filter(user=request.user) pages = [_.page.pk for _ in pp] - kwargs['queryset'] = kwargs['queryset'].filter(pk__in=pages) + kwargs["queryset"] = kwargs["queryset"].filter(pk__in=pages) return super().formfield_for_foreignkey(db_field, request, **kwargs) @@ -372,24 +446,29 @@ class RegLinkInline(admin.TabularInline): class AddUserLogAdmin(admin.ModelAdmin): - list_display = ('log_id', 'used_stat') - readonly_fields = ('log_id', ) - inlines = (RegLinkInline, ) - change_form_template = 'admin/adduserlog_change_form.html' + list_display = ("log_id", "used_stat") + readonly_fields = ("log_id",) + inlines = (RegLinkInline,) + change_form_template = "admin/adduserlog_change_form.html" - def changeform_view(self, request, object_id, form_url='', extra_context=None): + def changeform_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} - extra_context['years'] = GsocYear.objects.all() - extra_context['suborgs'] = SubOrg.objects.all() - extra_context['roles'] = [(0, 'Others'), (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student')] + extra_context["years"] = GsocYear.objects.all() + extra_context["suborgs"] = SubOrg.objects.all() + extra_context["roles"] = [ + (0, "Others"), + (1, "Suborg Admin"), + (2, "Mentor"), + (3, "Student"), + ] return super().changeform_view( - request, object_id, form_url, extra_context=extra_context, + request, object_id, form_url, extra_context=extra_context ) def used_stat(self, obj): _all = len(RegLink.objects.filter(adduserlog=obj)) _used = len(RegLink.objects.filter(adduserlog=obj, is_used=True)) - return '{}/{}'.format(_used, _all) + return "{}/{}".format(_used, _all) admin.site.register(AddUserLog, AddUserLogAdmin) @@ -397,21 +476,20 @@ def used_stat(self, obj): def rerun_builder(self, request, queryset): for builder in queryset: - Builder.objects.create(category=builder.category, - data=builder.data) + Builder.objects.create(category=builder.category, data=builder.data) -rerun_builder.short_description = 'Rerun builders' +rerun_builder.short_description = "Rerun builders" class BuilderAdmin(admin.ModelAdmin): - list_display = ('category', 'short_data', 'built', 'last_error') - list_filter = ('category', 'built') - sortable_by = ('last_error') + list_display = ("category", "short_data", "built", "last_error") + list_filter = ("category", "built") + sortable_by = "last_error" actions = [rerun_builder] def short_data(self, obj): - return '{}...'.format(obj.data[:50]) + return "{}...".format(obj.data[:50]) admin.site.register(Builder, BuilderAdmin) @@ -433,8 +511,8 @@ class GsocEndDateInline(admin.TabularInline): class TimelineAdmin(admin.ModelAdmin): - list_display = ('gsoc_year', ) - exclude = ('calendar_id', ) + list_display = ("gsoc_year",) + exclude = ("calendar_id",) inlines = (BlogPostDueDateInline, EventInline, GsocEndDateInline) @@ -442,10 +520,23 @@ class TimelineAdmin(admin.ModelAdmin): class ArticleReviewAdmin(admin.ModelAdmin): - list_display = ('article', 'author', 'article_link', 'is_reviewed', 'last_reviewed_by') - list_filter = ('last_reviewed_by', 'is_reviewed') - fields = ('article', 'author', 'article_link', 'lead', 'is_reviewed', 'last_reviewed_by') - change_form_template = 'admin/article_review_change_form.html' + list_display = ( + "article", + "author", + "article_link", + "is_reviewed", + "last_reviewed_by", + ) + list_filter = ("last_reviewed_by", "is_reviewed") + fields = ( + "article", + "author", + "article_link", + "lead", + "is_reviewed", + "last_reviewed_by", + ) + change_form_template = "admin/article_review_change_form.html" def lead(self, obj): return obj.article.lead_in @@ -454,8 +545,10 @@ def author(self, obj): return obj.article.owner def article_link(self, obj): - url = reverse('{}:article-detail'.format(obj.article.app_config.namespace), - args=[obj.article.slug]) + url = reverse( + "{}:article-detail".format(obj.article.app_config.namespace), + args=[obj.article.slug], + ) return mark_safe('Goto Article'.format(url)) def has_add_permission(self, request, obj=None): @@ -469,8 +562,8 @@ def has_change_permission(self, request, obj=None): class EventAdmin(admin.ModelAdmin): - list_display = ('title', 'start_date', 'end_date') - fields = ('title', 'start_date', 'end_date', 'timeline', 'calendar_link') + list_display = ("title", "start_date", "end_date") + fields = ("title", "start_date", "end_date", "timeline", "calendar_link") def has_add_permission(self, request, obj=None): return False @@ -483,38 +576,76 @@ def has_change_permission(self, request, obj=None): class SubOrgDetailsAdmin(admin.ModelAdmin): - list_display = ('suborg_name', 'gsoc_year', 'changed', 'accepted') - list_filter = ('gsoc_year', 'changed') + list_display = ("suborg_name", "gsoc_year", "changed", "accepted") + list_filter = ("gsoc_year", "changed") # fields = ('last_message', ) readonly_fields = ( - 'gsoc_year', 'suborg_admin_email', 'suborg_admin_2_email', 'suborg_admin_3_email', - 'past_gsoc_experience', 'past_years', - 'suborg_in_past', 'applied_but_not_selected', 'year_of_start', - 'source_code', 'docs', 'anything_else', 'suborg_name', 'description', - 'logo', 'primary_os_license', 'ideas_list', 'chat', 'mailing_list', 'twitter_url', - 'blog_url', 'link', 'accepted', 'changed', 'last_reviewed_at', 'last_reviewed_by', - 'created_at', 'updated_at', + "gsoc_year", + "suborg_admin_email", + "suborg_admin_2_email", + "suborg_admin_3_email", + "past_gsoc_experience", + "past_years", + "suborg_in_past", + "applied_but_not_selected", + "year_of_start", + "source_code", + "docs", + "anything_else", + "suborg_name", + "description", + "logo", + "primary_os_license", + "ideas_list", + "chat", + "mailing_list", + "twitter_url", + "blog_url", + "link", + "accepted", + "changed", + "last_reviewed_at", + "last_reviewed_by", + "created_at", + "updated_at", ) fieldsets = ( ( - 'Details', { - 'fields': ( - 'gsoc_year', 'suborg_admin_email', - 'past_gsoc_experience', 'past_years', - 'suborg_in_past', 'applied_but_not_selected', - 'year_of_start', 'source_code', 'docs', 'anything_else', - 'suborg_name', 'description', - 'logo', 'primary_os_license', 'ideas_list', 'chat', - 'mailing_list', 'twitter_url', - 'blog_url', 'link', 'changed', 'accepted', - 'last_reviewed_at', 'last_reviewed_by', - 'created_at', 'updated_at', + "Details", + { + "fields": ( + "gsoc_year", + "suborg_admin_email", + "past_gsoc_experience", + "past_years", + "suborg_in_past", + "applied_but_not_selected", + "year_of_start", + "source_code", + "docs", + "anything_else", + "suborg_name", + "description", + "logo", + "primary_os_license", + "ideas_list", + "chat", + "mailing_list", + "twitter_url", + "blog_url", + "link", + "changed", + "accepted", + "last_reviewed_at", + "last_reviewed_by", + "created_at", + "updated_at", ) - } + }, ), - ('Review', {'fields': ('last_message', )}) + ("Review", {"fields": ("last_message",)}), ) - change_form_template = 'admin/suborg_details_change_form.html' + change_form_template = "admin/suborg_details_change_form.html" def save_model(self, request, obj, form, change): obj.changed = False @@ -531,7 +662,7 @@ def has_add_permission(self, request, obj=None): class CommentAdmin(admin.ModelAdmin): - list_display = ('username', 'article', 'content') + list_display = ("username", "article", "content") def has_add_permission(self, request, obj=None): return False @@ -541,8 +672,8 @@ def has_add_permission(self, request, obj=None): class SendEmailAdmin(admin.ModelAdmin): - list_display = ('to', 'to_group', 'subject') - exclude = ('scheduler', ) + list_display = ("to", "to_group", "subject") + exclude = ("scheduler",) def has_change_permission(self, request, obj=None): return False @@ -552,9 +683,9 @@ def has_change_permission(self, request, obj=None): class BlogPostHistoryAdmin(admin.ModelAdmin): - list_display = ('article', 'timestamp') - list_filter = ('article', ) - fields = ('article', 'content_safe', 'timestamp') + list_display = ("article", "timestamp") + list_filter = ("article",) + fields = ("article", "content_safe", "timestamp") def has_change_permission(self, request, obj=None): return False @@ -567,7 +698,7 @@ def content_safe(self, obj): class GsocYearAdmin(admin.ModelAdmin): - list_display = ('gsoc_year', ) + list_display = ("gsoc_year",) admin.site.register(GsocYear, GsocYearAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 9fae7dd7..87d09d57 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -5,7 +5,7 @@ USER_SETTINGS_BREAK, TOOLBAR_DISABLE_BREAK, SHORTCUTS_BREAK, - CLIPBOARD_BREAK + CLIPBOARD_BREAK, ) from cms.utils.conf import get_cms_setting from cms.utils.urlutils import admin_reverse @@ -16,9 +16,7 @@ from django.utils.translation import get_language_from_request from django.urls import reverse -from aldryn_translation_tools.utils import ( - get_admin_url, get_object_from_request, -) +from aldryn_translation_tools.utils import get_admin_url, get_object_from_request from aldryn_newsblog.models import Article from aldryn_newsblog.cms_toolbars import NewsBlogToolbar @@ -29,78 +27,105 @@ def add_admin_menu(self): if not self._admin_menu: - self._admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, self.current_site.name) + self._admin_menu = self.toolbar.get_or_create_menu( + ADMIN_MENU_IDENTIFIER, self.current_site.name + ) # Users button self.add_users_button(self._admin_menu) # sites menu - sites_queryset = Site.objects.order_by('name') + sites_queryset = Site.objects.order_by("name") if len(sites_queryset) > 1: - sites_menu = self._admin_menu.get_or_create_menu('sites', _('Sites')) - sites_menu.add_sideframe_item(_('Admin Sites'), url=admin_reverse('sites_site_changelist')) + sites_menu = self._admin_menu.get_or_create_menu("sites", _("Sites")) + sites_menu.add_sideframe_item( + _("Admin Sites"), url=admin_reverse("sites_site_changelist") + ) sites_menu.add_break(ADMIN_SITES_BREAK) for site in sites_queryset: - sites_menu.add_link_item(site.name, url='http://%s' % site.domain, - active=site.pk == self.current_site.pk) + sites_menu.add_link_item( + site.name, + url="http://%s" % site.domain, + active=site.pk == self.current_site.pk, + ) - user = getattr(self.request, 'user', None) + user = getattr(self.request, "user", None) # admin - self._admin_menu.add_sideframe_item(_('Administration'), url=admin_reverse('index')) + self._admin_menu.add_sideframe_item( + _("Administration"), url=admin_reverse("index") + ) # scheduler if user and user.is_superuser: - self._admin_menu.add_sideframe_item(_('Schedulers'), - url=admin_reverse('gsoc_scheduler_changelist')) - self._admin_menu.add_sideframe_item(_('Builders'), - url=admin_reverse('gsoc_builder_changelist')) - self._admin_menu.add_sideframe_item(_('Review Article'), - url=admin_reverse('gsoc_articlereview_changelist')) - self._admin_menu.add_sideframe_item(_('Timeline'), - url=admin_reverse('gsoc_timeline_changelist')) - self._admin_menu.add_sideframe_item(_('Send Email'), - url=admin_reverse('gsoc_sendemail_add')) - self._admin_menu.add_sideframe_item(_('Suborg Applications'), - url=admin_reverse('gsoc_suborgdetails_changelist')) + self._admin_menu.add_sideframe_item( + _("Schedulers"), url=admin_reverse("gsoc_scheduler_changelist") + ) + self._admin_menu.add_sideframe_item( + _("Builders"), url=admin_reverse("gsoc_builder_changelist") + ) + self._admin_menu.add_sideframe_item( + _("Review Article"), url=admin_reverse("gsoc_articlereview_changelist") + ) + self._admin_menu.add_sideframe_item( + _("Timeline"), url=admin_reverse("gsoc_timeline_changelist") + ) + self._admin_menu.add_sideframe_item( + _("Send Email"), url=admin_reverse("gsoc_sendemail_add") + ) + self._admin_menu.add_sideframe_item( + _("Suborg Applications"), + url=admin_reverse("gsoc_suborgdetails_changelist"), + ) self._admin_menu.add_modal_item( - name='Add Users', - url=admin_reverse('gsoc_adduserlog_add'), + name="Add Users", + url=admin_reverse("gsoc_adduserlog_add"), on_close=None, - ) + ) if user: - self._admin_menu.add_link_item(_('New Suborg Application'), - reverse('suborg:register_suborg')) - self._admin_menu.add_link_item(_('Manage Suborg Application'), - reverse('suborg:application_list')) + self._admin_menu.add_link_item( + _("New Suborg Application"), reverse("suborg:register_suborg") + ) + self._admin_menu.add_link_item( + _("Manage Suborg Application"), reverse("suborg:application_list") + ) self._admin_menu.add_break(ADMINISTRATION_BREAK) # cms users settings - self._admin_menu.add_sideframe_item(_('User settings'), - url=admin_reverse('cms_usersettings_change')) + self._admin_menu.add_sideframe_item( + _("User settings"), url=admin_reverse("cms_usersettings_change") + ) self._admin_menu.add_break(USER_SETTINGS_BREAK) # clipboard if self.toolbar.edit_mode_active: # True if the clipboard exists and there's plugins in it. clipboard_is_bound = self.toolbar.clipboard_plugin - self._admin_menu.add_link_item(_('Clipboard...'), url='#', - extra_classes=['cms-clipboard-trigger'], - disabled=not clipboard_is_bound) - self._admin_menu.add_link_item(_('Clear clipboard'), url='#', - extra_classes=['cms-clipboard-empty'], - disabled=not clipboard_is_bound) + self._admin_menu.add_link_item( + _("Clipboard..."), + url="#", + extra_classes=["cms-clipboard-trigger"], + disabled=not clipboard_is_bound, + ) + self._admin_menu.add_link_item( + _("Clear clipboard"), + url="#", + extra_classes=["cms-clipboard-empty"], + disabled=not clipboard_is_bound, + ) self._admin_menu.add_break(CLIPBOARD_BREAK) # Disable toolbar self._admin_menu.add_link_item( - _('Disable toolbar'), url='?%s' % - get_cms_setting('CMS_TOOLBAR_URL__DISABLE')) + _("Disable toolbar"), + url="?%s" % get_cms_setting("CMS_TOOLBAR_URL__DISABLE"), + ) self._admin_menu.add_break(TOOLBAR_DISABLE_BREAK) - self._admin_menu.add_link_item(_('Shortcuts...'), url='#', - extra_classes=('cms-show-shortcuts',)) + self._admin_menu.add_link_item( + _("Shortcuts..."), url="#", extra_classes=("cms-show-shortcuts",) + ) self._admin_menu.add_break(SHORTCUTS_BREAK) # logout @@ -108,13 +133,13 @@ def add_admin_menu(self): def add_goto_blog_button(self): - user = getattr(self.request, 'user', None) + user = getattr(self.request, "user", None) if user and user.is_current_year_student(): profile = user.student_profile() ns = profile.app_config.namespace page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) url = page.get_absolute_url() - self.toolbar.add_button(_('My Blog'), url, side=self.toolbar.RIGHT) + self.toolbar.add_button(_("My Blog"), url, side=self.toolbar.RIGHT) def populate(self): @@ -137,7 +162,7 @@ def populate(self): # Do nothing if there is no NewsBlog app_config to work with return - user = getattr(self.request, 'user', None) + user = getattr(self.request, "user", None) try: view_name = self.request.resolver_match.view_name except AttributeError: @@ -147,18 +172,15 @@ def populate(self): language = get_language_from_request(self.request, check_path=True) # If we're on an Article detail page, then get the article - if view_name == '{0}:article-detail'.format(config.namespace): + if view_name == "{0}:article-detail".format(config.namespace): article = get_object_from_request(Article, self.request) else: article = None - menu = self.toolbar.get_or_create_menu('newsblog-app', - config.get_app_title()) + menu = self.toolbar.get_or_create_menu("newsblog-app", config.get_app_title()) - change_config_perm = user.has_perm( - 'aldryn_newsblog.change_newsblogconfig') - add_config_perm = user.has_perm( - 'aldryn_newsblog.add_newsblogconfig') + change_config_perm = user.has_perm("aldryn_newsblog.change_newsblogconfig") + add_config_perm = user.has_perm("aldryn_newsblog.add_newsblogconfig") config_perms = [change_config_perm, add_config_perm] change_article_perm = False @@ -169,7 +191,9 @@ def populate(self): else: for profile in userprofiles: if profile.app_config == config: - change_perm = Permission.objects.filter(codename='change_article').first() + change_perm = Permission.objects.filter( + codename="change_article" + ).first() if change_perm in user.user_permissions.all(): change_article_perm = True break @@ -177,16 +201,16 @@ def populate(self): add_article_perm = user.is_superuser if article else False delete_article_perm = user.is_superuser if article else False - article_perms = [change_article_perm, add_article_perm, - delete_article_perm, ] + article_perms = [change_article_perm, add_article_perm, delete_article_perm] if change_config_perm: url_args = {} if language: - url_args = {'language': language, } - url = get_admin_url('aldryn_newsblog_newsblogconfig_change', - [config.pk, ], **url_args) - menu.add_modal_item(_('Configure addon'), url=url) + url_args = {"language": language} + url = get_admin_url( + "aldryn_newsblog_newsblogconfig_change", [config.pk], **url_args + ) + menu.add_modal_item(_("Configure addon"), url=url) if any(config_perms) and any(article_perms): menu.add_break() @@ -194,10 +218,9 @@ def populate(self): if change_article_perm: url_args = {} if config: - url_args = {'app_config__id__exact': config.pk} - url = get_admin_url('aldryn_newsblog_article_changelist', - **url_args) - menu.add_sideframe_item(_('Article list'), url=url) + url_args = {"app_config__id__exact": config.pk} + url = get_admin_url("aldryn_newsblog_article_changelist", **url_args) + menu.add_sideframe_item(_("Article list"), url=url) # if add_article_perm: # url_args = {'app_config': config.pk, 'owner': user.pk, } @@ -209,45 +232,46 @@ def populate(self): if change_article_perm and article: url_args = {} if language: - url_args = {'language': language, } - url = get_admin_url('aldryn_newsblog_article_change', - [article.pk, ], **url_args) - menu.add_modal_item(_('Edit this article'), url=url, - active=True) + url_args = {"language": language} + url = get_admin_url( + "aldryn_newsblog_article_change", [article.pk], **url_args + ) + menu.add_modal_item(_("Edit this article"), url=url, active=True) if change_article_perm and article: if article.is_published: - text = _('Unpublish Article') - url = reverse('unpublish_article', args=[article.id]) + text = _("Unpublish Article") + url = reverse("unpublish_article", args=[article.id]) else: - text = _('Publish Article') - url = reverse('publish_article', args=[article.id]) + text = _("Publish Article") + url = reverse("publish_article", args=[article.id]) - self.toolbar.add_button(text, url=url, - side=self.toolbar.RIGHT) + self.toolbar.add_button(text, url=url, side=self.toolbar.RIGHT) if delete_article_perm and article: - redirect_url = self.get_on_delete_redirect_url( - article, language=language) - url = get_admin_url('aldryn_newsblog_article_delete', - [article.pk, ]) - menu.add_modal_item(_('Delete this article'), url=url, - on_close=redirect_url) + redirect_url = self.get_on_delete_redirect_url(article, language=language) + url = get_admin_url("aldryn_newsblog_article_delete", [article.pk]) + menu.add_modal_item( + _("Delete this article"), url=url, on_close=redirect_url + ) try: article_review = ArticleReview.objects.get(article=article) if not article_review.is_reviewed and user.is_superuser: - url = reverse('review_article', args=[article.id]) - self.toolbar.add_button(_('Mark Reviewed'), url=url, - side=self.toolbar.RIGHT) + url = reverse("review_article", args=[article.id]) + self.toolbar.add_button( + _("Mark Reviewed"), url=url, side=self.toolbar.RIGHT + ) except ArticleReview.DoesNotExist: pass try: if user.is_superuser: - url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" - f"?article__id__exact={article.id}") - self.toolbar.add_sideframe_item(_('View History'), url=url) + url = ( + f"{admin_reverse('gsoc_blogposthistory_changelist')}" + f"?article__id__exact={article.id}" + ) + self.toolbar.add_sideframe_item(_("View History"), url=url) except Exception as e: pass diff --git a/gsoc/cms_wizards.py b/gsoc/cms_wizards.py index bacb8478..cf16f02b 100644 --- a/gsoc/cms_wizards.py +++ b/gsoc/cms_wizards.py @@ -17,7 +17,7 @@ from aldryn_newsblog.cms_wizards import ( NewsBlogArticleWizard, get_published_app_configs, - newsblog_article_wizard + newsblog_article_wizard, ) from parler.forms import TranslatableModelForm @@ -37,7 +37,7 @@ def user_has_add_permission(self, user, **kwargs): # Ensure user has permission to create articles. if user.student_profile() is not None: - add_perm = Permission.objects.filter(codename='add_article').first() + add_perm = Permission.objects.filter(codename="add_article").first() if add_perm in user.user_permissions.all(): return True @@ -60,10 +60,10 @@ class CreateNewsBlogArticleForm(BaseFormMixin, TranslatableModelForm): class Meta: model = Article - fields = ['title', 'lead_in', 'app_config'] + fields = ["title", "lead_in", "app_config"] # The natural widget for app_config is meant for normal Admin views and # contains JS to refresh the page on change. This is not wanted here. - widgets = {'app_config': forms.Select()} + widgets = {"app_config": forms.Select()} def __init__(self, **kwargs): super(CreateNewsBlogArticleForm, self).__init__(**kwargs) @@ -79,18 +79,18 @@ def __init__(self, **kwargs): app_config_choices = [] for profile in userprofiles: - app_config_choices.append((profile.app_config.pk, profile.app_config.get_app_title())) - - self.fields['app_config'] = forms.ChoiceField( - label=_('Section'), - required=True, - choices=app_config_choices + app_config_choices.append( + (profile.app_config.pk, profile.app_config.get_app_title()) ) + self.fields["app_config"] = forms.ChoiceField( + label=_("Section"), required=True, choices=app_config_choices + ) + def clean(self): cd = self.cleaned_data - app_config = NewsBlogConfig.objects.get(pk=self.cleaned_data['app_config']) - cd['app_config'] = app_config + app_config = NewsBlogConfig.objects.get(pk=self.cleaned_data["app_config"]) + cd["app_config"] = app_config return cd def save(self, commit=True): @@ -107,7 +107,7 @@ def save(self, commit=True): title=_(u"New news/blog article"), weight=200, form=CreateNewsBlogArticleForm, - description=_(u"Create a new news/blog article.") + description=_(u"Create a new news/blog article."), ) wizard_pool.register(newsblog_article_wizard) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index ee32dd76..3e3a9bac 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -142,7 +142,8 @@ def build_remove_user_details(builder): ReaddUser.objects.create(user=profile.user, uuid=_uuid) template_data = { # TODO: change this after the view is created - "link": settings.INETLOCATION + "use reverse here" + "link": settings.INETLOCATION + + "use reverse here" } scheduler_data = build_send_mail_json( email, @@ -150,6 +151,6 @@ def build_remove_user_details(builder): subject="Your personal details have been removed from our database", template_data=template_data, ) - Scheduler.objects.create(command='send_email' data=scheduler_data) + Scheduler.objects.create(command="send_email", data=scheduler_data) except Exception as e: return str(e) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 28ce0aa8..e4f1ee78 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -6,41 +6,44 @@ from .irc import send_message -from gsoc.models import (Scheduler, RegLink, GsocYear, UserProfile, Event, - BlogPostDueDate, SubOrgDetails) -from .tools import (send_mail, render_site_template, push_site_template, - archive_current_gsoc_files, push_images) +from gsoc.models import ( + Scheduler, + RegLink, + GsocYear, + UserProfile, + Event, + BlogPostDueDate, + SubOrgDetails, +) +from .tools import ( + send_mail, + render_site_template, + push_site_template, + archive_current_gsoc_files, + push_images, +) def send_email(scheduler: Scheduler): data = json.loads(scheduler.data) try: - send_mail(data['send_to'], - data['subject'], - data['template'], - data['template_data']) + send_mail( + data["send_to"], data["subject"], data["template"], data["template_data"] + ) except SMTPSenderRefused as e: - last_error = json.dumps({ - "message": str(e), - "smtp_code": e.smtp_code, - }) + last_error = json.dumps({"message": str(e), "smtp_code": e.smtp_code}) scheduler.last_error = last_error scheduler.success = False scheduler.save() return str(e) except SMTPResponseException as e: - last_error = json.dumps({ - "message": str(e), - "smtp_code": e.smtp_code, - }) + last_error = json.dumps({"message": str(e), "smtp_code": e.smtp_code}) scheduler.last_error = last_error scheduler.success = False scheduler.save() return str(e) except Exception as e: - last_error = json.dumps({ - "message": str(e), - }) + last_error = json.dumps({"message": str(e)}) scheduler.last_error = last_error scheduler.success = False scheduler.save() @@ -58,10 +61,10 @@ def revoke_student_permissions(scheduler: Scheduler): try: u = User.objects.filter(pk=int(scheduler.data)).first() - add_perm = Permission.objects.filter(codename='add_article').first() - change_perm = Permission.objects.filter(codename='change_article').first() - delete_perm = Permission.objects.filter(codename='delete_article').first() - view_perm = Permission.objects.filter(codename='view_article').first() + add_perm = Permission.objects.filter(codename="add_article").first() + change_perm = Permission.objects.filter(codename="change_article").first() + delete_perm = Permission.objects.filter(codename="delete_article").first() + view_perm = Permission.objects.filter(codename="view_article").first() u.user_permissions.remove(add_perm, change_perm, delete_perm, view_perm) @@ -90,7 +93,7 @@ def send_irc_msgs(schedulers): def send_reg_reminder(scheduler: Scheduler): try: data = json.loads(scheduler.data) - reglink = RegLink.objects.get(pk=data['object_pk']) + reglink = RegLink.objects.get(pk=data["object_pk"]) if reglink.is_usable(): return send_email(scheduler) else: @@ -113,7 +116,7 @@ def add_blog_counter(scheduler: Scheduler): def add_calendar_event(scheduler: Scheduler): try: - pk = json.loads(scheduler.data)['event'] + pk = json.loads(scheduler.data)["event"] event = Event.objects.get(pk=pk) event.add_to_calendar() return None @@ -123,43 +126,46 @@ def add_calendar_event(scheduler: Scheduler): def update_site_template(scheduler: Scheduler): try: - template = json.loads(scheduler.data)['template'] + template = json.loads(scheduler.data)["template"] gsoc_year = GsocYear.objects.first() - if template == 'deadlines.html': + if template == "deadlines.html": context = { - 'events': Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), - 'duedates': BlogPostDueDate.objects.filter(timeline__gsoc_year=gsoc_year).all(), - } - elif template == 'index.html': + "events": Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), + "duedates": BlogPostDueDate.objects.filter( + timeline__gsoc_year=gsoc_year + ).all(), + } + elif template == "index.html": # change this if the number of contact fields increase - contact_fields = ('chat', 'mailing_list', 'twitter_url', 'blog_url', 'link') - suborgs = SubOrgDetails.objects.filter(gsoc_year=gsoc_year, accepted=True).all() + contact_fields = ("chat", "mailing_list", "twitter_url", "blog_url", "link") + suborgs = SubOrgDetails.objects.filter( + gsoc_year=gsoc_year, accepted=True + ).all() suborg_list = [] for suborg in suborgs: - f = open(suborg.logo.path, 'rb') + f = open(suborg.logo.path, "rb") lines = f.readlines() - content = b'' + content = b"" for line in lines: content = content + line push_images(suborg.logo.name, content) _ = { - 'name': suborg.suborg.suborg_name, - 'description': suborg.description, - 'logo': f'/{suborg.logo.name}', - 'ideas_list': suborg.ideas_list, - 'contact': [] - } + "name": suborg.suborg.suborg_name, + "description": suborg.description, + "logo": f"/{suborg.logo.name}", + "ideas_list": suborg.ideas_list, + "contact": [], + } contact_count = 0 for field in contact_fields: if getattr(suborg, field): contact_count += 1 - _['contact'].append((field.title().replace('_', ' '), - getattr(suborg, field))) - _['count'] = (contact_count // 2) + 1 + _["contact"].append( + (field.title().replace("_", " "), getattr(suborg, field)) + ) + _["count"] = (contact_count // 2) + 1 suborg_list.append(_) - context = { - 'suborgs': suborg_list - } + context = {"suborgs": suborg_list} content = render_site_template(template, context) push_site_template(settings.GITHUB_FILE_PATH[template], content) except Exception as e: diff --git a/gsoc/common/utils/irc.py b/gsoc/common/utils/irc.py index 3cabd968..f18d78fd 100644 --- a/gsoc/common/utils/irc.py +++ b/gsoc/common/utils/irc.py @@ -9,14 +9,12 @@ class ModIRCClient(IRCClient): - def __init__(self, handler, nick, server, messages): IRCClient.__init__(self, handler, nick, server) self.messages = messages class CommandBot(BaseIRCHandler): - def handle_register(self): for data in self.client.messages: commands = parse_data(data) @@ -29,7 +27,7 @@ def handle_disconnect(self): def handle_error(self, error, **params): if error == Err.NICKNAMEINUSE: - new_nick = params['nick'] + str(randint(1, 9)) + new_nick = params["nick"] + str(randint(1, 9)) self.client.register(nick=new_nick) @@ -41,18 +39,25 @@ def parse_data(data): """ data = json.loads(data) chunk_size = 150 - chunks = [data['message'][i:i + chunk_size] for i in range(0, len(data['message']), chunk_size)] + chunks = [ + data["message"][i : i + chunk_size] + for i in range(0, len(data["message"]), chunk_size) + ] num_chunks = len(chunks) commands = [] for i in range(num_chunks): commands.append('@aka add m{} "echo {}"'.format(i, chunks[i])) - echo_text = ' '.join(['echo' for i in range(num_chunks)]) - msg_text = ' '.join(['[m{}]'.format(i) for i in range(num_chunks)]) - commands.append('@messageparser add global "{}" [{} {}]'.format(data['command'], echo_text, msg_text)) + echo_text = " ".join(["echo" for i in range(num_chunks)]) + msg_text = " ".join(["[m{}]".format(i) for i in range(num_chunks)]) + commands.append( + '@messageparser add global "{}" [{} {}]'.format( + data["command"], echo_text, msg_text + ) + ) for i in range(num_chunks): - commands.append('@aka remove m{}'.format(i)) + commands.append("@aka remove m{}".format(i)) return commands diff --git a/gsoc/common/utils/memcached_stats.py b/gsoc/common/utils/memcached_stats.py index 9b7d267b..d71aac08 100644 --- a/gsoc/common/utils/memcached_stats.py +++ b/gsoc/common/utils/memcached_stats.py @@ -6,11 +6,11 @@ class MemcachedStats: _client = None - _key_regex = re.compile(r'ITEM (.*) \[(.*); (.*)\]') - _slab_regex = re.compile(r'STAT items:(.*):number') + _key_regex = re.compile(r"ITEM (.*) \[(.*); (.*)\]") + _slab_regex = re.compile(r"STAT items:(.*):number") _stat_regex = re.compile(r"STAT (.*) (.*)\r") - def __init__(self, host='localhost', port='11211', timeout=None): + def __init__(self, host="localhost", port="11211", timeout=None): self._host = host self._port = port self._timeout = timeout @@ -18,33 +18,35 @@ def __init__(self, host='localhost', port='11211', timeout=None): @property def client(self): if self._client is None: - self._client = telnetlib.Telnet(self._host, self._port, - self._timeout) + self._client = telnetlib.Telnet(self._host, self._port, self._timeout) return self._client def command(self, cmd): - ' Write a command to telnet and return the response ' - self.client.write(("%s\n" % cmd).encode('ascii')) - return self.client.read_until(b'END').decode('ascii') + " Write a command to telnet and return the response " + self.client.write(("%s\n" % cmd).encode("ascii")) + return self.client.read_until(b"END").decode("ascii") def key_details(self, sort=True, limit=100): - ' Return a list of tuples containing keys and details ' - cmd = 'stats cachedump %s %s' - keys = [key for id in self.slab_ids() - for key in self._key_regex.findall(self.command(cmd % (id, limit)))] + " Return a list of tuples containing keys and details " + cmd = "stats cachedump %s %s" + keys = [ + key + for id in self.slab_ids() + for key in self._key_regex.findall(self.command(cmd % (id, limit))) + ] if sort: return sorted(keys) else: return keys def keys(self, sort=True, limit=100): - ' Return a list of keys in use ' + " Return a list of keys in use " return [key[0] for key in self.key_details(sort=sort, limit=limit)] def slab_ids(self): - ' Return a list of slab ids in use ' - return self._slab_regex.findall(self.command('stats items')) + " Return a list of slab ids in use " + return self._slab_regex.findall(self.command("stats items")) def stats(self): - ' Return a dict containing memcached stats ' - return dict(self._stat_regex.findall(self.command('stats'))) + " Return a dict containing memcached stats " + return dict(self._stat_regex.findall(self.command("stats"))) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 0eedd6eb..4fed1183 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -10,30 +10,31 @@ from github import Github -def build_send_mail_json(send_to, - template: str, - subject: str, - template_data: dict = None): +def build_send_mail_json( + send_to, template: str, subject: str, template_data: dict = None +): if not isinstance(send_to, Sequence) and not isinstance(send_to, str): - raise TypeError('send_to must be a sequence of email addresses ' - 'or one email address as str!') + raise TypeError( + "send_to must be a sequence of email addresses " + "or one email address as str!" + ) return json.dumps(locals()) -def build_send_reminder_json(send_to, - object_pk, - template: str, - subject: str, - template_data: dict = None): +def build_send_reminder_json( + send_to, object_pk, template: str, subject: str, template_data: dict = None +): if not isinstance(send_to, Sequence) and not isinstance(send_to, str): - raise TypeError('send_to must be a sequence of email addresses ' - 'or one email address as str!') + raise TypeError( + "send_to must be a sequence of email addresses " + "or one email address as str!" + ) return json.dumps(locals()) def send_mail(send_to, subject, template, context={}): try: - template = get_template(f'email/{template}') + template = get_template(f"email/{template}") except TemplateDoesNotExist: template = Template(template) @@ -47,14 +48,14 @@ def send_mail(send_to, subject, template, context={}): from_email=settings.SERVER_EMAIL, reply_to=settings.REPLY_EMAIL, to=send_to, - ) + ) send_email.content_subtype = "html" send_email.send() def render_site_template(template, context): try: - template = get_template(f'site/{template}') + template = get_template(f"site/{template}") except TemplateDoesNotExist: template = Template(template) @@ -66,13 +67,13 @@ def push_site_template(file_path, content): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) f = repo.get_contents(file_path) - repo.update_file(f.path, f'Update {file_path}', content, f.sha) + repo.update_file(f.path, f"Update {file_path}", content, f.sha) def push_images(file_path, content): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) - repo.create_file(file_path, f'Add {file_path} logo', content) + repo.create_file(file_path, f"Add {file_path} logo", content) def is_year(file_name): @@ -85,13 +86,15 @@ def is_year(file_name): return False -def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md', 'favicon.ico', 'robots.txt']): - contents = repo.get_contents('') +def get_files( + repo, except_files=["CNAME", "LICENSE.md", "README.md", "favicon.ico", "robots.txt"] +): + contents = repo.get_contents("") files = [] while contents: file_content = contents.pop(0) if not (file_content.path in except_files or is_year(file_content.path)): - if file_content.type == 'dir': + if file_content.type == "dir": contents.extend(repo.get_contents(file_content.path)) else: files.append(file_content) @@ -99,9 +102,9 @@ def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md', 'favicon.i def update_robots_file(repo, current_year): - c = repo.get_contents('robots.txt') - new_content = c.decoded_content.strip() + f'\nDisallow: /{current_year}/\n'.encode() - repo.update_file(c.path, 'Update robots.txt', new_content, c.sha) + c = repo.get_contents("robots.txt") + new_content = c.decoded_content.strip() + f"\nDisallow: /{current_year}/\n".encode() + repo.update_file(c.path, "Update robots.txt", new_content, c.sha) def archive_current_gsoc_files(current_year): @@ -111,10 +114,15 @@ def archive_current_gsoc_files(current_year): update_robots_file(repo, current_year) for file in files: try: - repo.create_file(f'{current_year}/{file.path}', - f'Archive GSoC {current_year} files', file.decoded_content) + repo.create_file( + f"{current_year}/{file.path}", + f"Archive GSoC {current_year} files", + file.decoded_content, + ) except Exception as e: - repo.update_file(f'{current_year}/{file.path}', - f'Archive GSoC {current_year} files', - file.content, - file.sha) + repo.update_file( + f"{current_year}/{file.path}", + f"Archive GSoC {current_year} files", + file.content, + file.sha, + ) diff --git a/gsoc/context_processors.py b/gsoc/context_processors.py index 42df4023..2a812086 100644 --- a/gsoc/context_processors.py +++ b/gsoc/context_processors.py @@ -2,6 +2,4 @@ def recaptcha_site_key(request): - return { - 'recaptcha_site_key': RECAPTCHA_PUBLIC_KEY, - } + return {"recaptcha_site_key": RECAPTCHA_PUBLIC_KEY} diff --git a/gsoc/forms.py b/gsoc/forms.py index 15dcf836..294aed86 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -1,7 +1,15 @@ from PIL import Image -from .models import (UserDetails, UserProfile, RegLink, BlogPostDueDate, Event, - SubOrgDetails, SubOrg, GsocEndDate) +from .models import ( + UserDetails, + UserProfile, + RegLink, + BlogPostDueDate, + Event, + SubOrgDetails, + SubOrg, + GsocEndDate, +) from django import forms from django.core.exceptions import ValidationError @@ -11,119 +19,134 @@ class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile fields = ( - 'role', - 'suborg_full_name', - 'gsoc_year', - 'accepted_proposal_pdf', - 'app_config', - 'hidden' - ) - widgets = { - 'app_config': forms.Select(), - } + "role", + "suborg_full_name", + "gsoc_year", + "accepted_proposal_pdf", + "app_config", + "hidden", + ) + widgets = {"app_config": forms.Select()} class ProposalUploadForm(forms.ModelForm): class Meta: model = UserProfile - fields = ['accepted_proposal_pdf'] + fields = ["accepted_proposal_pdf"] class UserDetailsForm(forms.ModelForm): class Meta: model = UserDetails - fields = ('deactivation_date',) + fields = ("deactivation_date",) class RegLinkForm(forms.ModelForm): class Meta: model = RegLink - fields = ('email', 'user_role', 'user_suborg', 'user_gsoc_year') + fields = ("email", "user_role", "user_suborg", "user_gsoc_year") class BlogPostDueDateForm(forms.ModelForm): class Meta: model = BlogPostDueDate - fields = ('title', 'date', 'category') + fields = ("title", "date", "category") class EventForm(forms.ModelForm): class Meta: model = Event - fields = ('title', 'start_date', 'end_date') + fields = ("title", "start_date", "end_date") class GsocEndDateForm(forms.ModelForm): class Meta: model = GsocEndDate - fields = ('date', ) + fields = ("date",) class SubOrgApplicationForm(forms.ModelForm): class Meta: model = SubOrgDetails - exclude = ('accepted', 'last_message', 'changed', 'last_reviewed_at', 'last_reviewed_by', - 'created_at', 'updated_at', 'reason_for_participation', - 'mentors_student_engagement', 'students_on_schedule', - 'students_involvement_gsoc', 'students_involvement_after') + exclude = ( + "accepted", + "last_message", + "changed", + "last_reviewed_at", + "last_reviewed_by", + "created_at", + "updated_at", + "reason_for_participation", + "mentors_student_engagement", + "students_on_schedule", + "students_involvement_gsoc", + "students_involvement_after", + ) widgets = { - 'suborg_admin_email': forms.HiddenInput(), - 'gsoc_year': forms.HiddenInput(), - 'past_years': forms.CheckboxSelectMultiple(), - 'applied_but_not_selected': forms.CheckboxSelectMultiple(), + "suborg_admin_email": forms.HiddenInput(), + "gsoc_year": forms.HiddenInput(), + "past_years": forms.CheckboxSelectMultiple(), + "applied_but_not_selected": forms.CheckboxSelectMultiple(), } def clean(self): cd = self.cleaned_data - past_exp = cd.get('past_gsoc_experience') - past_years = cd.get('past_years').all() - applied_not_selected = cd.get('applied_but_not_selected').all() - suborg_name = cd.get('suborg_name') - suborg = cd.get('suborg') - logo = cd.get('logo') + past_exp = cd.get("past_gsoc_experience") + past_years = cd.get("past_years").all() + applied_not_selected = cd.get("applied_but_not_selected").all() + suborg_name = cd.get("suborg_name") + suborg = cd.get("suborg") + logo = cd.get("logo") im = Image.open(logo) width, height = im.size contact = [ - cd.get('chat', None), - cd.get('mailing_list', None), - cd.get('twitter_url', None), - cd.get('blog_url', None), - cd.get('link', None) + cd.get("chat", None), + cd.get("mailing_list", None), + cd.get("twitter_url", None), + cd.get("blog_url", None), + cd.get("link", None), ] contact = list(filter(lambda a: a is not None, contact)) if width != 256 or height != 256: - raise ValidationError('The image should of size 256 x 256 pixels') + raise ValidationError("The image should of size 256 x 256 pixels") if not suborg and suborg_name: suborg = SubOrg.objects.filter(suborg_name=suborg_name) if len(suborg) > 0: - cd['suborg'] = suborg.first() + cd["suborg"] = suborg.first() elif suborg and not suborg_name: - cd['suborg_name'] = suborg.suborg_name + cd["suborg_name"] = suborg.suborg_name elif suborg and suborg_name: if suborg.suborg_name != suborg_name: - raise ValidationError('Inconsistent suborg field values') + raise ValidationError("Inconsistent suborg field values") else: - raise ValidationError('Either suborg should be selected or ' - 'the suborg name') + raise ValidationError( + "Either suborg should be selected or " "the suborg name" + ) if len(contact) < 1: - raise ValidationError('At least one out of the five contact ' - 'details should be entered') + raise ValidationError( + "At least one out of the five contact " "details should be entered" + ) if past_exp and len(past_years) == 0: - raise ValidationError('No past years mentioned but past experience selected') + raise ValidationError( + "No past years mentioned but past experience selected" + ) elif not past_exp and len(past_years) > 0: - raise ValidationError('Past years mentioned but past experience not selected') + raise ValidationError( + "Past years mentioned but past experience not selected" + ) for _y in applied_not_selected: for y in past_years: if y == _y: - raise ValidationError('Applied but not selected year can not' - ' match with past years') + raise ValidationError( + "Applied but not selected year can not" " match with past years" + ) return cd diff --git a/gsoc/management/commands/googleapiauth.py b/gsoc/management/commands/googleapiauth.py index ac28385e..cdd8e3cc 100644 --- a/gsoc/management/commands/googleapiauth.py +++ b/gsoc/management/commands/googleapiauth.py @@ -9,17 +9,17 @@ class Command(BaseCommand): - help = 'Generates the api token pickle for authenticating Google API.' - requires_system_checks = False # for debugging + help = "Generates the api token pickle for authenticating Google API." + requires_system_checks = False # for debugging def handle(self, *args, **options): - if os.path.exists(os.path.join(BASE_DIR, 'google_api_token.pickle')): - os.remove(os.path.join(BASE_DIR, 'google_api_token.pickle')) + if os.path.exists(os.path.join(BASE_DIR, "google_api_token.pickle")): + os.remove(os.path.join(BASE_DIR, "google_api_token.pickle")) flow = InstalledAppFlow.from_client_config( GOOGLE_API_CLIENT_CONFIG, GOOGLE_API_SCOPES ) creds = flow.run_console() - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'wb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "wb") as token: pickle.dump(creds, token) diff --git a/gsoc/management/commands/runcron.py b/gsoc/management/commands/runcron.py index 15ee89f5..66fcb70b 100644 --- a/gsoc/management/commands/runcron.py +++ b/gsoc/management/commands/runcron.py @@ -10,37 +10,37 @@ class Command(BaseCommand): - help = 'Run the cron command to process items such as sending scheduled emails etc.' - tasks = ['build_items', 'process_items'] - requires_system_checks = False # for debugging + help = "Run the cron command to process items such as sending scheduled emails etc." + tasks = ["build_items", "process_items"] + requires_system_checks = False # for debugging # cleanup sessions # Session.objects.all().delete() def add_arguments(self, parser): parser.add_argument( - 'task', - nargs='?', + "task", + nargs="?", choices=self.tasks, type=str, - help='The task which will be started' - ) + help="The task which will be started", + ) parser.add_argument( - '-t', - '--timeout', - nargs='?', + "-t", + "--timeout", + nargs="?", default=settings.RUNCRON_TIMEOUT, type=int, - help='Set timeout' - ) + help="Set timeout", + ) parser.add_argument( - '-n', - '--num_workers', - nargs='?', + "-n", + "--num_workers", + nargs="?", default=settings.RUNCRON_NUM_WORKERS, type=int, - help='Set number of workers' - ) + help="Set number of workers", + ) def build_items(self, options): # build tasks @@ -50,99 +50,122 @@ def build_items(self, options): builders = x | y if len(builders) is 0: - self.stdout.write(self.style.SUCCESS('No build tasks'), ending='\n') + self.stdout.write(self.style.SUCCESS("No build tasks"), ending="\n") else: for builder in builders: - self.stdout.write('Running build task {}:{}' - .format(builder.category, builder.pk), ending='\n') + self.stdout.write( + "Running build task {}:{}".format(builder.category, builder.pk), + ending="\n", + ) err = getattr(build_tasks, builder.category)(builder) if not err: - self.stdout.write(self.style - .SUCCESS('Finished build task {}:{}' - .format(builder.category, builder.pk)), - ending='\n') + self.stdout.write( + self.style.SUCCESS( + "Finished build task {}:{}".format( + builder.category, builder.pk + ) + ), + ending="\n", + ) builder.built = True builder.save() else: self.stdout.write( self.style.ERROR( - 'Build task {}:{} failed with error: {}' .format( - builder.category, - builder.pk, - err)), - ending='\n') + "Build task {}:{} failed with error: {}".format( + builder.category, builder.pk, err + ) + ), + ending="\n", + ) builder.built = False builder.last_error = err builder.save() - send_mail(settings.ADMINS, - 'Exception on runcron build_items', - 'cron_error.html', - { - 'message': err, - 'time': today, - }) + send_mail( + settings.ADMINS, + "Exception on runcron build_items", + "cron_error.html", + {"message": err, "time": today}, + ) def handle_process(self, scheduler): today = timezone.now() - self.stdout.write('Running command {}:{}' - .format(scheduler.command, scheduler.id), ending='\n') + self.stdout.write( + "Running command {}:{}".format(scheduler.command, scheduler.id), ending="\n" + ) err = getattr(commands, scheduler.command)(scheduler) if not err: - self.stdout.write(self.style - .SUCCESS('Finished command {}:{}' - .format(scheduler.command, scheduler.id)), - ending='\n') + self.stdout.write( + self.style.SUCCESS( + "Finished command {}:{}".format(scheduler.command, scheduler.id) + ), + ending="\n", + ) scheduler.success = True scheduler.save() else: self.stdout.write( self.style.ERROR( - 'Command {}:{} failed with error: {}' .format( - scheduler.command, - scheduler.id, - err)), - ending='\n') + "Command {}:{} failed with error: {}".format( + scheduler.command, scheduler.id, err + ) + ), + ending="\n", + ) scheduler.success = False scheduler.last_error = err scheduler.save() - send_mail(settings.ADMINS, - 'Exception on runcron process_items', - 'cron_error.html', - { - 'message': err, - 'time': today, - }) + send_mail( + settings.ADMINS, + "Exception on runcron process_items", + "cron_error.html", + {"message": err, "time": today}, + ) def process_items(self, options): today = timezone.now() # custom handlers - irc_schedulers_1 = Scheduler.objects.filter(success=None, command='send_irc_msg', - activation_date=None) - irc_schedulers_2 = Scheduler.objects.filter(success=None, command='send_irc_msg', - activation_date__lte=today) + irc_schedulers_1 = Scheduler.objects.filter( + success=None, command="send_irc_msg", activation_date=None + ) + irc_schedulers_2 = Scheduler.objects.filter( + success=None, command="send_irc_msg", activation_date__lte=today + ) irc_schedulers = irc_schedulers_1 | irc_schedulers_2 if len(irc_schedulers) is 0: - self.stdout.write(self.style.SUCCESS('No scheduled send_irc_msg tasks'), ending='\n') + self.stdout.write( + self.style.SUCCESS("No scheduled send_irc_msg tasks"), ending="\n" + ) else: - self.stdout.write(self.style.SUCCESS('Sending {} scheduled irc message(s)' - .format(len(irc_schedulers))), ending='\n') + self.stdout.write( + self.style.SUCCESS( + "Sending {} scheduled irc message(s)".format(len(irc_schedulers)) + ), + ending="\n", + ) commands.send_irc_msgs(irc_schedulers) - self.stdout.write(self.style.SUCCESS('Sent {} irc message(s)' - .format(len(irc_schedulers))), ending='\n') - - template_schedulers_1 = Scheduler.objects.filter(success=None, - command='update_site_template', - activation_date=None).all() - template_schedulers_2 = Scheduler.objects.filter(success=None, - command='update_site_template', - activation_date__lte=today).all() + self.stdout.write( + self.style.SUCCESS( + "Sent {} irc message(s)".format(len(irc_schedulers)) + ), + ending="\n", + ) + + template_schedulers_1 = Scheduler.objects.filter( + success=None, command="update_site_template", activation_date=None + ).all() + template_schedulers_2 = Scheduler.objects.filter( + success=None, command="update_site_template", activation_date__lte=today + ).all() template_schedulers = template_schedulers_1 | template_schedulers_2 if len(template_schedulers) is 0: - self.stdout.write(self.style.SUCCESS('No scheduled update_site_template tasks'), - ending='\n') + self.stdout.write( + self.style.SUCCESS("No scheduled update_site_template tasks"), + ending="\n", + ) else: for scheduler in template_schedulers: self.handle_process(scheduler) @@ -155,17 +178,21 @@ def process_items(self, options): threads = [] if len(schedulers) is not 0: try: - executor = ThreadPoolExecutor(max_workers=options['num_workers']) - executor.map(self.handle_process, schedulers, timeout=options['timeout']) + executor = ThreadPoolExecutor(max_workers=options["num_workers"]) + executor.map( + self.handle_process, schedulers, timeout=options["timeout"] + ) except TimeoutError as e: - self.stdout.write(self.style.ERROR('Time limit exceeded'), ending='\n') + self.stdout.write(self.style.ERROR("Time limit exceeded"), ending="\n") else: - self.stdout.write(self.style.SUCCESS('No more scheduled tasks'), ending='\n') + self.stdout.write( + self.style.SUCCESS("No more scheduled tasks"), ending="\n" + ) def handle(self, *args, **options): - if options['task']: - getattr(self, options['task'])(options) + if options["task"]: + getattr(self, options["task"])(options) else: self.build_items(options) self.process_items(options) diff --git a/gsoc/migrations/0001_initial.py b/gsoc/migrations/0001_initial.py index dcf1877f..aca09ce7 100644 --- a/gsoc/migrations/0001_initial.py +++ b/gsoc/migrations/0001_initial.py @@ -9,32 +9,63 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] + dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] operations = [ migrations.CreateModel( - name='GsocYear', + name="GsocYear", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('gsoc_year', models.IntegerField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("gsoc_year", models.IntegerField()), ], ), migrations.CreateModel( - name='SubOrg', + name="SubOrg", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('suborg_name', models.CharField(max_length=80)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("suborg_name", models.CharField(max_length=80)), ], ), migrations.CreateModel( - name='UserProfile', + name="UserProfile", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('gsoc_year', models.ManyToManyField(blank=True, to='gsoc.GsocYear')), - ('suborg_full_name', models.ManyToManyField(blank=True, to='gsoc.SubOrg')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("gsoc_year", models.ManyToManyField(blank=True, to="gsoc.GsocYear")), + ( + "suborg_full_name", + models.ManyToManyField(blank=True, to="gsoc.SubOrg"), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/gsoc/migrations/0002_scheduler.py b/gsoc/migrations/0002_scheduler.py index f916fb0a..3799d03a 100644 --- a/gsoc/migrations/0002_scheduler.py +++ b/gsoc/migrations/0002_scheduler.py @@ -5,19 +5,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0001_initial'), - ] + dependencies = [("gsoc", "0001_initial")] operations = [ migrations.CreateModel( - name='Scheduler', + name="Scheduler", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('command', models.CharField(choices=[('send_emails', 'send_emails'), ('send_irc_msgs', 'send_irc_msgs')], max_length=20)), - ('data', models.CharField(max_length=1000)), - ('success', models.BooleanField(default=False)), - ('created', models.DateTimeField(auto_now_add=True)), + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "command", + models.CharField( + choices=[ + ("send_emails", "send_emails"), + ("send_irc_msgs", "send_irc_msgs"), + ], + max_length=20, + ), + ), + ("data", models.CharField(max_length=1000)), + ("success", models.BooleanField(default=False)), + ("created", models.DateTimeField(auto_now_add=True)), ], - ), + ) ] diff --git a/gsoc/migrations/0003_auto_20190219_0320.py b/gsoc/migrations/0003_auto_20190219_0320.py index 5be4d7c0..6a7151dd 100644 --- a/gsoc/migrations/0003_auto_20190219_0320.py +++ b/gsoc/migrations/0003_auto_20190219_0320.py @@ -5,19 +5,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0002_scheduler'), - ] + dependencies = [("gsoc", "0002_scheduler")] operations = [ migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg')], max_length=20), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ], + max_length=20, + ), ), migrations.AlterField( - model_name='scheduler', - name='data', - field=models.TextField(), + model_name="scheduler", name="data", field=models.TextField() ), ] diff --git a/gsoc/migrations/0004_auto_20190219_1215.py b/gsoc/migrations/0004_auto_20190219_1215.py index 5c49b5cd..33159211 100644 --- a/gsoc/migrations/0004_auto_20190219_1215.py +++ b/gsoc/migrations/0004_auto_20190219_1215.py @@ -5,19 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0003_auto_20190219_0320'), - ] + dependencies = [("gsoc", "0003_auto_20190219_0320")] operations = [ migrations.AddField( - model_name='scheduler', - name='last_error', + model_name="scheduler", + name="last_error", field=models.CharField(default=None, max_length=100, null=True), ), migrations.AlterField( - model_name='scheduler', - name='success', + model_name="scheduler", + name="success", field=models.BooleanField(default=None, null=True), ), ] diff --git a/gsoc/migrations/0005_auto_20190219_1253.py b/gsoc/migrations/0005_auto_20190219_1253.py index 71dbdf44..4af7f68f 100644 --- a/gsoc/migrations/0005_auto_20190219_1253.py +++ b/gsoc/migrations/0005_auto_20190219_1253.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0004_auto_20190219_1215'), - ] + dependencies = [("gsoc", "0004_auto_20190219_1215")] operations = [ migrations.AlterField( - model_name='scheduler', - name='last_error', + model_name="scheduler", + name="last_error", field=models.TextField(default=None, null=True), - ), + ) ] diff --git a/gsoc/migrations/0006_auto_20190219_1301.py b/gsoc/migrations/0006_auto_20190219_1301.py index 007e1f7b..fa1597a5 100644 --- a/gsoc/migrations/0006_auto_20190219_1301.py +++ b/gsoc/migrations/0006_auto_20190219_1301.py @@ -5,14 +5,10 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0005_auto_20190219_1253'), - ] + dependencies = [("gsoc", "0005_auto_20190219_1253")] operations = [ migrations.AlterField( - model_name='scheduler', - name='success', - field=models.BooleanField(null=True), - ), + model_name="scheduler", name="success", field=models.BooleanField(null=True) + ) ] diff --git a/gsoc/migrations/0007_auto_20190305_0853.py b/gsoc/migrations/0007_auto_20190305_0853.py index e3518156..42d81a58 100644 --- a/gsoc/migrations/0007_auto_20190305_0853.py +++ b/gsoc/migrations/0007_auto_20190305_0853.py @@ -7,37 +7,45 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0006_auto_20190219_1301'), - ] + dependencies = [("gsoc", "0006_auto_20190219_1301")] operations = [ migrations.AddField( - model_name='userprofile', - name='role', - field=models.IntegerField(choices=[(0, 'Others'), (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student')], default=0), - ), - migrations.RemoveField( - model_name='userprofile', - name='gsoc_year', + model_name="userprofile", + name="role", + field=models.IntegerField( + choices=[ + (0, "Others"), + (1, "Suborg Admin"), + (2, "Mentor"), + (3, "Student"), + ], + default=0, + ), ), + migrations.RemoveField(model_name="userprofile", name="gsoc_year"), migrations.AddField( - model_name='userprofile', - name='gsoc_year', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.GsocYear'), - ), - migrations.RemoveField( - model_name='userprofile', - name='suborg_full_name', + model_name="userprofile", + name="gsoc_year", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.GsocYear", + ), ), + migrations.RemoveField(model_name="userprofile", name="suborg_full_name"), migrations.AddField( - model_name='userprofile', - name='suborg_full_name', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.SubOrg'), + model_name="userprofile", + name="suborg_full_name", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.CASCADE, to="gsoc.SubOrg" + ), ), migrations.AlterField( - model_name='userprofile', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="userprofile", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/gsoc/migrations/0008_auto_20190322_0318.py b/gsoc/migrations/0008_auto_20190322_0318.py index f7134c33..429d1a4a 100644 --- a/gsoc/migrations/0008_auto_20190322_0318.py +++ b/gsoc/migrations/0008_auto_20190322_0318.py @@ -9,34 +9,53 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('gsoc', '0007_auto_20190305_0853'), + ("gsoc", "0007_auto_20190305_0853"), ] operations = [ migrations.CreateModel( - name='UserDetails', + name="UserDetails", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('deactivation_date', models.DateTimeField(blank=True, null=True)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("deactivation_date", models.DateTimeField(blank=True, null=True)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - options={ - 'verbose_name_plural': 'User details', - }, + options={"verbose_name_plural": "User details"}, ), migrations.AddField( - model_name='scheduler', - name='activation_date', + model_name="scheduler", + name="activation_date", field=models.DateTimeField(blank=True, null=True), ), migrations.AddField( - model_name='userprofile', - name='accepted_proposal_pdf', - field=models.FileField(blank=True, null=True, upload_to=''), + model_name="userprofile", + name="accepted_proposal_pdf", + field=models.FileField(blank=True, null=True, upload_to=""), ), migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user')], max_length=20), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("deactivate_user", "deactivate_user"), + ], + max_length=20, + ), ), ] diff --git a/gsoc/migrations/0009_reglink.py b/gsoc/migrations/0009_reglink.py index 1f8f830e..9fa18cc1 100644 --- a/gsoc/migrations/0009_reglink.py +++ b/gsoc/migrations/0009_reglink.py @@ -6,18 +6,29 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0008_auto_20190322_0318'), - ] + dependencies = [("gsoc", "0008_auto_20190322_0318")] operations = [ migrations.CreateModel( - name='RegLink', + name="RegLink", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_used', models.BooleanField(default=False, editable=False)), - ('reglink_id', models.CharField(default=gsoc.models.gen_uuid_str, editable=False, max_length=36)), - ('created_at', models.DateTimeField(auto_now_add=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_used", models.BooleanField(default=False, editable=False)), + ( + "reglink_id", + models.CharField( + default=gsoc.models.gen_uuid_str, editable=False, max_length=36 + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), ], - ), + ) ] diff --git a/gsoc/migrations/0010_auto_20190324_2216.py b/gsoc/migrations/0010_auto_20190324_2216.py index 2bcafb26..59fe212c 100644 --- a/gsoc/migrations/0010_auto_20190324_2216.py +++ b/gsoc/migrations/0010_auto_20190324_2216.py @@ -6,24 +6,37 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0009_reglink'), - ] + dependencies = [("gsoc", "0009_reglink")] operations = [ migrations.AddField( - model_name='reglink', - name='user_gsoc_year', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.GsocYear'), + model_name="reglink", + name="user_gsoc_year", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.GsocYear", + ), ), migrations.AddField( - model_name='reglink', - name='user_role', - field=models.IntegerField(choices=[(0, 'Others'), (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student')], default=0, null=True), + model_name="reglink", + name="user_role", + field=models.IntegerField( + choices=[ + (0, "Others"), + (1, "Suborg Admin"), + (2, "Mentor"), + (3, "Student"), + ], + default=0, + null=True, + ), ), migrations.AddField( - model_name='reglink', - name='user_suborg', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.SubOrg'), + model_name="reglink", + name="user_suborg", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.CASCADE, to="gsoc.SubOrg" + ), ), ] diff --git a/gsoc/migrations/0011_userprofile_app_config.py b/gsoc/migrations/0011_userprofile_app_config.py index 62e8b545..5751c418 100644 --- a/gsoc/migrations/0011_userprofile_app_config.py +++ b/gsoc/migrations/0011_userprofile_app_config.py @@ -8,14 +8,21 @@ class Migration(migrations.Migration): dependencies = [ - ('aldryn_newsblog', '0016_auto_20180329_1417'), - ('gsoc', '0010_auto_20190324_2216'), + ("aldryn_newsblog", "0016_auto_20180329_1417"), + ("gsoc", "0010_auto_20190324_2216"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='app_config', - field=aldryn_apphooks_config.fields.AppHookConfigField(blank=True, help_text='When selecting a value, the form is reloaded to get the updated default', null=True, on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.NewsBlogConfig', verbose_name='Section'), - ), + model_name="userprofile", + name="app_config", + field=aldryn_apphooks_config.fields.AppHookConfigField( + blank=True, + help_text="When selecting a value, the form is reloaded to get the updated default", + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aldryn_newsblog.NewsBlogConfig", + verbose_name="Section", + ), + ) ] diff --git a/gsoc/migrations/0012_auto_20190418_2214.py b/gsoc/migrations/0012_auto_20190418_2214.py index ace12772..6bd71c6e 100644 --- a/gsoc/migrations/0012_auto_20190418_2214.py +++ b/gsoc/migrations/0012_auto_20190418_2214.py @@ -5,19 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0011_userprofile_app_config'), - ] + dependencies = [("gsoc", "0011_userprofile_app_config")] operations = [ migrations.AddField( - model_name='userprofile', - name='hidden', + model_name="userprofile", + name="hidden", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='scheduler', - name='last_error', + model_name="scheduler", + name="last_error", field=models.TextField(blank=True, default=None, null=True), ), ] diff --git a/gsoc/migrations/0013_pagenotification.py b/gsoc/migrations/0013_pagenotification.py index 5172564b..04b2eba6 100644 --- a/gsoc/migrations/0013_pagenotification.py +++ b/gsoc/migrations/0013_pagenotification.py @@ -9,19 +9,47 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('cms', '0022_auto_20180620_1551'), - ('gsoc', '0012_auto_20190418_2214'), + ("cms", "0022_auto_20180620_1551"), + ("gsoc", "0012_auto_20190418_2214"), ] operations = [ migrations.CreateModel( - name='PageNotification', + name="PageNotification", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('message', models.TextField()), - ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='cms.Page')), - ('published_page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications_for_published', to='cms.Page')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("message", models.TextField()), + ( + "page", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications", + to="cms.Page", + ), + ), + ( + "published_page", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_for_published", + to="cms.Page", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - ), + ) ] diff --git a/gsoc/migrations/0014_auto_20190420_0140.py b/gsoc/migrations/0014_auto_20190420_0140.py index 26c8f6f3..715a027f 100644 --- a/gsoc/migrations/0014_auto_20190420_0140.py +++ b/gsoc/migrations/0014_auto_20190420_0140.py @@ -8,39 +8,63 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0013_pagenotification'), - ] + dependencies = [("gsoc", "0013_pagenotification")] operations = [ migrations.CreateModel( - name='AddUserLog', + name="AddUserLog", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('log_id', models.CharField(default=gsoc.models.gen_uuid_str, max_length=36)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "log_id", + models.CharField(default=gsoc.models.gen_uuid_str, max_length=36), + ), ], options={ - 'verbose_name': 'Add Users(The invites will be sent to the emails on save)', - 'verbose_name_plural': 'Add Users(The invites will be sent to the emails on save)', + "verbose_name": "Add Users(The invites will be sent to the emails on save)", + "verbose_name_plural": "Add Users(The invites will be sent to the emails on save)", }, ), migrations.AlterModelOptions( - name='gsocyear', - options={'ordering': ['-gsoc_year']}, + name="gsocyear", options={"ordering": ["-gsoc_year"]} ), migrations.AddField( - model_name='reglink', - name='email', - field=models.CharField(default='', max_length=300, validators=[django.core.validators.EmailValidator()]), + model_name="reglink", + name="email", + field=models.CharField( + default="", + max_length=300, + validators=[django.core.validators.EmailValidator()], + ), ), migrations.AddField( - model_name='reglink', - name='scheduler', - field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Scheduler'), + model_name="reglink", + name="scheduler", + field=models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.Scheduler", + ), ), migrations.AddField( - model_name='reglink', - name='adduserlog', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reglinks', to='gsoc.AddUserLog'), + model_name="reglink", + name="adduserlog", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="reglinks", + to="gsoc.AddUserLog", + ), ), ] diff --git a/gsoc/migrations/0015_auto_20190423_1031.py b/gsoc/migrations/0015_auto_20190423_1031.py index 06034677..a97787c4 100644 --- a/gsoc/migrations/0015_auto_20190423_1031.py +++ b/gsoc/migrations/0015_auto_20190423_1031.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0014_auto_20190420_0140'), - ] + dependencies = [("gsoc", "0014_auto_20190420_0140")] operations = [ migrations.AlterField( - model_name='userprofile', - name='accepted_proposal_pdf', - field=models.FileField(blank=True, null=True, upload_to='proposals/'), - ), + model_name="userprofile", + name="accepted_proposal_pdf", + field=models.FileField(blank=True, null=True, upload_to="proposals/"), + ) ] diff --git a/gsoc/migrations/0016_comment.py b/gsoc/migrations/0016_comment.py index adbfc319..e4638c28 100644 --- a/gsoc/migrations/0016_comment.py +++ b/gsoc/migrations/0016_comment.py @@ -9,26 +9,50 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('aldryn_newsblog', '0016_auto_20180329_1417'), - ('gsoc', '0015_auto_20190423_1031'), + ("aldryn_newsblog", "0016_auto_20180329_1417"), + ("gsoc", "0015_auto_20190423_1031"), ] operations = [ migrations.CreateModel( - name='Comment', + name="Comment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, - serialize=False, verbose_name='ID')), - ('username', models.CharField(max_length=50)), - ('content', models.TextField()), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, - to='aldryn_newsblog.Article')), - ('parent', models.ForeignKey(null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name='replies', to='gsoc.Comment')), - ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("username", models.CharField(max_length=50)), + ("content", models.TextField()), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "article", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aldryn_newsblog.Article", + ), + ), + ( + "parent", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="replies", + to="gsoc.Comment", + ), + ), + ( + "user", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - ), + ) ] diff --git a/gsoc/migrations/0017_userprofile_proposal_confirmed.py b/gsoc/migrations/0017_userprofile_proposal_confirmed.py index 427299ff..7ae7b47a 100644 --- a/gsoc/migrations/0017_userprofile_proposal_confirmed.py +++ b/gsoc/migrations/0017_userprofile_proposal_confirmed.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0016_comment'), - ] + dependencies = [("gsoc", "0016_comment")] operations = [ migrations.AddField( - model_name='userprofile', - name='proposal_confirmed', + model_name="userprofile", + name="proposal_confirmed", field=models.BooleanField(default=False), - ), + ) ] diff --git a/gsoc/migrations/0018_auto_20190526_1519.py b/gsoc/migrations/0018_auto_20190526_1519.py index 7671885e..bec93d14 100644 --- a/gsoc/migrations/0018_auto_20190526_1519.py +++ b/gsoc/migrations/0018_auto_20190526_1519.py @@ -6,30 +6,39 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0017_userprofile_proposal_confirmed'), - ] + dependencies = [("gsoc", "0017_userprofile_proposal_confirmed")] operations = [ migrations.AlterModelOptions( - name='adduserlog', - options={'verbose_name': 'Add Users (The invites will be sent to the emails on save)', - 'verbose_name_plural': 'Add Users (The invites will be sent to the emails on save)'}, + name="adduserlog", + options={ + "verbose_name": "Add Users (The invites will be sent to the emails on save)", + "verbose_name_plural": "Add Users (The invites will be sent to the emails on save)", + }, ), migrations.AddField( - model_name='reglink', - name='reminder', - field=models.ForeignKey(blank=True, editable=False, null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name='reglinks', to='gsoc.Scheduler'), + model_name="reglink", + name="reminder", + field=models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="reglinks", + to="gsoc.Scheduler", + ), ), migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), - ('send_irc_msg', 'send_irc_msg'), - ('deactivate_user', 'deactivate_user'), - ('send_reg_reminder', 'send_reg_reminder')], - max_length=20), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("deactivate_user", "deactivate_user"), + ("send_reg_reminder", "send_reg_reminder"), + ], + max_length=20, + ), ), ] diff --git a/gsoc/migrations/0019_auto_20190529_1656.py b/gsoc/migrations/0019_auto_20190529_1656.py index 33c2f5c1..ec4588ba 100644 --- a/gsoc/migrations/0019_auto_20190529_1656.py +++ b/gsoc/migrations/0019_auto_20190529_1656.py @@ -6,66 +6,110 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0018_auto_20190526_1519'), - ] + dependencies = [("gsoc", "0018_auto_20190526_1519")] operations = [ migrations.CreateModel( - name='BlogPostDueDate', + name="BlogPostDueDate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255)), - ('date', models.DateTimeField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=255)), + ("date", models.DateTimeField()), ], ), migrations.CreateModel( - name='Builder', + name="Builder", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('category', models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders')], max_length=40)), - ('activation_date', models.DateTimeField(blank=True, null=True)), - ('built', models.BooleanField(default=False)), - ('data', models.TextField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "category", + models.CharField( + choices=[ + ("build_pre_blog_reminders", "build_pre_blog_reminders"), + ("build_post_blog_reminders", "build_post_blog_reminders"), + ], + max_length=40, + ), + ), + ("activation_date", models.DateTimeField(blank=True, null=True)), + ("built", models.BooleanField(default=False)), + ("data", models.TextField()), ], ), migrations.AlterModelOptions( - name='suborg', - options={'ordering': ['suborg_name']}, + name="suborg", options={"ordering": ["suborg_name"]} ), migrations.AddField( - model_name='userprofile', - name='current_blog_count', + model_name="userprofile", + name="current_blog_count", field=models.IntegerField(default=0), ), migrations.AddField( - model_name='userprofile', - name='reminder_disabled', + model_name="userprofile", + name="reminder_disabled", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter')], max_length=40), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("deactivate_user", "deactivate_user"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ], + max_length=40, + ), ), migrations.AddField( - model_name='blogpostduedate', - name='add_counter_scheduler', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Scheduler'), + model_name="blogpostduedate", + name="add_counter_scheduler", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.Scheduler", + ), ), migrations.AddField( - model_name='blogpostduedate', - name='gsoc_year', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gsoc.GsocYear'), + model_name="blogpostduedate", + name="gsoc_year", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="gsoc.GsocYear" + ), ), migrations.AddField( - model_name='blogpostduedate', - name='post_blog_reminder_builder', - field=models.ManyToManyField(blank=True, null=True, to='gsoc.Builder'), + model_name="blogpostduedate", + name="post_blog_reminder_builder", + field=models.ManyToManyField(blank=True, null=True, to="gsoc.Builder"), ), migrations.AddField( - model_name='blogpostduedate', - name='pre_blog_reminder_builder', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pre', to='gsoc.Builder'), + model_name="blogpostduedate", + name="pre_blog_reminder_builder", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="pre", + to="gsoc.Builder", + ), ), ] diff --git a/gsoc/migrations/0020_auto_20190529_1759.py b/gsoc/migrations/0020_auto_20190529_1759.py index ce43ca1f..1fa4c05a 100644 --- a/gsoc/migrations/0020_auto_20190529_1759.py +++ b/gsoc/migrations/0020_auto_20190529_1759.py @@ -6,14 +6,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0019_auto_20190529_1656'), - ] + dependencies = [("gsoc", "0019_auto_20190529_1656")] operations = [ migrations.AlterField( - model_name='blogpostduedate', - name='gsoc_year', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.GsocYear'), - ), + model_name="blogpostduedate", + name="gsoc_year", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.GsocYear", + ), + ) ] diff --git a/gsoc/migrations/0021_auto_20190530_0622.py b/gsoc/migrations/0021_auto_20190530_0622.py index 7603dc00..b5836536 100644 --- a/gsoc/migrations/0021_auto_20190530_0622.py +++ b/gsoc/migrations/0021_auto_20190530_0622.py @@ -6,39 +6,47 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0020_auto_20190529_1759'), - ] + dependencies = [("gsoc", "0020_auto_20190529_1759")] operations = [ migrations.CreateModel( - name='Timeline', + name="Timeline", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('gsoc_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gsoc.GsocYear')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "gsoc_year", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="gsoc.GsocYear" + ), + ), ], ), - migrations.RemoveField( - model_name='blogpostduedate', - name='gsoc_year', - ), - migrations.RemoveField( - model_name='blogpostduedate', - name='title', - ), + migrations.RemoveField(model_name="blogpostduedate", name="gsoc_year"), + migrations.RemoveField(model_name="blogpostduedate", name="title"), migrations.AlterField( - model_name='blogpostduedate', - name='date', - field=models.DateField(), + model_name="blogpostduedate", name="date", field=models.DateField() ), migrations.AlterField( - model_name='blogpostduedate', - name='post_blog_reminder_builder', - field=models.ManyToManyField(blank=True, to='gsoc.Builder'), + model_name="blogpostduedate", + name="post_blog_reminder_builder", + field=models.ManyToManyField(blank=True, to="gsoc.Builder"), ), migrations.AddField( - model_name='blogpostduedate', - name='timeline', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Timeline'), + model_name="blogpostduedate", + name="timeline", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.Timeline", + ), ), ] diff --git a/gsoc/migrations/0022_articlereview.py b/gsoc/migrations/0022_articlereview.py index 50da4971..e8d91e93 100644 --- a/gsoc/migrations/0022_articlereview.py +++ b/gsoc/migrations/0022_articlereview.py @@ -8,19 +8,42 @@ class Migration(migrations.Migration): dependencies = [ - ('aldryn_newsblog', '0016_auto_20180329_1417'), + ("aldryn_newsblog", "0016_auto_20180329_1417"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('gsoc', '0021_auto_20190530_0622'), + ("gsoc", "0021_auto_20190530_0622"), ] operations = [ migrations.CreateModel( - name='ArticleReview', + name="ArticleReview", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_reviewed', models.BooleanField(default=False)), - ('article', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), - ('last_reviewed_by', models.ForeignKey(blank=True, limit_choices_to={'is_superuser': True}, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_reviewed", models.BooleanField(default=False)), + ( + "article", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to="aldryn_newsblog.Article", + ), + ), + ( + "last_reviewed_by", + models.ForeignKey( + blank=True, + limit_choices_to={"is_superuser": True}, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - ), + ) ] diff --git a/gsoc/migrations/0023_auto_20190604_1310.py b/gsoc/migrations/0023_auto_20190604_1310.py index a58abb4a..0954f052 100644 --- a/gsoc/migrations/0023_auto_20190604_1310.py +++ b/gsoc/migrations/0023_auto_20190604_1310.py @@ -5,14 +5,10 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0022_articlereview'), - ] + dependencies = [("gsoc", "0022_articlereview")] operations = [ migrations.AlterField( - model_name='comment', - name='content', - field=models.CharField(max_length=255), - ), + model_name="comment", name="content", field=models.CharField(max_length=255) + ) ] diff --git a/gsoc/migrations/0024_auto_20190607_0428.py b/gsoc/migrations/0024_auto_20190607_0428.py index 00eb608d..bf6eb69e 100644 --- a/gsoc/migrations/0024_auto_20190607_0428.py +++ b/gsoc/migrations/0024_auto_20190607_0428.py @@ -6,23 +6,36 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0023_auto_20190604_1310'), - ] + dependencies = [("gsoc", "0023_auto_20190604_1310")] operations = [ migrations.CreateModel( - name='Event', + name="Event", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=100)), - ('start_date', models.DateField()), - ('end_date', models.DateField(blank=True, null=True)), - ('timeline', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Timeline')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=100)), + ("start_date", models.DateField()), + ("end_date", models.DateField(blank=True, null=True)), + ( + "timeline", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.Timeline", + ), + ), ], ), migrations.AlterModelOptions( - name='blogpostduedate', - options={'ordering': ['date']}, + name="blogpostduedate", options={"ordering": ["date"]} ), ] diff --git a/gsoc/migrations/0025_auto_20190607_1004.py b/gsoc/migrations/0025_auto_20190607_1004.py index 622ae745..03033baf 100644 --- a/gsoc/migrations/0025_auto_20190607_1004.py +++ b/gsoc/migrations/0025_auto_20190607_1004.py @@ -5,19 +5,27 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0024_auto_20190607_0428'), - ] + dependencies = [("gsoc", "0024_auto_20190607_0428")] operations = [ migrations.AddField( - model_name='event', - name='link', + model_name="event", + name="link", field=models.URLField(blank=True, null=True), ), migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('add_calendar_event', 'add_calendar_event')], max_length=40), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("deactivate_user", "deactivate_user"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ("add_calendar_event", "add_calendar_event"), + ], + max_length=40, + ), ), ] diff --git a/gsoc/migrations/0026_auto_20190608_0852.py b/gsoc/migrations/0026_auto_20190608_0852.py index 20a0a22f..7e9fc96a 100644 --- a/gsoc/migrations/0026_auto_20190608_0852.py +++ b/gsoc/migrations/0026_auto_20190608_0852.py @@ -5,33 +5,37 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0025_auto_20190607_1004'), - ] + dependencies = [("gsoc", "0025_auto_20190607_1004")] operations = [ - migrations.RemoveField( - model_name='event', - name='link', - ), + migrations.RemoveField(model_name="event", name="link"), migrations.AddField( - model_name='blogpostduedate', - name='event_id', + model_name="blogpostduedate", + name="event_id", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='event', - name='event_id', + model_name="event", + name="event_id", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='timeline', - name='calendar_id', + model_name="timeline", + name="calendar_id", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter')], max_length=40), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("deactivate_user", "deactivate_user"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ], + max_length=40, + ), ), ] diff --git a/gsoc/migrations/0027_auto_20190608_1421.py b/gsoc/migrations/0027_auto_20190608_1421.py index c2686637..6f777108 100644 --- a/gsoc/migrations/0027_auto_20190608_1421.py +++ b/gsoc/migrations/0027_auto_20190608_1421.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0026_auto_20190608_0852'), - ] + dependencies = [("gsoc", "0026_auto_20190608_0852")] operations = [ migrations.AlterField( - model_name='comment', - name='content', + model_name="comment", + name="content", field=models.CharField(max_length=1100), - ), + ) ] diff --git a/gsoc/migrations/0028_blogpostduedate_title.py b/gsoc/migrations/0028_blogpostduedate_title.py index 3085c7c3..f051be62 100644 --- a/gsoc/migrations/0028_blogpostduedate_title.py +++ b/gsoc/migrations/0028_blogpostduedate_title.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0027_auto_20190608_1421'), - ] + dependencies = [("gsoc", "0027_auto_20190608_1421")] operations = [ migrations.AddField( - model_name='blogpostduedate', - name='title', - field=models.CharField(default='Weekly Blog Post Due', max_length=100), - ), + model_name="blogpostduedate", + name="title", + field=models.CharField(default="Weekly Blog Post Due", max_length=100), + ) ] diff --git a/gsoc/migrations/0029_suborgdetails.py b/gsoc/migrations/0029_suborgdetails.py index 985a2b77..f519a19e 100644 --- a/gsoc/migrations/0029_suborgdetails.py +++ b/gsoc/migrations/0029_suborgdetails.py @@ -6,38 +6,145 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0028_blogpostduedate_title'), - ] + dependencies = [("gsoc", "0028_blogpostduedate_title")] operations = [ migrations.CreateModel( - name='SubOrgDetails', + name="SubOrgDetails", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('reason_for_participation', models.TextField(verbose_name='Why does your org want to participate in Google Summer of Code?')), - ('suborg_admin_email', models.EmailField(max_length=254, verbose_name='Suborg admin email')), - ('mentors_student_engagement', models.TextField(verbose_name='How will you keep mentors engaged with their students?')), - ('students_on_schedule', models.TextField(verbose_name='How will you help your students stay on schedule to complete their projects?')), - ('students_involvement_gsoc', models.TextField(verbose_name='How will you get your students involved in your community during GSoC?')), - ('students_involvement_after', models.TextField(verbose_name='How will you keep students involved with your community after GSoC?')), - ('past_gsoc_experience', models.BooleanField(verbose_name='Has your org been accepted as a mentor org in Google Summer of Code before?')), - ('suborg_in_past', models.BooleanField(verbose_name='Was this as a Suborg?')), - ('year_of_start', models.IntegerField(verbose_name='What year was your project started?')), - ('source_code', models.URLField(verbose_name='Where does your source code live?')), - ('docs', models.URLField(verbose_name='Please provide the URL that points to the repository, GitHub organization, or a web page that describes how to get your source code')), - ('anything_else', models.TextField(blank=True, null=True, verbose_name='Anything else we should know (optional)')), - ('suborg_name', models.CharField(max_length=80, verbose_name='Name')), - ('description', models.TextField(verbose_name='A very short description of your organization')), - ('logo', models.ImageField(help_text='Must be a 24-bit PNG, minimum height 256 pixels.', upload_to='logos/', verbose_name='Your organization logo')), - ('primary_os_license', models.CharField(max_length=50, verbose_name='Primary Open Source License')), - ('ideas_list', models.TextField(help_text='Write the ideas one by one, separated with newlines.', verbose_name='Ideas List')), - ('applied_but_not_selected', models.ManyToManyField(null=True, related_name='applied_not_selected', to='gsoc.GsocYear', verbose_name='If your org has applied for GSoC before but not been accepted, select the years')), - ('gsoc_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='suborg_details', to='gsoc.GsocYear')), - ('past_years', models.ManyToManyField(null=True, to='gsoc.GsocYear', verbose_name='Which years did your org participate in GSoC?')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "reason_for_participation", + models.TextField( + verbose_name="Why does your org want to participate in Google Summer of Code?" + ), + ), + ( + "suborg_admin_email", + models.EmailField( + max_length=254, verbose_name="Suborg admin email" + ), + ), + ( + "mentors_student_engagement", + models.TextField( + verbose_name="How will you keep mentors engaged with their students?" + ), + ), + ( + "students_on_schedule", + models.TextField( + verbose_name="How will you help your students stay on schedule to complete their projects?" + ), + ), + ( + "students_involvement_gsoc", + models.TextField( + verbose_name="How will you get your students involved in your community during GSoC?" + ), + ), + ( + "students_involvement_after", + models.TextField( + verbose_name="How will you keep students involved with your community after GSoC?" + ), + ), + ( + "past_gsoc_experience", + models.BooleanField( + verbose_name="Has your org been accepted as a mentor org in Google Summer of Code before?" + ), + ), + ( + "suborg_in_past", + models.BooleanField(verbose_name="Was this as a Suborg?"), + ), + ( + "year_of_start", + models.IntegerField( + verbose_name="What year was your project started?" + ), + ), + ( + "source_code", + models.URLField(verbose_name="Where does your source code live?"), + ), + ( + "docs", + models.URLField( + verbose_name="Please provide the URL that points to the repository, GitHub organization, or a web page that describes how to get your source code" + ), + ), + ( + "anything_else", + models.TextField( + blank=True, + null=True, + verbose_name="Anything else we should know (optional)", + ), + ), + ("suborg_name", models.CharField(max_length=80, verbose_name="Name")), + ( + "description", + models.TextField( + verbose_name="A very short description of your organization" + ), + ), + ( + "logo", + models.ImageField( + help_text="Must be a 24-bit PNG, minimum height 256 pixels.", + upload_to="logos/", + verbose_name="Your organization logo", + ), + ), + ( + "primary_os_license", + models.CharField( + max_length=50, verbose_name="Primary Open Source License" + ), + ), + ( + "ideas_list", + models.TextField( + help_text="Write the ideas one by one, separated with newlines.", + verbose_name="Ideas List", + ), + ), + ( + "applied_but_not_selected", + models.ManyToManyField( + null=True, + related_name="applied_not_selected", + to="gsoc.GsocYear", + verbose_name="If your org has applied for GSoC before but not been accepted, select the years", + ), + ), + ( + "gsoc_year", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="suborg_details", + to="gsoc.GsocYear", + ), + ), + ( + "past_years", + models.ManyToManyField( + null=True, + to="gsoc.GsocYear", + verbose_name="Which years did your org participate in GSoC?", + ), + ), ], - options={ - 'verbose_name_plural': 'Suborg Details', - }, - ), + options={"verbose_name_plural": "Suborg Details"}, + ) ] diff --git a/gsoc/migrations/0030_auto_20190614_0559.py b/gsoc/migrations/0030_auto_20190614_0559.py index 46ca7a44..a1d87a23 100644 --- a/gsoc/migrations/0030_auto_20190614_0559.py +++ b/gsoc/migrations/0030_auto_20190614_0559.py @@ -5,39 +5,37 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0029_suborgdetails'), - ] + dependencies = [("gsoc", "0029_suborgdetails")] operations = [ migrations.AddField( - model_name='suborgdetails', - name='accepted', + model_name="suborgdetails", + name="accepted", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='suborgdetails', - name='blog_url', + model_name="suborgdetails", + name="blog_url", field=models.URLField(blank=True, null=True), ), migrations.AddField( - model_name='suborgdetails', - name='chat', + model_name="suborgdetails", + name="chat", field=models.URLField(blank=True, null=True), ), migrations.AddField( - model_name='suborgdetails', - name='link', - field=models.URLField(blank=True, null=True, verbose_name='Any other link'), + model_name="suborgdetails", + name="link", + field=models.URLField(blank=True, null=True, verbose_name="Any other link"), ), migrations.AddField( - model_name='suborgdetails', - name='mailing_list', + model_name="suborgdetails", + name="mailing_list", field=models.EmailField(blank=True, max_length=254, null=True), ), migrations.AddField( - model_name='suborgdetails', - name='twitter_url', + model_name="suborgdetails", + name="twitter_url", field=models.URLField(blank=True, null=True), ), ] diff --git a/gsoc/migrations/0031_auto_20190614_0630.py b/gsoc/migrations/0031_auto_20190614_0630.py index 2e8212f3..44395164 100644 --- a/gsoc/migrations/0031_auto_20190614_0630.py +++ b/gsoc/migrations/0031_auto_20190614_0630.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0030_auto_20190614_0559'), - ] + dependencies = [("gsoc", "0030_auto_20190614_0559")] operations = [ migrations.AlterField( - model_name='suborgdetails', - name='accepted', + model_name="suborgdetails", + name="accepted", field=models.BooleanField(default=None, null=True), - ), + ) ] diff --git a/gsoc/migrations/0032_auto_20190621_0347.py b/gsoc/migrations/0032_auto_20190621_0347.py index 73e7b37d..5d0c474d 100644 --- a/gsoc/migrations/0032_auto_20190621_0347.py +++ b/gsoc/migrations/0032_auto_20190621_0347.py @@ -5,34 +5,41 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0031_auto_20190614_0630'), - ] + dependencies = [("gsoc", "0031_auto_20190614_0630")] operations = [ migrations.AddField( - model_name='suborgdetails', - name='changed', + model_name="suborgdetails", + name="changed", field=models.BooleanField(default=None, null=True), ), migrations.AddField( - model_name='suborgdetails', - name='last_message', + model_name="suborgdetails", + name="last_message", field=models.TextField(blank=True, null=True), ), migrations.AlterField( - model_name='suborgdetails', - name='accepted', + model_name="suborgdetails", + name="accepted", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='suborgdetails', - name='applied_but_not_selected', - field=models.ManyToManyField(blank=True, related_name='applied_not_selected', to='gsoc.GsocYear', verbose_name='If your org has applied for GSoC before but not been accepted, select the years'), + model_name="suborgdetails", + name="applied_but_not_selected", + field=models.ManyToManyField( + blank=True, + related_name="applied_not_selected", + to="gsoc.GsocYear", + verbose_name="If your org has applied for GSoC before but not been accepted, select the years", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='past_years', - field=models.ManyToManyField(blank=True, to='gsoc.GsocYear', verbose_name='Which years did your org participate in GSoC?'), + model_name="suborgdetails", + name="past_years", + field=models.ManyToManyField( + blank=True, + to="gsoc.GsocYear", + verbose_name="Which years did your org participate in GSoC?", + ), ), ] diff --git a/gsoc/migrations/0033_auto_20190623_0751.py b/gsoc/migrations/0033_auto_20190623_0751.py index 4845c582..9d47a98d 100644 --- a/gsoc/migrations/0033_auto_20190623_0751.py +++ b/gsoc/migrations/0033_auto_20190623_0751.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0032_auto_20190621_0347'), - ] + dependencies = [("gsoc", "0032_auto_20190621_0347")] operations = [ migrations.AlterField( - model_name='suborgdetails', - name='ideas_list', - field=models.URLField(verbose_name='Ideas List'), - ), + model_name="suborgdetails", + name="ideas_list", + field=models.URLField(verbose_name="Ideas List"), + ) ] diff --git a/gsoc/migrations/0034_auto_20190623_1123.py b/gsoc/migrations/0034_auto_20190623_1123.py index fc6cec9b..973e0982 100644 --- a/gsoc/migrations/0034_auto_20190623_1123.py +++ b/gsoc/migrations/0034_auto_20190623_1123.py @@ -5,19 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0033_auto_20190623_0751'), - ] + dependencies = [("gsoc", "0033_auto_20190623_0751")] operations = [ migrations.AlterField( - model_name='suborgdetails', - name='chat', + model_name="suborgdetails", + name="chat", field=models.CharField(blank=True, max_length=80, null=True), ), migrations.AlterField( - model_name='suborgdetails', - name='mailing_list', + model_name="suborgdetails", + name="mailing_list", field=models.CharField(blank=True, max_length=80, null=True), ), ] diff --git a/gsoc/migrations/0035_auto_20190626_0437.py b/gsoc/migrations/0035_auto_20190626_0437.py index 57cfe808..0f9827b0 100644 --- a/gsoc/migrations/0035_auto_20190626_0437.py +++ b/gsoc/migrations/0035_auto_20190626_0437.py @@ -9,28 +9,44 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('gsoc', '0034_auto_20190623_1123'), + ("gsoc", "0034_auto_20190623_1123"), ] operations = [ migrations.AddField( - model_name='suborgdetails', - name='last_updated_at', + model_name="suborgdetails", + name="last_updated_at", field=models.DateTimeField(blank=True, null=True), ), migrations.AddField( - model_name='suborgdetails', - name='last_updated_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="suborgdetails", + name="last_updated_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='suborgdetails', - name='suborg', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.SubOrg', verbose_name='Select your suborg, if you have applied before'), + model_name="suborgdetails", + name="suborg", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.SubOrg", + verbose_name="Select your suborg, if you have applied before", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='suborg_name', - field=models.CharField(blank=True, max_length=80, null=True, verbose_name='If applying for the first time enter the name of your suborg'), + model_name="suborgdetails", + name="suborg_name", + field=models.CharField( + blank=True, + max_length=80, + null=True, + verbose_name="If applying for the first time enter the name of your suborg", + ), ), ] diff --git a/gsoc/migrations/0036_auto_20190628_0502.py b/gsoc/migrations/0036_auto_20190628_0502.py index 4305bd55..1b753845 100644 --- a/gsoc/migrations/0036_auto_20190628_0502.py +++ b/gsoc/migrations/0036_auto_20190628_0502.py @@ -5,19 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0035_auto_20190626_0437'), - ] + dependencies = [("gsoc", "0035_auto_20190626_0437")] operations = [ migrations.AddField( - model_name='builder', - name='last_error', + model_name="builder", + name="last_error", field=models.TextField(blank=True, default=None, null=True), ), migrations.AlterField( - model_name='builder', - name='built', + model_name="builder", + name="built", field=models.BooleanField(default=None, null=True), ), ] diff --git a/gsoc/migrations/0037_auto_20190629_1627.py b/gsoc/migrations/0037_auto_20190629_1627.py index 5ec2d23b..fb5e7f9e 100644 --- a/gsoc/migrations/0037_auto_20190629_1627.py +++ b/gsoc/migrations/0037_auto_20190629_1627.py @@ -5,14 +5,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0036_auto_20190628_0502'), - ] + dependencies = [("gsoc", "0036_auto_20190628_0502")] operations = [ migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('deactivate_user', 'deactivate_user'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template')], max_length=40), - ), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("deactivate_user", "deactivate_user"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ("update_site_template", "update_site_template"), + ], + max_length=40, + ), + ) ] diff --git a/gsoc/migrations/0038_auto_20190702_0326.py b/gsoc/migrations/0038_auto_20190702_0326.py index c0414857..2ab11d51 100644 --- a/gsoc/migrations/0038_auto_20190702_0326.py +++ b/gsoc/migrations/0038_auto_20190702_0326.py @@ -5,14 +5,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0037_auto_20190629_1627'), - ] + dependencies = [("gsoc", "0037_auto_20190629_1627")] operations = [ migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('revoke_student_permissions', 'revoke_student_permissions'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template')], max_length=40), - ), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("revoke_student_permissions", "revoke_student_permissions"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ("update_site_template", "update_site_template"), + ], + max_length=40, + ), + ) ] diff --git a/gsoc/migrations/0039_gsocenddate.py b/gsoc/migrations/0039_gsocenddate.py index f918b260..84e269d5 100644 --- a/gsoc/migrations/0039_gsocenddate.py +++ b/gsoc/migrations/0039_gsocenddate.py @@ -6,17 +6,28 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0038_auto_20190702_0326'), - ] + dependencies = [("gsoc", "0038_auto_20190702_0326")] operations = [ migrations.CreateModel( - name='GsocEndDate', + name="GsocEndDate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateField()), - ('timeline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='gsoc.Timeline')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField()), + ( + "timeline", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, to="gsoc.Timeline" + ), + ), ], - ), + ) ] diff --git a/gsoc/migrations/0040_auto_20190703_0617.py b/gsoc/migrations/0040_auto_20190703_0617.py index eb4da56a..3325b79b 100644 --- a/gsoc/migrations/0040_auto_20190703_0617.py +++ b/gsoc/migrations/0040_auto_20190703_0617.py @@ -5,14 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0039_gsocenddate'), - ] + dependencies = [("gsoc", "0039_gsocenddate")] operations = [ migrations.AlterField( - model_name='builder', - name='category', - field=models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms')], max_length=40), - ), + model_name="builder", + name="category", + field=models.CharField( + choices=[ + ("build_pre_blog_reminders", "build_pre_blog_reminders"), + ("build_post_blog_reminders", "build_post_blog_reminders"), + ("build_revoke_student_perms", "build_revoke_student_perms"), + ], + max_length=40, + ), + ) ] diff --git a/gsoc/migrations/0041_auto_20190707_0432.py b/gsoc/migrations/0041_auto_20190707_0432.py index f06e4a75..8afa97b3 100644 --- a/gsoc/migrations/0041_auto_20190707_0432.py +++ b/gsoc/migrations/0041_auto_20190707_0432.py @@ -5,19 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0040_auto_20190703_0617'), - ] + dependencies = [("gsoc", "0040_auto_20190703_0617")] operations = [ migrations.RenameField( - model_name='suborgdetails', - old_name='last_updated_at', - new_name='last_reviewed_at', + model_name="suborgdetails", + old_name="last_updated_at", + new_name="last_reviewed_at", ), migrations.RenameField( - model_name='suborgdetails', - old_name='last_updated_by', - new_name='last_reviewed_by', + model_name="suborgdetails", + old_name="last_updated_by", + new_name="last_reviewed_by", ), ] diff --git a/gsoc/migrations/0042_auto_20190707_0505.py b/gsoc/migrations/0042_auto_20190707_0505.py index 023a08d4..d4a76aef 100644 --- a/gsoc/migrations/0042_auto_20190707_0505.py +++ b/gsoc/migrations/0042_auto_20190707_0505.py @@ -5,19 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0041_auto_20190707_0432'), - ] + dependencies = [("gsoc", "0041_auto_20190707_0432")] operations = [ migrations.AddField( - model_name='suborgdetails', - name='created_at', + model_name="suborgdetails", + name="created_at", field=models.DateTimeField(blank=True, null=True), ), migrations.AddField( - model_name='suborgdetails', - name='updated_at', + model_name="suborgdetails", + name="updated_at", field=models.DateTimeField(blank=True, null=True), ), ] diff --git a/gsoc/migrations/0043_blogpostduedate_category.py b/gsoc/migrations/0043_blogpostduedate_category.py index c4987774..40a8b990 100644 --- a/gsoc/migrations/0043_blogpostduedate_category.py +++ b/gsoc/migrations/0043_blogpostduedate_category.py @@ -5,14 +5,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0042_auto_20190707_0505'), - ] + dependencies = [("gsoc", "0042_auto_20190707_0505")] operations = [ migrations.AddField( - model_name='blogpostduedate', - name='category', - field=models.IntegerField(blank=True, choices=[(0, 'Weekly Check-In'), (1, 'Blog Post')], null=True), - ), + model_name="blogpostduedate", + name="category", + field=models.IntegerField( + blank=True, + choices=[(0, "Weekly Check-In"), (1, "Blog Post")], + null=True, + ), + ) ] diff --git a/gsoc/migrations/0044_auto_20190716_0700.py b/gsoc/migrations/0044_auto_20190716_0700.py index 4eba5614..b1f6e725 100644 --- a/gsoc/migrations/0044_auto_20190716_0700.py +++ b/gsoc/migrations/0044_auto_20190716_0700.py @@ -5,14 +5,23 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0043_blogpostduedate_category'), - ] + dependencies = [("gsoc", "0043_blogpostduedate_category")] operations = [ migrations.AlterField( - model_name='scheduler', - name='command', - field=models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('revoke_student_permissions', 'revoke_student_permissions'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template'), ('archive_gsoc_pages', 'archive_gsoc_pages')], max_length=40), - ), + model_name="scheduler", + name="command", + field=models.CharField( + choices=[ + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("revoke_student_permissions", "revoke_student_permissions"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ("update_site_template", "update_site_template"), + ("archive_gsoc_pages", "archive_gsoc_pages"), + ], + max_length=40, + ), + ) ] diff --git a/gsoc/migrations/0045_auto_20190719_1851.py b/gsoc/migrations/0045_auto_20190719_1851.py index b5f9fad7..1a40a97f 100644 --- a/gsoc/migrations/0045_auto_20190719_1851.py +++ b/gsoc/migrations/0045_auto_20190719_1851.py @@ -5,24 +5,32 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0044_auto_20190716_0700'), - ] + dependencies = [("gsoc", "0044_auto_20190716_0700")] operations = [ migrations.AlterField( - model_name='suborgdetails', - name='logo', - field=models.ImageField(help_text='Must be a 24-bit PNG of 256 x 256 pixels.', upload_to='logos/', verbose_name='Your organization logo'), + model_name="suborgdetails", + name="logo", + field=models.ImageField( + help_text="Must be a 24-bit PNG of 256 x 256 pixels.", + upload_to="logos/", + verbose_name="Your organization logo", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='past_gsoc_experience', - field=models.BooleanField(help_text='Mark the checkbox for yes', verbose_name='Has your org been accepted as a mentor org in Google Summer of Code before?'), + model_name="suborgdetails", + name="past_gsoc_experience", + field=models.BooleanField( + help_text="Mark the checkbox for yes", + verbose_name="Has your org been accepted as a mentor org in Google Summer of Code before?", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='suborg_in_past', - field=models.BooleanField(help_text='Mark the checkbox for yes', verbose_name='Was this as a Suborg?'), + model_name="suborgdetails", + name="suborg_in_past", + field=models.BooleanField( + help_text="Mark the checkbox for yes", + verbose_name="Was this as a Suborg?", + ), ), ] diff --git a/gsoc/migrations/0046_sendemail.py b/gsoc/migrations/0046_sendemail.py index c48c3b2f..27046783 100644 --- a/gsoc/migrations/0046_sendemail.py +++ b/gsoc/migrations/0046_sendemail.py @@ -6,20 +6,56 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0045_auto_20190719_1851'), - ] + dependencies = [("gsoc", "0045_auto_20190719_1851")] operations = [ migrations.CreateModel( - name='SendEmail', + name="SendEmail", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('to', models.CharField(blank=True, help_text='Separate email with a comma', max_length=255, null=True)), - ('to_group', models.CharField(blank=True, choices=[('students', 'Students'), ('mentors', 'Mentors'), ('suborg_admins', 'Suborg Admins'), ('admins', 'Admins'), ('all', 'All')], max_length=80, null=True)), - ('subject', models.CharField(max_length=255)), - ('body', models.TextField()), - ('scheduler', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.Scheduler')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "to", + models.CharField( + blank=True, + help_text="Separate email with a comma", + max_length=255, + null=True, + ), + ), + ( + "to_group", + models.CharField( + blank=True, + choices=[ + ("students", "Students"), + ("mentors", "Mentors"), + ("suborg_admins", "Suborg Admins"), + ("admins", "Admins"), + ("all", "All"), + ], + max_length=80, + null=True, + ), + ), + ("subject", models.CharField(max_length=255)), + ("body", models.TextField()), + ( + "scheduler", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gsoc.Scheduler", + ), + ), ], - ), + ) ] diff --git a/gsoc/migrations/0047_sendemail_activation_date.py b/gsoc/migrations/0047_sendemail_activation_date.py index a4261b1e..5f1658df 100644 --- a/gsoc/migrations/0047_sendemail_activation_date.py +++ b/gsoc/migrations/0047_sendemail_activation_date.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0046_sendemail'), - ] + dependencies = [("gsoc", "0046_sendemail")] operations = [ migrations.AddField( - model_name='sendemail', - name='activation_date', + model_name="sendemail", + name="activation_date", field=models.DateTimeField(blank=True, null=True), - ), + ) ] diff --git a/gsoc/migrations/0048_reglink_send_notifications.py b/gsoc/migrations/0048_reglink_send_notifications.py index 2186429b..af4ab14b 100644 --- a/gsoc/migrations/0048_reglink_send_notifications.py +++ b/gsoc/migrations/0048_reglink_send_notifications.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0047_sendemail_activation_date'), - ] + dependencies = [("gsoc", "0047_sendemail_activation_date")] operations = [ migrations.AddField( - model_name='reglink', - name='send_notifications', + model_name="reglink", + name="send_notifications", field=models.BooleanField(default=True), - ), + ) ] diff --git a/gsoc/migrations/0049_blogposthistory.py b/gsoc/migrations/0049_blogposthistory.py index b10bacee..3b357efe 100644 --- a/gsoc/migrations/0049_blogposthistory.py +++ b/gsoc/migrations/0049_blogposthistory.py @@ -7,18 +7,32 @@ class Migration(migrations.Migration): dependencies = [ - ('aldryn_newsblog', '0016_auto_20180329_1417'), - ('gsoc', '0048_reglink_send_notifications'), + ("aldryn_newsblog", "0016_auto_20180329_1417"), + ("gsoc", "0048_reglink_send_notifications"), ] operations = [ migrations.CreateModel( - name='BlogPostHistory', + name="BlogPostHistory", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('content', models.TextField(blank=True, null=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ("content", models.TextField(blank=True, null=True)), + ( + "article", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aldryn_newsblog.Article", + ), + ), ], - ), + ) ] diff --git a/gsoc/migrations/0050_auto_20190809_0529.py b/gsoc/migrations/0050_auto_20190809_0529.py index 057396e4..9c86f20a 100644 --- a/gsoc/migrations/0050_auto_20190809_0529.py +++ b/gsoc/migrations/0050_auto_20190809_0529.py @@ -5,34 +5,52 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0049_blogposthistory'), - ] + dependencies = [("gsoc", "0049_blogposthistory")] operations = [ migrations.AlterField( - model_name='suborgdetails', - name='mentors_student_engagement', - field=models.TextField(blank=True, null=True, verbose_name='How will you keep mentors engaged with their students?'), + model_name="suborgdetails", + name="mentors_student_engagement", + field=models.TextField( + blank=True, + null=True, + verbose_name="How will you keep mentors engaged with their students?", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='reason_for_participation', - field=models.TextField(blank=True, null=True, verbose_name='Why does your org want to participate in Google Summer of Code?'), + model_name="suborgdetails", + name="reason_for_participation", + field=models.TextField( + blank=True, + null=True, + verbose_name="Why does your org want to participate in Google Summer of Code?", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='students_involvement_after', - field=models.TextField(blank=True, null=True, verbose_name='How will you keep students involved with your community after GSoC?'), + model_name="suborgdetails", + name="students_involvement_after", + field=models.TextField( + blank=True, + null=True, + verbose_name="How will you keep students involved with your community after GSoC?", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='students_involvement_gsoc', - field=models.TextField(blank=True, null=True, verbose_name='How will you get your students involved in your community during GSoC?'), + model_name="suborgdetails", + name="students_involvement_gsoc", + field=models.TextField( + blank=True, + null=True, + verbose_name="How will you get your students involved in your community during GSoC?", + ), ), migrations.AlterField( - model_name='suborgdetails', - name='students_on_schedule', - field=models.TextField(blank=True, null=True, verbose_name='How will you help your students stay on schedule to complete their projects?'), + model_name="suborgdetails", + name="students_on_schedule", + field=models.TextField( + blank=True, + null=True, + verbose_name="How will you help your students stay on schedule to complete their projects?", + ), ), ] diff --git a/gsoc/migrations/0051_auto_20190814_1454.py b/gsoc/migrations/0051_auto_20190814_1454.py index a94373a4..09c4081b 100644 --- a/gsoc/migrations/0051_auto_20190814_1454.py +++ b/gsoc/migrations/0051_auto_20190814_1454.py @@ -9,21 +9,43 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('gsoc', '0050_auto_20190809_0529'), + ("gsoc", "0050_auto_20190809_0529"), ] operations = [ migrations.CreateModel( - name='ReaddUser', + name="ReaddUser", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('uuid', models.CharField(max_length=100)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("uuid", models.CharField(max_length=100)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AlterField( - model_name='builder', - name='category', - field=models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), ('build_remove_user_details', 'build_remove_user_details')], max_length=40), + model_name="builder", + name="category", + field=models.CharField( + choices=[ + ("build_pre_blog_reminders", "build_pre_blog_reminders"), + ("build_post_blog_reminders", "build_post_blog_reminders"), + ("build_revoke_student_perms", "build_revoke_student_perms"), + ("build_remove_user_details", "build_remove_user_details"), + ], + max_length=40, + ), ), ] diff --git a/gsoc/migrations/0052_userprofile_github_handle.py b/gsoc/migrations/0052_userprofile_github_handle.py index a355e43f..a992b2af 100644 --- a/gsoc/migrations/0052_userprofile_github_handle.py +++ b/gsoc/migrations/0052_userprofile_github_handle.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0051_auto_20190814_1454'), - ] + dependencies = [("gsoc", "0051_auto_20190814_1454")] operations = [ migrations.AddField( - model_name='userprofile', - name='github_handle', + model_name="userprofile", + name="github_handle", field=models.TextField(blank=True, max_length=100, null=True), - ), + ) ] diff --git a/gsoc/migrations/0053_auto_20190815_1130.py b/gsoc/migrations/0053_auto_20190815_1130.py index 60c49be2..9baa11b0 100644 --- a/gsoc/migrations/0053_auto_20190815_1130.py +++ b/gsoc/migrations/0053_auto_20190815_1130.py @@ -5,19 +5,27 @@ class Migration(migrations.Migration): - dependencies = [ - ('gsoc', '0052_userprofile_github_handle'), - ] + dependencies = [("gsoc", "0052_userprofile_github_handle")] operations = [ migrations.AddField( - model_name='suborgdetails', - name='suborg_admin_2_email', - field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Suborg admin 2 email'), + model_name="suborgdetails", + name="suborg_admin_2_email", + field=models.EmailField( + blank=True, + max_length=254, + null=True, + verbose_name="Suborg admin 2 email", + ), ), migrations.AddField( - model_name='suborgdetails', - name='suborg_admin_3_email', - field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Suborg admin 3 email'), + model_name="suborgdetails", + name="suborg_admin_3_email", + field=models.EmailField( + blank=True, + max_length=254, + null=True, + verbose_name="Suborg admin 3 email", + ), ), ] diff --git a/gsoc/models.py b/gsoc/models.py index bb7c6603..036041f2 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -43,6 +43,7 @@ # Util Functions + def gen_uuid_str(): return str(uuid.uuid4()) @@ -58,7 +59,7 @@ def current_year_profile(self): return profile.first() if len(profile) > 0 else None -auth.models.User.add_to_class('current_year_profile', current_year_profile) +auth.models.User.add_to_class("current_year_profile", current_year_profile) def has_proposal(self): @@ -69,7 +70,7 @@ def has_proposal(self): return False -auth.models.User.add_to_class('has_proposal', has_proposal) +auth.models.User.add_to_class("has_proposal", has_proposal) def is_current_year_student(self): @@ -81,7 +82,7 @@ def is_current_year_student(self): return current_year == year -auth.models.User.add_to_class('is_current_year_student', is_current_year_student) +auth.models.User.add_to_class("is_current_year_student", is_current_year_student) def is_current_year_suborg_admin(self): @@ -93,50 +94,59 @@ def is_current_year_suborg_admin(self): return current_year == year -auth.models.User.add_to_class('is_current_year_suborg_admin', is_current_year_suborg_admin) +auth.models.User.add_to_class( + "is_current_year_suborg_admin", is_current_year_suborg_admin +) def suborg_admin_profile(self, year=timezone.now().year): gsoc_year = GsocYear.objects.filter(gsoc_year=year).first() if gsoc_year is None: return None - return self.userprofile_set.filter(role=1, - gsoc_year=gsoc_year).first() + return self.userprofile_set.filter(role=1, gsoc_year=gsoc_year).first() -auth.models.User.add_to_class('suborg_admin_profile', suborg_admin_profile) +auth.models.User.add_to_class("suborg_admin_profile", suborg_admin_profile) def student_profile(self, year=timezone.now().year): gsoc_year = GsocYear.objects.filter(gsoc_year=year).first() if gsoc_year is None: return None - return self.userprofile_set.filter(role=3, - gsoc_year=gsoc_year).first() + return self.userprofile_set.filter(role=3, gsoc_year=gsoc_year).first() -auth.models.User.add_to_class('student_profile', student_profile) +auth.models.User.add_to_class("student_profile", student_profile) def get_root_comments(self): return self.comment_set.filter(parent=None).all() -Article.add_to_class('get_root_comments', get_root_comments) +Article.add_to_class("get_root_comments", get_root_comments) def save(self, *args, **kwargs): tags = settings.BLEACH_ALLOWED_TAGS attrs = bleach.sanitizer.ALLOWED_ATTRIBUTES - attrs['iframe'] = ['src', 'frameborder', 'allow', 'allowfullscreen', 'width', 'height'] - attrs['img'] = ['src', 'alt'] - attrs['*'] = ['class', 'style'] + attrs["iframe"] = [ + "src", + "frameborder", + "allow", + "allowfullscreen", + "width", + "height", + ] + attrs["img"] = ["src", "alt"] + attrs["*"] = ["class", "style"] styles = settings.BLEACH_ALLOWED_STYLES - self.lead_in = bleach.clean(self.lead_in, tags=tags, attributes=attrs, styles=styles) - soup = BeautifulSoup(self.lead_in, 'html5lib') - for iframe_tag in soup.find_all('iframe'): - _ = iframe_tag.attrs.get('src', None) - if not(_ and "https://www.youtube.com/embed" in _): + self.lead_in = bleach.clean( + self.lead_in, tags=tags, attributes=attrs, styles=styles + ) + soup = BeautifulSoup(self.lead_in, "html5lib") + for iframe_tag in soup.find_all("iframe"): + _ = iframe_tag.attrs.get("src", None) + if not (_ and "https://www.youtube.com/embed" in _): iframe_text = str(iframe_tag) self.lead_in = self.lead_in.replace(iframe_text, bleach.clean(iframe_text)) @@ -148,12 +158,8 @@ def save(self, *args, **kwargs): if self.app_config.create_authors and self.author is None: self.author = Person.objects.get_or_create( user=self.owner, - defaults={ - 'name': ' '.join(( - self.owner.first_name, - self.owner.last_name, - )), - })[0] + defaults={"name": " ".join((self.owner.first_name, self.owner.last_name))}, + )[0] # slug would be generated by TranslatedAutoSlugifyMixin super(Article, self).save(*args, **kwargs) @@ -163,10 +169,12 @@ def save(self, *args, **kwargs): # Models + class SubOrg(models.Model): class Meta: - ordering = ['suborg_name'] - suborg_name = models.CharField(name='suborg_name', max_length=80) + ordering = ["suborg_name"] + + suborg_name = models.CharField(name="suborg_name", max_length=80) def __str__(self): return self.suborg_name @@ -174,8 +182,9 @@ def __str__(self): class GsocYear(models.Model): class Meta: - ordering = ['-gsoc_year'] - gsoc_year = models.IntegerField(name='gsoc_year') + ordering = ["-gsoc_year"] + + gsoc_year = models.IntegerField(name="gsoc_year") def __str__(self): return str(self.gsoc_year) @@ -183,103 +192,122 @@ def __str__(self): class SubOrgDetails(models.Model): gsoc_year = models.ForeignKey( - GsocYear, - on_delete=models.CASCADE, - related_name='suborg_details' - ) + GsocYear, on_delete=models.CASCADE, related_name="suborg_details" + ) reason_for_participation = models.TextField( - verbose_name='Why does your org want to participate in Google Summer of Code?', - null=True, blank=True, - ) - suborg_admin_email = models.EmailField( - verbose_name='Suborg admin email' - ) + verbose_name="Why does your org want to participate in Google Summer of Code?", + null=True, + blank=True, + ) + suborg_admin_email = models.EmailField(verbose_name="Suborg admin email") suborg_admin_2_email = models.EmailField( - verbose_name='Suborg admin 2 email', - blank=True, null=True, - help_text='Fill this if there are other suborg admins other than you' - ) - + verbose_name="Suborg admin 2 email", + blank=True, + null=True, + help_text="Fill this if there are other suborg admins other than you", + ) + suborg_admin_3_email = models.EmailField( - verbose_name='Suborg admin 3 email', - blank=True, null=True, - help_text='Fill this if there are other suborg admins other than you' - ) + verbose_name="Suborg admin 3 email", + blank=True, + null=True, + help_text="Fill this if there are other suborg admins other than you", + ) mentors_student_engagement = models.TextField( - verbose_name='How will you keep mentors engaged with their students?', - null=True, blank=True - ) + verbose_name="How will you keep mentors engaged with their students?", + null=True, + blank=True, + ) students_on_schedule = models.TextField( - verbose_name='How will you help your students stay ' - 'on schedule to complete their projects?', - null=True, blank=True - ) + verbose_name="How will you help your students stay " + "on schedule to complete their projects?", + null=True, + blank=True, + ) students_involvement_gsoc = models.TextField( - verbose_name='How will you get your students involved in your community during GSoC?', - null=True, blank=True - ) + verbose_name="How will you get your students involved in your community during GSoC?", + null=True, + blank=True, + ) students_involvement_after = models.TextField( - verbose_name='How will you keep students involved with your community after GSoC?', - null=True, blank=True - ) + verbose_name="How will you keep students involved with your community after GSoC?", + null=True, + blank=True, + ) past_gsoc_experience = models.BooleanField( - verbose_name='Has your org been accepted as a mentor org ' - 'in Google Summer of Code before?', - help_text='Mark the checkbox for yes' - ) + verbose_name="Has your org been accepted as a mentor org " + "in Google Summer of Code before?", + help_text="Mark the checkbox for yes", + ) past_years = models.ManyToManyField( GsocYear, blank=True, - verbose_name='Which years did your org participate in GSoC?' - ) - suborg_in_past = models.BooleanField(verbose_name='Was this as a Suborg?', - help_text='Mark the checkbox for yes') + verbose_name="Which years did your org participate in GSoC?", + ) + suborg_in_past = models.BooleanField( + verbose_name="Was this as a Suborg?", help_text="Mark the checkbox for yes" + ) applied_but_not_selected = models.ManyToManyField( GsocYear, blank=True, - related_name='applied_not_selected', - verbose_name='If your org has applied for GSoC ' - 'before but not been accepted, select the years' - ) - year_of_start = models.IntegerField(verbose_name='What year was your project started?') - source_code = models.URLField(verbose_name='Where does your source code live?') + related_name="applied_not_selected", + verbose_name="If your org has applied for GSoC " + "before but not been accepted, select the years", + ) + year_of_start = models.IntegerField( + verbose_name="What year was your project started?" + ) + source_code = models.URLField(verbose_name="Where does your source code live?") docs = models.URLField( - verbose_name='Please provide the URL that points to the repository, ' - 'GitHub organization, or a web page that describes how to' - ' get your source code' - ) + verbose_name="Please provide the URL that points to the repository, " + "GitHub organization, or a web page that describes how to" + " get your source code" + ) anything_else = models.TextField( + null=True, blank=True, verbose_name="Anything else we should know (optional)" + ) + + suborg = models.ForeignKey( + SubOrg, null=True, blank=True, - verbose_name='Anything else we should know (optional)' - ) - - suborg = models.ForeignKey(SubOrg, null=True, blank=True, - on_delete=models.CASCADE, verbose_name='Select your suborg, if ' - 'you have applied before') - suborg_name = models.CharField(max_length=80, verbose_name='If applying for the first time' - ' enter the name of your suborg', - null=True, blank=True) - description = models.TextField(verbose_name='A very short description of your organization') - logo = models.ImageField(upload_to='logos/', verbose_name='Your organization logo', - help_text='Must be a 24-bit PNG of 256 x 256 pixels.') - primary_os_license = models.CharField(max_length=50, - verbose_name='Primary Open Source License') - ideas_list = models.URLField(verbose_name='Ideas List') + on_delete=models.CASCADE, + verbose_name="Select your suborg, if " "you have applied before", + ) + suborg_name = models.CharField( + max_length=80, + verbose_name="If applying for the first time" " enter the name of your suborg", + null=True, + blank=True, + ) + description = models.TextField( + verbose_name="A very short description of your organization" + ) + logo = models.ImageField( + upload_to="logos/", + verbose_name="Your organization logo", + help_text="Must be a 24-bit PNG of 256 x 256 pixels.", + ) + primary_os_license = models.CharField( + max_length=50, verbose_name="Primary Open Source License" + ) + ideas_list = models.URLField(verbose_name="Ideas List") chat = models.CharField(max_length=80, null=True, blank=True) mailing_list = models.CharField(max_length=80, null=True, blank=True) twitter_url = models.URLField(null=True, blank=True) blog_url = models.URLField(null=True, blank=True) - link = models.URLField(null=True, blank=True, verbose_name='Any other link') + link = models.URLField(null=True, blank=True, verbose_name="Any other link") last_message = models.TextField(null=True, blank=True) last_reviewed_at = models.DateTimeField(null=True, blank=True) - last_reviewed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) + last_reviewed_by = models.ForeignKey( + User, null=True, blank=True, on_delete=models.CASCADE + ) created_at = models.DateTimeField(null=True, blank=True) updated_at = models.DateTimeField(null=True, blank=True) @@ -288,7 +316,7 @@ class SubOrgDetails(models.Model): changed = models.BooleanField(default=None, null=True) class Meta: - verbose_name_plural = 'Suborg Details' + verbose_name_plural = "Suborg Details" def accept(self): self.accepted = True @@ -297,45 +325,55 @@ def accept(self): self.save() template_data = { - 'gsoc_year': self.gsoc_year.gsoc_year, - 'suborg_name': self.suborg.suborg_name, - } - scheduler_data = build_send_mail_json(self.suborg_admin_email, - template='suborg_accept.html', - subject='Acceptance for GSoC@PSF {}'. - format(self.gsoc_year.gsoc_year), - template_data=template_data) - Scheduler.objects.create(command='send_email', - data=scheduler_data) - - RegLink.objects.create(user_role=1, - user_suborg=self.suborg, - user_gsoc_year=self.gsoc_year, - email=self.suborg_admin_email, - send_notifications=False) + "gsoc_year": self.gsoc_year.gsoc_year, + "suborg_name": self.suborg.suborg_name, + } + scheduler_data = build_send_mail_json( + self.suborg_admin_email, + template="suborg_accept.html", + subject="Acceptance for GSoC@PSF {}".format(self.gsoc_year.gsoc_year), + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) + + RegLink.objects.create( + user_role=1, + user_suborg=self.suborg, + user_gsoc_year=self.gsoc_year, + email=self.suborg_admin_email, + send_notifications=False, + ) if self.suborg_admin_2_email: - RegLink.objects.create(user_role=1, - user_suborg=self.suborg, - user_gsoc_year=self.gsoc_year, - email=self.suborg_admin_2_email, - send_notifications=False) + RegLink.objects.create( + user_role=1, + user_suborg=self.suborg, + user_gsoc_year=self.gsoc_year, + email=self.suborg_admin_2_email, + send_notifications=False, + ) if self.suborg_admin_3_email: - RegLink.objects.create(user_role=1, - user_suborg=self.suborg, - user_gsoc_year=self.gsoc_year, - email=self.suborg_admin_3_email, - send_notifications=False) - - s = Scheduler.objects.filter(command='update_site_template', - data=json.dumps({'template': 'index.html'}), - success=None).all() + RegLink.objects.create( + user_role=1, + user_suborg=self.suborg, + user_gsoc_year=self.gsoc_year, + email=self.suborg_admin_3_email, + send_notifications=False, + ) + + s = Scheduler.objects.filter( + command="update_site_template", + data=json.dumps({"template": "index.html"}), + success=None, + ).all() if len(s) == 0: time = timezone.now() + timezone.timedelta(minutes=5) - Scheduler.objects.create(command='update_site_template', - data=json.dumps({'template': 'index.html'}), - activation_date=time) + Scheduler.objects.create( + command="update_site_template", + data=json.dumps({"template": "index.html"}), + activation_date=time, + ) def send_update_notification(self): if self.suborg: @@ -343,15 +381,14 @@ def send_update_notification(self): else: suborg_name = self.suborg_name - template_data = { - 'suborg_name': suborg_name - } - scheduler_data = build_send_mail_json(settings.ADMINS, - template='suborg_application_notification.html', - subject='Review new/updated SubOrg Application', - template_data=template_data) - Scheduler.objects.create(command='send_email', - data=scheduler_data) + template_data = {"suborg_name": suborg_name} + scheduler_data = build_send_mail_json( + settings.ADMINS, + template="suborg_application_notification.html", + subject="Review new/updated SubOrg Application", + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) def send_review(self): self.accepted = False @@ -363,18 +400,18 @@ def send_review(self): suborg_name = self.suborg_name template_data = { - 'gsoc_year': self.gsoc_year.gsoc_year, - 'suborg_name': suborg_name, - 'message': self.last_message, - } - scheduler_data = build_send_mail_json(self.suborg_admin_email, - template='suborg_review.html', - subject='Review your SubOrg Application' - ' for GSoC@PSF {}'. - format(self.gsoc_year.gsoc_year), - template_data=template_data) - Scheduler.objects.create(command='send_email', - data=scheduler_data) + "gsoc_year": self.gsoc_year.gsoc_year, + "suborg_name": suborg_name, + "message": self.last_message, + } + scheduler_data = build_send_mail_json( + self.suborg_admin_email, + template="suborg_review.html", + subject="Review your SubOrg Application" + " for GSoC@PSF {}".format(self.gsoc_year.gsoc_year), + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) class ReaddUser(models.Model): @@ -392,23 +429,24 @@ def get_queryset(self): class UserProfile(models.Model): - ROLES = ( - (0, 'Others'), - (1, 'Suborg Admin'), - (2, 'Mentor'), - (3, 'Student') - ) + ROLES = ((0, "Others"), (1, "Suborg Admin"), (2, "Mentor"), (3, "Student")) user = models.ForeignKey(User, on_delete=models.CASCADE) - role = models.IntegerField(name='role', choices=ROLES, default=0) - gsoc_year = models.ForeignKey(GsocYear, on_delete=models.CASCADE, null=True, blank=False) - suborg_full_name = models.ForeignKey(SubOrg, on_delete=models.CASCADE, null=True, blank=False) - accepted_proposal_pdf = models.FileField(blank=True, null=True, upload_to=PROPOSALS_PATH) + role = models.IntegerField(name="role", choices=ROLES, default=0) + gsoc_year = models.ForeignKey( + GsocYear, on_delete=models.CASCADE, null=True, blank=False + ) + suborg_full_name = models.ForeignKey( + SubOrg, on_delete=models.CASCADE, null=True, blank=False + ) + accepted_proposal_pdf = models.FileField( + blank=True, null=True, upload_to=PROPOSALS_PATH + ) proposal_confirmed = models.BooleanField(default=False) - app_config = AppHookConfigField(NewsBlogConfig, - verbose_name=_('Section'), - blank=True, null=True,) - hidden = models.BooleanField(name='hidden', default=False) + app_config = AppHookConfigField( + NewsBlogConfig, verbose_name=_("Section"), blank=True, null=True + ) + hidden = models.BooleanField(name="hidden", default=False) reminder_disabled = models.BooleanField(default=False) current_blog_count = models.IntegerField(default=0) github_handle = models.TextField(null=True, blank=True, max_length=100) @@ -423,15 +461,20 @@ def confirm_proposal(self): class UserDetails(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) - deactivation_date = models.DateTimeField(name='deactivation_date', blank=True, null=True) + deactivation_date = models.DateTimeField( + name="deactivation_date", blank=True, null=True + ) class Meta: - verbose_name_plural = 'User details' + verbose_name_plural = "User details" def save(self, *args, **kwargs): if self.deactivation_date: - s = Scheduler(command='deactivate_user', data=self.user.pk, - activation_date=self.deactivation_date) + s = Scheduler( + command="deactivate_user", + data=self.user.pk, + activation_date=self.deactivation_date, + ) s.save() super(UserDetails, self).save(*args, **kwargs) @@ -439,21 +482,25 @@ def save(self, *args, **kwargs): class Scheduler(models.Model): commands = ( - ('send_email', 'send_email'), - ('send_irc_msg', 'send_irc_msg'), - ('revoke_student_permissions', 'revoke_student_permissions'), - ('send_reg_reminder', 'send_reg_reminder'), - ('add_blog_counter', 'add_blog_counter'), - ('update_site_template', 'update_site_template'), - ('archive_gsoc_pages', 'archive_gsoc_pages') - ) + ("send_email", "send_email"), + ("send_irc_msg", "send_irc_msg"), + ("revoke_student_permissions", "revoke_student_permissions"), + ("send_reg_reminder", "send_reg_reminder"), + ("add_blog_counter", "add_blog_counter"), + ("update_site_template", "update_site_template"), + ("archive_gsoc_pages", "archive_gsoc_pages"), + ) id = models.AutoField(primary_key=True) - command = models.CharField(name='command', max_length=40, choices=commands) - activation_date = models.DateTimeField(name='activation_date', null=True, blank=True) - data = models.TextField(name='data') - success = models.BooleanField(name='success', null=True) - last_error = models.TextField(name='last_error', null=True, default=None, blank=True) + command = models.CharField(name="command", max_length=40, choices=commands) + activation_date = models.DateTimeField( + name="activation_date", null=True, blank=True + ) + data = models.TextField(name="data") + success = models.BooleanField(name="success", null=True) + last_error = models.TextField( + name="last_error", null=True, default=None, blank=True + ) created = models.DateTimeField(auto_now_add=True) def __str__(self): @@ -462,11 +509,11 @@ def __str__(self): class Builder(models.Model): categories = ( - ('build_pre_blog_reminders', 'build_pre_blog_reminders'), - ('build_post_blog_reminders', 'build_post_blog_reminders'), - ('build_revoke_student_perms', 'build_revoke_student_perms'), - ('build_remove_user_details', 'build_remove_user_details'), - ) + ("build_pre_blog_reminders", "build_pre_blog_reminders"), + ("build_post_blog_reminders", "build_post_blog_reminders"), + ("build_revoke_student_perms", "build_revoke_student_perms"), + ("build_remove_user_details", "build_remove_user_details"), + ) category = models.CharField(max_length=40, choices=categories) activation_date = models.DateTimeField(null=True, blank=True) @@ -484,15 +531,12 @@ class Timeline(models.Model): def add_calendar(self): if not self.calendar_id: - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "rb") as token: creds = pickle.load(token) - service = build('calendar', 'v3', credentials=creds) - calendar = { - 'summary': 'GSoC @ PSF Calendar', - 'timezone': 'UTC', - } + service = build("calendar", "v3", credentials=creds) + calendar = {"summary": "GSoC @ PSF Calendar", "timezone": "UTC"} calendar = service.calendars().insert(body=calendar).execute() - self.calendar_id = calendar.get('id') + self.calendar_id = calendar.get("id") self.save() @@ -500,51 +544,56 @@ class Event(models.Model): title = models.CharField(max_length=100) start_date = models.DateField() end_date = models.DateField(null=True, blank=True) - timeline = models.ForeignKey(Timeline, on_delete=models.CASCADE, null=True, - blank=True) + timeline = models.ForeignKey( + Timeline, on_delete=models.CASCADE, null=True, blank=True + ) event_id = models.CharField(max_length=255, null=True, blank=True) @property def calendar_link(self): if self.event_id: - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "rb") as token: creds = pickle.load(token) - service = build('calendar', 'v3', credentials=creds) - event = service.events().get(calendarId=self.timeline.calendar_id, - eventId=self.event_id).execute() - return event.get('htmlLink', None) + service = build("calendar", "v3", credentials=creds) + event = ( + service.events() + .get(calendarId=self.timeline.calendar_id, eventId=self.event_id) + .execute() + ) + return event.get("htmlLink", None) return None def add_to_calendar(self): - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "rb") as token: creds = pickle.load(token) - service = build('calendar', 'v3', credentials=creds) + service = build("calendar", "v3", credentials=creds) event = { - 'summary': self.title, - 'start': { - 'date': self.start_date.strftime('%Y-%m-%d') - }, - 'end': { - 'date': self.end_date.strftime('%Y-%m-%d') - }, - } - calendar_id = self.timeline.calendar_id if self.timeline else 'primary' + "summary": self.title, + "start": {"date": self.start_date.strftime("%Y-%m-%d")}, + "end": {"date": self.end_date.strftime("%Y-%m-%d")}, + } + calendar_id = self.timeline.calendar_id if self.timeline else "primary" if not self.event_id: - event = service.events().insert(calendarId=calendar_id, body=event).execute() - self.event_id = event.get('id') + event = ( + service.events() + .insert(calendarId=calendar_id, body=event) + .execute() + ) + self.event_id = event.get("id") self.save() else: - service.events().update(calendarId=calendar_id, - eventId=self.event_id, - body=event).execute() + service.events().update( + calendarId=calendar_id, eventId=self.event_id, body=event + ).execute() def delete_from_calendar(self): if self.event_id: - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "rb") as token: creds = pickle.load(token) - service = build('calendar', 'v3', credentials=creds) - service.events().delete(calendarId=self.timeline.calendar_id, - eventId=self.event_id).execute() + service = build("calendar", "v3", credentials=creds) + service.events().delete( + calendarId=self.timeline.calendar_id, eventId=self.event_id + ).execute() def save(self, *args, **kwargs): if not self.end_date: @@ -559,82 +608,89 @@ class BlogPostHistory(models.Model): class BlogPostDueDate(models.Model): - categories = ( - (0, 'Weekly Check-In'), - (1, 'Blog Post'), - ) + categories = ((0, "Weekly Check-In"), (1, "Blog Post")) class Meta: - ordering = ['date'] - title = models.CharField(max_length=100, default='Weekly Blog Post Due') + ordering = ["date"] + + title = models.CharField(max_length=100, default="Weekly Blog Post Due") date = models.DateField() - timeline = models.ForeignKey(Timeline, on_delete=models.CASCADE, null=True, - blank=True) - add_counter_scheduler = models.ForeignKey(Scheduler, on_delete=models.CASCADE, null=True, - blank=True) - pre_blog_reminder_builder = models.ForeignKey(Builder, on_delete=models.CASCADE, - null=True, blank=True, - related_name='pre') + timeline = models.ForeignKey( + Timeline, on_delete=models.CASCADE, null=True, blank=True + ) + add_counter_scheduler = models.ForeignKey( + Scheduler, on_delete=models.CASCADE, null=True, blank=True + ) + pre_blog_reminder_builder = models.ForeignKey( + Builder, on_delete=models.CASCADE, null=True, blank=True, related_name="pre" + ) post_blog_reminder_builder = models.ManyToManyField(Builder, blank=True) event_id = models.CharField(max_length=255, null=True, blank=True) category = models.IntegerField(choices=categories, null=True, blank=True) def add_to_calendar(self): - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "rb") as token: creds = pickle.load(token) - service = build('calendar', 'v3', credentials=creds) + service = build("calendar", "v3", credentials=creds) event = { - 'summary': self.title, - 'start': { - 'date': self.date.strftime('%Y-%m-%d') - }, - 'end': { - 'date': self.date.strftime('%Y-%m-%d') - }, - } - calendar_id = self.timeline.calendar_id if self.timeline else 'primary' + "summary": self.title, + "start": {"date": self.date.strftime("%Y-%m-%d")}, + "end": {"date": self.date.strftime("%Y-%m-%d")}, + } + calendar_id = self.timeline.calendar_id if self.timeline else "primary" if not self.event_id: - event = service.events().insert(calendarId=calendar_id, body=event).execute() - self.event_id = event.get('id') + event = ( + service.events() + .insert(calendarId=calendar_id, body=event) + .execute() + ) + self.event_id = event.get("id") self.save() else: - service.events().update(calendarId=calendar_id, - eventId=self.event_id, - body=event).execute() + service.events().update( + calendarId=calendar_id, eventId=self.event_id, body=event + ).execute() def delete_from_calendar(self): if self.event_id: - with open(os.path.join(BASE_DIR, 'google_api_token.pickle'), 'rb') as token: + with open(os.path.join(BASE_DIR, "google_api_token.pickle"), "rb") as token: creds = pickle.load(token) - service = build('calendar', 'v3', credentials=creds) - service.events().delete(calendarId=self.timeline.calendar_id, - eventId=self.event_id).execute() + service = build("calendar", "v3", credentials=creds) + service.events().delete( + calendarId=self.timeline.calendar_id, eventId=self.event_id + ).execute() def create_scheduler(self): - s = Scheduler.objects.create(command='add_blog_counter', - activation_date=self.date + datetime.timedelta(days=-6), - data='{}') + s = Scheduler.objects.create( + command="add_blog_counter", + activation_date=self.date + datetime.timedelta(days=-6), + data="{}", + ) self.add_counter_scheduler = s self.save() def create_builders(self): - builder_data = json.dumps({ - 'due_date_pk': self.pk - }) + builder_data = json.dumps({"due_date_pk": self.pk}) - s = Builder.objects.create(category='build_pre_blog_reminders', - activation_date=self.date + datetime.timedelta(days=-3), - data=builder_data) + s = Builder.objects.create( + category="build_pre_blog_reminders", + activation_date=self.date + datetime.timedelta(days=-3), + data=builder_data, + ) self.pre_blog_reminder_builder = s - s = Builder.objects.create(category='build_post_blog_reminders', - activation_date=self.date + datetime.timedelta(days=1), - data=builder_data) + s = Builder.objects.create( + category="build_post_blog_reminders", + activation_date=self.date + datetime.timedelta(days=1), + data=builder_data, + ) self.post_blog_reminder_builder.add(s) - s = Builder.objects.create(category='build_post_blog_reminders', - activation_date=self.date + datetime.timedelta(days=3), - data=builder_data) + s = Builder.objects.create( + category="build_post_blog_reminders", + activation_date=self.date + datetime.timedelta(days=3), + data=builder_data, + ) self.post_blog_reminder_builder.add(s) self.save() @@ -646,29 +702,37 @@ class GsocEndDate(models.Model): class PageNotification(models.Model): - message = models.TextField(name='message') - user = models.ForeignKey(User, name='user', - on_delete=models.CASCADE) - page = models.ForeignKey(Page, name='page', related_name='notifications', - on_delete=models.CASCADE) - pubished_page = models.ForeignKey(Page, name='published_page', - related_name='notifications_for_published', - on_delete=models.CASCADE) + message = models.TextField(name="message") + user = models.ForeignKey(User, name="user", on_delete=models.CASCADE) + page = models.ForeignKey( + Page, name="page", related_name="notifications", on_delete=models.CASCADE + ) + pubished_page = models.ForeignKey( + Page, + name="published_page", + related_name="notifications_for_published", + on_delete=models.CASCADE, + ) def save(self, *args, **kwargs): if self.page and self.page.publisher_is_draft: page = self.page - published_page = Page.objects.filter(node_id=page.node_id, - publisher_is_draft=False).first() + published_page = Page.objects.filter( + node_id=page.node_id, publisher_is_draft=False + ).first() self.published_page = published_page - perm = PagePermission.objects.filter(page=page).filter(user=self.user).first() + perm = ( + PagePermission.objects.filter(page=page).filter(user=self.user).first() + ) if self.user.is_superuser or (perm and perm.can_change): super().save(*args, **kwargs) else: - raise ValidationError(message='User does not have permissions on this page') + raise ValidationError( + message="User does not have permissions on this page" + ) else: - raise ValidationError(message='Add notification on unpublished page') + raise ValidationError(message="Add notification on unpublished page") class ProposalTextValidator: @@ -676,10 +740,13 @@ def find_all_emails(self, text): """ Returns all emails in the text in a list. """ - quick_email_pattern = re.compile(""" + quick_email_pattern = re.compile( + """ [a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@ (?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? - """, re.X) + """, + re.X, + ) emails = re.findall(quick_email_pattern, text) real_emails = [] for email in emails: @@ -694,10 +761,10 @@ def find_all_possible_phone_numbers(self, text): """ Returns all possible phone numbers in a list. """ - matcher = PhoneNumberMatcher(text, 'US') + matcher = PhoneNumberMatcher(text, "US") all_numbers = list(iter(matcher)) all_number_strings = [x.raw_string for x in all_numbers] - ptn = re.compile(r'\+?[0-9][0-9\(\)\-\ ]{3,}[0-9]', re.A | re.M) + ptn = re.compile(r"\+?[0-9][0-9\(\)\-\ ]{3,}[0-9]", re.A | re.M) maybe_numbers = re.findall(ptn, text) for maybe_number in maybe_numbers: if maybe_number in all_number_strings: @@ -722,7 +789,7 @@ def validate(self, text): "emails": emails, "possible_phone_numbers": possible_phone_numbers, "locations": locations, - } + } raise ValidationError(message=message) def __call__(self, text): @@ -734,12 +801,12 @@ def get_help_text(self): class AddUserLog(models.Model): class Meta: - verbose_name = 'Add Users ' \ - '(The invites will be sent to the emails on save)' - verbose_name_plural = 'Add Users ' \ - '(The invites will be sent to the emails on save)' - log_id = models.CharField(max_length=36, - default=gen_uuid_str) + verbose_name = "Add Users " "(The invites will be sent to the emails on save)" + verbose_name_plural = ( + "Add Users " "(The invites will be sent to the emails on save)" + ) + + log_id = models.CharField(max_length=36, default=gen_uuid_str) def __str__(self): return self.log_id @@ -749,21 +816,40 @@ class RegLink(models.Model): is_used = models.BooleanField(default=False, editable=False) reglink_id = models.CharField(max_length=36, default=gen_uuid_str, editable=False) created_at = models.DateTimeField(auto_now_add=True) - user_role = models.IntegerField(name="user_role", - choices=UserProfile.ROLES, default=0, null=True, blank=False, - ) - user_suborg = models.ForeignKey(SubOrg, name="user_suborg", - on_delete=models.CASCADE, null=True, blank=False) - user_gsoc_year = models.ForeignKey(GsocYear, name="user_gsoc_year", - on_delete=models.CASCADE, null=True, blank=False) - adduserlog = models.ForeignKey(AddUserLog, on_delete=models.CASCADE, - null=True, blank=True, related_name='reglinks') - email = models.CharField(null=False, blank=False, - default='', max_length=300, validators=[validate_email]) - scheduler = models.ForeignKey(Scheduler, null=True, - blank=True, on_delete=models.CASCADE, editable=False) - reminder = models.ForeignKey(Scheduler, null=True, related_name='reglinks', - blank=True, on_delete=models.CASCADE, editable=False) + user_role = models.IntegerField( + name="user_role", choices=UserProfile.ROLES, default=0, null=True, blank=False + ) + user_suborg = models.ForeignKey( + SubOrg, name="user_suborg", on_delete=models.CASCADE, null=True, blank=False + ) + user_gsoc_year = models.ForeignKey( + GsocYear, + name="user_gsoc_year", + on_delete=models.CASCADE, + null=True, + blank=False, + ) + adduserlog = models.ForeignKey( + AddUserLog, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="reglinks", + ) + email = models.CharField( + null=False, blank=False, default="", max_length=300, validators=[validate_email] + ) + scheduler = models.ForeignKey( + Scheduler, null=True, blank=True, on_delete=models.CASCADE, editable=False + ) + reminder = models.ForeignKey( + Scheduler, + null=True, + related_name="reglinks", + blank=True, + on_delete=models.CASCADE, + editable=False, + ) send_notifications = models.BooleanField(default=True) @property @@ -785,34 +871,43 @@ def is_sent(self): def __str__(self): sent = self.is_sent if sent: - sent_str = 'Sent.' + sent_str = "Sent." else: - sent_str = 'Not sent.' + sent_str = "Not sent." return f"Register Link {self.url} for {self.email}. {sent_str}" def is_usable(self): timenow = timezone.now() return (not self.is_used) and self.created_at < timenow - def create_user(self, *args, is_staff=True, reminder_disabled=False, - github_handle=None, **kwargs): + def create_user( + self, + *args, + is_staff=True, + reminder_disabled=False, + github_handle=None, + **kwargs, + ): namespace = str(uuid.uuid4()) - email = kwargs.get('email', self.email) - user, status = User.objects.get_or_create(*args, is_staff=is_staff, - email=email, **kwargs) + email = kwargs.get("email", self.email) + user, status = User.objects.get_or_create( + *args, is_staff=is_staff, email=email, **kwargs + ) if not status: profiles = user.userprofile_set.all() for _ in profiles: github_handle = profile.github_handle - + role = {k: v for v, k in UserProfile.ROLES} - profile = UserProfile.objects.create(user=user, role=self.user_role, - gsoc_year=self.user_gsoc_year, - suborg_full_name=self.user_suborg, - reminder_disabled=reminder_disabled, - github_handle=github_handle - ) - if self.user_role != role.get('Student', 3): + profile = UserProfile.objects.create( + user=user, + role=self.user_role, + gsoc_year=self.user_gsoc_year, + suborg_full_name=self.user_suborg, + reminder_disabled=reminder_disabled, + github_handle=github_handle, + ) + if self.user_role != role.get("Student", 3): return user # setup blog @@ -822,30 +917,43 @@ def create_user(self, *args, is_staff=True, reminder_disabled=False, app_config.save() profile.app_config = app_config profile.save() - blog_list_page = Page.objects.\ - filter(application_namespace='blogs_list').\ - filter(publisher_is_draft=True).first() - page = api.create_page(blogname, - get_cms_setting('TEMPLATES')[0][0], - 'en', published=True, - publication_date=timezone.now(), - apphook=app_config.cmsapp, - apphook_namespace=namespace, - parent=blog_list_page) + blog_list_page = ( + Page.objects.filter(application_namespace="blogs_list") + .filter(publisher_is_draft=True) + .first() + ) + page = api.create_page( + blogname, + get_cms_setting("TEMPLATES")[0][0], + "en", + published=True, + publication_date=timezone.now(), + apphook=app_config.cmsapp, + apphook_namespace=namespace, + parent=blog_list_page, + ) su = User.objects.filter(is_superuser=True).first() - page = api.publish_page(page, su, 'en') + page = api.publish_page(page, su, "en") PagePermission.objects.create(user=user, page=page) permissions = list() - permissions.append(Permission.objects.filter(codename='add_article').first()) - permissions.append(Permission.objects.filter(codename='change_article').first()) - permissions.append(Permission.objects.filter(codename='delete_article').first()) - permissions.append(Permission.objects.filter(codename='view_article').first()) - permissions.append(Permission.objects.filter(codename='add_pagenotification').first()) - permissions.append(Permission.objects.filter(codename='change_pagenotification').first()) - permissions.append(Permission.objects.filter(codename='delete_pagenotification').first()) - permissions.append(Permission.objects.filter(codename='view_pagenotification').first()) + permissions.append(Permission.objects.filter(codename="add_article").first()) + permissions.append(Permission.objects.filter(codename="change_article").first()) + permissions.append(Permission.objects.filter(codename="delete_article").first()) + permissions.append(Permission.objects.filter(codename="view_article").first()) + permissions.append( + Permission.objects.filter(codename="add_pagenotification").first() + ) + permissions.append( + Permission.objects.filter(codename="change_pagenotification").first() + ) + permissions.append( + Permission.objects.filter(codename="delete_pagenotification").first() + ) + permissions.append( + Permission.objects.filter(codename="view_pagenotification").first() + ) user.user_permissions.set(permissions) mark_urlconf_as_changed() @@ -853,49 +961,52 @@ def create_user(self, *args, is_staff=True, reminder_disabled=False, def create_scheduler(self, trigger_time=timezone.now()): validate_email(self.email) - role = { - 0: 'Others', - 1: 'Suborg Admin', - 2: 'Mentor', - 3: 'Student', - } - scheduler_data = build_send_mail_json(self.email, - template='invite.html', - subject=(f'You have been invited to join ' - f'{self.user_suborg.suborg_name.strip()}' - f' as a {role[self.user_role]} for GSoC ' - f'{self.user_gsoc_year.gsoc_year} with PSF'), - template_data={ - 'register_link': settings.INETLOCATION + self.url, - 'role': self.user_role, - 'gsoc_year': self.user_gsoc_year.gsoc_year, - 'suborg': self.user_suborg.suborg_name.strip()}) - s = Scheduler.objects.create(command='send_email', - activation_date=trigger_time, - data=scheduler_data) + role = {0: "Others", 1: "Suborg Admin", 2: "Mentor", 3: "Student"} + scheduler_data = build_send_mail_json( + self.email, + template="invite.html", + subject=( + f"You have been invited to join " + f"{self.user_suborg.suborg_name.strip()}" + f" as a {role[self.user_role]} for GSoC " + f"{self.user_gsoc_year.gsoc_year} with PSF" + ), + template_data={ + "register_link": settings.INETLOCATION + self.url, + "role": self.user_role, + "gsoc_year": self.user_gsoc_year.gsoc_year, + "suborg": self.user_suborg.suborg_name.strip(), + }, + ) + s = Scheduler.objects.create( + command="send_email", activation_date=trigger_time, data=scheduler_data + ) self.scheduler = s self.save() def create_reminder(self, trigger_time=None): if self.has_scheduler: validate_email(self.email) - scheduler_data = build_send_reminder_json(self.email, - self.pk, - template='registration_reminder.html', - subject='Reminder for registration', - template_data={ - 'register_link': - settings.INETLOCATION + - self.url}) + scheduler_data = build_send_reminder_json( + self.email, + self.pk, + template="registration_reminder.html", + subject="Reminder for registration", + template_data={"register_link": settings.INETLOCATION + self.url}, + ) if not trigger_time: - activation_date = self.scheduler.activation_date + datetime.timedelta(days=3) + activation_date = self.scheduler.activation_date + datetime.timedelta( + days=3 + ) else: activation_date = trigger_time - s = Scheduler.objects.create(command='send_reg_reminder', - activation_date=activation_date, - data=scheduler_data) + s = Scheduler.objects.create( + command="send_reg_reminder", + activation_date=activation_date, + data=scheduler_data, + ) self.reminder = s self.save() else: @@ -907,106 +1018,115 @@ class Comment(models.Model): user = models.ForeignKey(User, null=True, on_delete=models.CASCADE) article = models.ForeignKey(Article, on_delete=models.CASCADE) content = models.CharField(max_length=1100) - parent = models.ForeignKey('self', null=True, - on_delete=models.CASCADE, - related_name='replies') + parent = models.ForeignKey( + "self", null=True, on_delete=models.CASCADE, related_name="replies" + ) created_at = models.DateTimeField(auto_now_add=True) def send_notifications(self): article_link = self.article.get_absolute_url() - comment_link = '{}#comment-{}'.format(article_link, self.pk) + comment_link = "{}#comment-{}".format(article_link, self.pk) template_data = { - 'article': self.article.title, - 'created_at': self.created_at.strftime('%I:%M %p, %d %B %Y'), - 'username': self.username, - 'link': urljoin(settings.INETLOCATION, comment_link), - 'article_owner': self.article.owner.username, - 'parent_comment_owner': self.parent.user.username - } - scheduler_data = build_send_mail_json(self.article.owner.email, - template='comment_notification.html', - subject='{} commented on your article'. - format(self.username), - template_data=template_data) - Scheduler.objects.create(command='send_email', - data=scheduler_data) + "article": self.article.title, + "created_at": self.created_at.strftime("%I:%M %p, %d %B %Y"), + "username": self.username, + "link": urljoin(settings.INETLOCATION, comment_link), + "article_owner": self.article.owner.username, + "parent_comment_owner": self.parent.user.username, + } + scheduler_data = build_send_mail_json( + self.article.owner.email, + template="comment_notification.html", + subject="{} commented on your article".format(self.username), + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) if self.parent and self.parent.user: - scheduler_data = build_send_mail_json(self.parent.user.email, - template='comment_reply_notification.html', - subject='{} replied to your comment'. - format(self.username), - template_data=template_data) - Scheduler.objects.create(command='send_email', - data=scheduler_data) + scheduler_data = build_send_mail_json( + self.parent.user.email, + template="comment_reply_notification.html", + subject="{} replied to your comment".format(self.username), + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) class ArticleReview(models.Model): article = models.OneToOneField(Article, on_delete=models.CASCADE) - last_reviewed_by = models.ForeignKey(User, on_delete=models.CASCADE, - null=True, blank=True, - limit_choices_to={ - 'is_superuser': True, - }) + last_reviewed_by = models.ForeignKey( + User, + on_delete=models.CASCADE, + null=True, + blank=True, + limit_choices_to={"is_superuser": True}, + ) is_reviewed = models.BooleanField(default=False) def save(self, *args, **kwargs): if self.last_reviewed_by: if not self.last_reviewed_by.is_superuser: - raise ValidationError('The user does not have permissions to review an article.') + raise ValidationError( + "The user does not have permissions to review an article." + ) super(ArticleReview, self).save(*args, **kwargs) class SendEmail(models.Model): groups = ( - ('students', 'Students'), - ('mentors', 'Mentors'), - ('suborg_admins', 'Suborg Admins'), - ('admins', 'Admins'), - ('all', 'All') - ) - - to = models.CharField(null=True, blank=True, max_length=255, - help_text='Separate email with a comma') + ("students", "Students"), + ("mentors", "Mentors"), + ("suborg_admins", "Suborg Admins"), + ("admins", "Admins"), + ("all", "All"), + ) + + to = models.CharField( + null=True, blank=True, max_length=255, help_text="Separate email with a comma" + ) to_group = models.CharField(max_length=80, choices=groups, null=True, blank=True) subject = models.CharField(max_length=255) body = models.TextField() activation_date = models.DateTimeField(blank=True, null=True) - scheduler = models.ForeignKey(Scheduler, blank=True, null=True, on_delete=models.CASCADE) + scheduler = models.ForeignKey( + Scheduler, blank=True, null=True, on_delete=models.CASCADE + ) def save(self, *args, **kwargs): if not (self.to or self.to_group): raise ValidationError( message="Any one of the fields 'to' or 'to_group' should be filled." - ) + ) emails = [] if self.to: - emails.extend(self.to.split(',')) + emails.extend(self.to.split(",")) gsoc_year = GsocYear.objects.first() - if self.to_group == 'students': + if self.to_group == "students": ups = UserProfile.objects.filter(role=3, gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) - elif self.to_group == 'mentors': + elif self.to_group == "mentors": ups = UserProfile.objects.filter(role=2, gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) - elif self.to_group == 'suborg_admins': + elif self.to_group == "suborg_admins": ups = UserProfile.objects.filter(role=1, gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) - elif self.to_group == 'all': + elif self.to_group == "all": ups = UserProfile.objects.filter(gsoc_year=gsoc_year).all() emails.extend([_.user.email for _ in ups]) - scheduler_data = build_send_mail_json(emails, - template='generic_email.html', - subject=self.subject, - template_data={ - 'body': self.body - }) - self.scheduler = Scheduler.objects.create(command='send_email', - data=scheduler_data, - activation_date=self.activation_date) + scheduler_data = build_send_mail_json( + emails, + template="generic_email.html", + subject=self.subject, + template_data={"body": self.body}, + ) + self.scheduler = Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=self.activation_date, + ) super(SendEmail, self).save(*args, **kwargs) @@ -1084,14 +1204,18 @@ def event_add_to_calendar(sender, instance, **kwargs): # Publish the event to Github pages @receiver(models.signals.post_save, sender=Event) def event_publish_to_github_pages(sender, instance, **kwargs): - s = Scheduler.objects.filter(command='update_site_template', - data=json.dumps({'template': 'deadlines.html'}), - success=None).all() + s = Scheduler.objects.filter( + command="update_site_template", + data=json.dumps({"template": "deadlines.html"}), + success=None, + ).all() if len(s) == 0: time = timezone.now() + timezone.timedelta(minutes=5) - Scheduler.objects.create(command='update_site_template', - data=json.dumps({'template': 'deadlines.html'}), - activation_date=time) + Scheduler.objects.create( + command="update_site_template", + data=json.dumps({"template": "deadlines.html"}), + activation_date=time, + ) # Delete Event from Calendar when obj is deleted @@ -1118,27 +1242,32 @@ def due_date_add_to_calendar(sender, instance, **kwargs): # Add new builder for GsocEndDate @receiver(models.signals.post_save, sender=GsocEndDate) def add_revoke_perms_builder(sender, instance, **kwargs): - Builder.objects.create(category='build_revoke_student_perms', - activation_date=instance.date) + Builder.objects.create( + category="build_revoke_student_perms", activation_date=instance.date + ) # Add new builder for GsocEndDate @receiver(models.signals.post_save, sender=GsocEndDate) def add_revoke_perms_builder(sender, instance, **kwargs): - Scheduler.objects.create(command='archive_gsoc_pages', - activation_date=instance.date, - data="{}") + Scheduler.objects.create( + command="archive_gsoc_pages", activation_date=instance.date, data="{}" + ) # Publish the duedate to Github pages @receiver(models.signals.post_save, sender=BlogPostDueDate) def duedate_publish_to_github_pages(sender, instance, **kwargs): - s = Scheduler.objects.filter(command='update_site_template', - data=json.dumps({'template': 'deadlines.html'}), - success=None).all() + s = Scheduler.objects.filter( + command="update_site_template", + data=json.dumps({"template": "deadlines.html"}), + success=None, + ).all() if len(s) == 0: - Scheduler.objects.create(command='update_site_template', - data=json.dumps({'template': 'deadlines.html'})) + Scheduler.objects.create( + command="update_site_template", + data=json.dumps({"template": "deadlines.html"}), + ) # Delete BlogPostDueDate from Calendar when obj is deleted @@ -1175,7 +1304,7 @@ def decrease_blog_counter(sender, instance, **kwargs): up = UserProfile.objects.get(app_config=section) if up.current_blog_count > 0: up.current_blog_count -= 1 - print('Decreasing', up.current_blog_count) + print("Decreasing", up.current_blog_count) up.save() diff --git a/gsoc/router.py b/gsoc/router.py index 5d4468da..ad99f14f 100644 --- a/gsoc/router.py +++ b/gsoc/router.py @@ -1,4 +1,4 @@ -class DatabaseAppsRouter(): +class DatabaseAppsRouter: """ def db_for_read(self, model, **hints): diff --git a/gsoc/settings.py b/gsoc/settings.py index 69fb4a39..c97da93a 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -12,13 +12,15 @@ import logging.config import os + try: from settings_local import * except ImportError: - raise Exception('Missing settings_local.py. Did you create it from the template?') + raise Exception("Missing settings_local.py. Did you create it from the template?") -def gettext(s): return s +def gettext(s): + return s DATA_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -29,19 +31,19 @@ def gettext(s): return s # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ -ALLOWED_HOSTS = ['*'] -INTERNAL_IPS = ('127.0.0.1',) +ALLOWED_HOSTS = ["*"] +INTERNAL_IPS = ("127.0.0.1",) -INETLOCATION = 'https://blogs.python-gsoc.org' +INETLOCATION = "https://blogs.python-gsoc.org" # Application definition -ROOT_URLCONF = 'gsoc.urls' +ROOT_URLCONF = "gsoc.urls" # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ -LANGUAGE_CODE = 'en' +LANGUAGE_CODE = "en" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -52,182 +54,172 @@ def gettext(s): return s # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ -STATIC_URL = '/static/' -MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(DATA_DIR, 'media') -STATIC_ROOT = os.path.join(DATA_DIR, 'static') +STATIC_URL = "/static/" +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(DATA_DIR, "media") +STATIC_ROOT = os.path.join(DATA_DIR, "static") -PROPOSALS_PATH = 'proposals/' +PROPOSALS_PATH = "proposals/" -STATICFILES_DIRS = ( - os.path.join(BASE_DIR, 'gsoc', 'static'), -) +STATICFILES_DIRS = (os.path.join(BASE_DIR, "gsoc", "static"),) SITE_ID = 1 TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'gsoc', 'templates'), - os.path.join(BASE_DIR, 'blogs_list', 'templates'), - os.path.join(BASE_DIR, 'suborg', 'templates'), + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "gsoc", "templates"), + os.path.join(BASE_DIR, "blogs_list", "templates"), + os.path.join(BASE_DIR, "suborg", "templates"), + ], + "OPTIONS": { + "context_processors": [ + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "django.template.context_processors.i18n", + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.template.context_processors.media", + "django.template.context_processors.csrf", + "django.template.context_processors.tz", + "sekizai.context_processors.sekizai", + "django.template.context_processors.static", + "cms.context_processors.cms_settings", + "gsoc.context_processors.recaptcha_site_key", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - 'OPTIONS': { - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'django.template.context_processors.i18n', - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.template.context_processors.media', - 'django.template.context_processors.csrf', - 'django.template.context_processors.tz', - 'sekizai.context_processors.sekizai', - 'django.template.context_processors.static', - 'cms.context_processors.cms_settings', - 'gsoc.context_processors.recaptcha_site_key', - ], - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ], - }, }, + } ] MIDDLEWARE = ( - 'django.middleware.cache.UpdateCacheMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'cms.middleware.utils.ApphookReloadMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'cms.middleware.user.CurrentUserMiddleware', - 'cms.middleware.page.CurrentPageMiddleware', - 'cms.middleware.toolbar.ToolbarMiddleware', - 'cms.middleware.language.LanguageCookieMiddleware', - 'django.middleware.cache.FetchFromCacheMiddleware', + "django.middleware.cache.UpdateCacheMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", + "cms.middleware.utils.ApphookReloadMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "cms.middleware.user.CurrentUserMiddleware", + "cms.middleware.page.CurrentPageMiddleware", + "cms.middleware.toolbar.ToolbarMiddleware", + "cms.middleware.language.LanguageCookieMiddleware", + "django.middleware.cache.FetchFromCacheMiddleware", ) INSTALLED_APPS = ( - 'djangocms_admin_style', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.admin', - 'django.contrib.sites', - 'django.contrib.sitemaps', - 'django.contrib.staticfiles', - 'django.contrib.messages', - 'cms', - 'menus', - 'treebeard', - 'sekizai', - 'djangocms_text_ckeditor', - 'djangocms_history', - 'easy_thumbnails', - 'filer', - 'djangocms_audio', - 'djangocms_video', - 'djangocms_file', - 'djangocms_picture', - 'djangocms_column', - 'djangocms_link', - 'djangocms_style', - 'djangocms_snippet', - 'aldryn_apphooks_config', - 'aldryn_categories', - 'aldryn_common', - 'aldryn_newsblog', - 'aldryn_people', - 'aldryn_translation_tools', - 'parler', - 'sortedm2m', - 'taggit', - 'gsoc', - 'blogs_list', - 'suborg', - 'debug_toolbar', - 'django_simple_cookie_consent' + "djangocms_admin_style", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.admin", + "django.contrib.sites", + "django.contrib.sitemaps", + "django.contrib.staticfiles", + "django.contrib.messages", + "cms", + "menus", + "treebeard", + "sekizai", + "djangocms_text_ckeditor", + "djangocms_history", + "easy_thumbnails", + "filer", + "djangocms_audio", + "djangocms_video", + "djangocms_file", + "djangocms_picture", + "djangocms_column", + "djangocms_link", + "djangocms_style", + "djangocms_snippet", + "aldryn_apphooks_config", + "aldryn_categories", + "aldryn_common", + "aldryn_newsblog", + "aldryn_people", + "aldryn_translation_tools", + "parler", + "sortedm2m", + "taggit", + "gsoc", + "blogs_list", + "suborg", + "debug_toolbar", + "django_simple_cookie_consent", ) THUMBNAIL_PROCESSORS = ( - 'easy_thumbnails.processors.colorspace', - 'easy_thumbnails.processors.autocrop', + "easy_thumbnails.processors.colorspace", + "easy_thumbnails.processors.autocrop", # 'easy_thumbnails.processors.scale_and_crop', - 'filer.thumbnail_processors.scale_and_crop_with_subject_location', - 'easy_thumbnails.processors.filters', - 'easy_thumbnails.processors.background', + "filer.thumbnail_processors.scale_and_crop_with_subject_location", + "easy_thumbnails.processors.filters", + "easy_thumbnails.processors.background", ) LANGUAGES = ( # Customize this - ('en', gettext('en')), + ("en", gettext("en")), ) CMS_LANGUAGES = { # Customize this 1: [ { - 'code': 'en', - 'name': gettext('en'), - 'redirect_on_fallback': True, - 'public': True, - 'hide_untranslated': False, - }, - ], - 'default': { - 'redirect_on_fallback': True, - 'public': True, - 'hide_untranslated': False, - }, + "code": "en", + "name": gettext("en"), + "redirect_on_fallback": True, + "public": True, + "hide_untranslated": False, + } + ], + "default": { + "redirect_on_fallback": True, + "public": True, + "hide_untranslated": False, + }, } CMS_TEMPLATES = ( # Customize this - ('fullwidth.html', 'Fullwidth'), - ('sidebar_left.html', 'Sidebar Left'), - ('sidebar_right.html', 'Sidebar Right'), - ('homepage.html', 'Homepage'), - ('gettingstarted.html', 'Getting Started'), - ('ideas.html', 'Project Ideas'), - ('mentors.html', 'Mentors'), - ('schedule.html', 'Schedule'), - ('students.html', 'Students'), - ('contact.html', 'Contact'), - ('myprofile.html', 'My Profile') + ("fullwidth.html", "Fullwidth"), + ("sidebar_left.html", "Sidebar Left"), + ("sidebar_right.html", "Sidebar Right"), + ("homepage.html", "Homepage"), + ("gettingstarted.html", "Getting Started"), + ("ideas.html", "Project Ideas"), + ("mentors.html", "Mentors"), + ("schedule.html", "Schedule"), + ("students.html", "Students"), + ("contact.html", "Contact"), + ("myprofile.html", "My Profile"), ) CMS_PERMISSION = True CMS_PLACEHOLDER_CONF = {} -MIGRATION_MODULES = { - -} +MIGRATION_MODULES = {} # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] -LOGIN_REDIRECT_URL = '/after-login/' +LOGIN_REDIRECT_URL = "/after-login/" # AUTH_USER_MODEL = 'gsoc.User' @@ -239,86 +231,68 @@ def gettext(s): return s LOGGING_CONFIG = None if DEBUG: - ERROR_LEVEL = 'INFO' + ERROR_LEVEL = "INFO" else: - ERROR_LEVEL = 'ERROR' + ERROR_LEVEL = "ERROR" -ERROR_HANDLERS = ['file', 'mail_admins'] +ERROR_HANDLERS = ["file", "mail_admins"] LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': ('%(levelname)s %(asctime)s %(process)d ' - '%(thread)d %(filename)s %(module)s %(funcName)s ' - '%(lineno)d %(message)s') - }, - 'simple': { - 'format': '%(levelname)s: %(message)s' - }, + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": ( + "%(levelname)s %(asctime)s %(process)d " + "%(thread)d %(filename)s %(module)s %(funcName)s " + "%(lineno)d %(message)s" + ) + }, + "simple": {"format": "%(levelname)s: %(message)s"}, + }, + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + "file": { + "level": "DEBUG", + "class": "logging.handlers.TimedRotatingFileHandler", + "filename": os.path.join(BASE_DIR, "logs/pygsoc.log"), + "formatter": "verbose", + "when": "midnight", + "backupCount": 5, + "encoding": "utf-8", + }, + "access_logs": { + "level": "INFO", + "class": "logging.handlers.TimedRotatingFileHandler", + "filename": os.path.join(BASE_DIR, "logs/access.log"), + "formatter": "simple", + "when": "midnight", + "backupCount": 7, + "encoding": "utf-8", }, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } + "mail_admins": { + "level": "ERROR", + "class": "django.utils.log.AdminEmailHandler", + "include_html": True, }, - 'handlers': { - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'verbose' - }, - 'file': { - 'level': 'DEBUG', - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'filename': os.path.join(BASE_DIR, 'logs/pygsoc.log'), - 'formatter': 'verbose', - 'when': 'midnight', - 'backupCount': 5, - 'encoding': 'utf-8', - }, - 'access_logs': { - 'level': 'INFO', - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'filename': os.path.join(BASE_DIR, 'logs/access.log'), - 'formatter': 'simple', - 'when': 'midnight', - 'backupCount': 7, - 'encoding': 'utf-8', - }, - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'include_html': True - }, + }, + "loggers": { + "django": {"handlers": ["file"], "level": "INFO", "propagate": True}, + "django.server": { + "handlers": ["console"], + "level": "DEBUG", + "propagate": False, }, - 'loggers': { - 'django': { - 'handlers': ['file'], - 'level': 'INFO', - 'propagate': True, - }, - 'django.server': { - 'handlers': ['console'], - 'level': 'DEBUG', - 'propagate': False, - }, - 'django.db': { - 'handlers': ['file'], - 'level': 'WARNING', - 'propagate': False, - }, - 'django.security.DisallowedHost': { - 'handlers': ['file'], - 'propagate': False, - }, + "django.db": {"handlers": ["file"], "level": "WARNING", "propagate": False}, + "django.security.DisallowedHost": {"handlers": ["file"], "propagate": False}, # Catch All Logger -- Captures any other logging - '': { - 'handlers': ERROR_HANDLERS, - 'level': ERROR_LEVEL, - } - } + "": {"handlers": ERROR_HANDLERS, "level": ERROR_LEVEL}, + }, } logging.config.dictConfig(LOGGING) @@ -327,40 +301,91 @@ def gettext(s): return s RUNCRON_NUM_WORKERS = 5 RUNCRON_TIMEOUT = 10 -DJANGOCMS_AUDIO_ALLOWED_EXTENSIONS = ['mp3', 'ogg', 'wav'] -DJANGOCMS_VIDEO_ALLOWED_EXTENSIONS = ['mp4', 'webm', 'ogv'] +DJANGOCMS_AUDIO_ALLOWED_EXTENSIONS = ["mp3", "ogg", "wav"] +DJANGOCMS_VIDEO_ALLOWED_EXTENSIONS = ["mp4", "webm", "ogv"] # Ckeditor settings CKEDITOR_SETTINGS = { - 'disableNativeSpellChecker': False, - 'language': '{{ language }}', - 'extraPlugins': 'button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube,codesnippet,notificationaggregator,filetools,uploadwidget,uploadfile,uploadimage', - 'uploadUrl': '/upload/', - 'toolbar': [ - {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', - 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']}, - {'name': 'editing', 'items': ['Find', 'Replace', '-', 'SelectAll', '-', 'Scayt']}, + "disableNativeSpellChecker": False, + "language": "{{ language }}", + "extraPlugins": "button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube,codesnippet,notificationaggregator,filetools,uploadwidget,uploadfile,uploadimage", + "uploadUrl": "/upload/", + "toolbar": [ + { + "name": "clipboard", + "items": [ + "Cut", + "Copy", + "Paste", + "PasteText", + "PasteFromWord", + "-", + "Undo", + "Redo", + ], + }, + { + "name": "editing", + "items": ["Find", "Replace", "-", "SelectAll", "-", "Scayt"], + }, # ['cmsplugins', 'cmswidget'], - {'name': 'settings', 'items': ['Source', 'ShowBlocks', 'Maximize']}, - '/', - {'name': 'basicstyles', - 'items': ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'CopyFormatting', - 'RemoveFormat']}, - {'name': 'paragraph', - 'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', - 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']}, - {'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']}, - {'name': 'insert', 'items': ['Table', 'HorizontalRule', - 'Smiley', 'SpecialChar', 'PageBreak', 'Image', 'Youtube', 'CodeSnippet']}, - '/', - {'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']}, - {'name': 'colors', 'items': ['TextColor', 'BGColor']}, - ], - 'toolbarCanCollapse': False, + {"name": "settings", "items": ["Source", "ShowBlocks", "Maximize"]}, + "/", + { + "name": "basicstyles", + "items": [ + "Bold", + "Italic", + "Underline", + "Strike", + "Subscript", + "Superscript", + "-", + "CopyFormatting", + "RemoveFormat", + ], + }, + { + "name": "paragraph", + "items": [ + "NumberedList", + "BulletedList", + "-", + "Outdent", + "Indent", + "-", + "Blockquote", + "CreateDiv", + "-", + "JustifyLeft", + "JustifyCenter", + "JustifyRight", + "JustifyBlock", + ], + }, + {"name": "links", "items": ["Link", "Unlink", "Anchor"]}, + { + "name": "insert", + "items": [ + "Table", + "HorizontalRule", + "Smiley", + "SpecialChar", + "PageBreak", + "Image", + "Youtube", + "CodeSnippet", + ], + }, + "/", + {"name": "styles", "items": ["Styles", "Format", "Font", "FontSize"]}, + {"name": "colors", "items": ["TextColor", "BGColor"]}, + ], + "toolbarCanCollapse": False, } -TEXT_ADDITIONAL_TAGS = ('iframe',) +TEXT_ADDITIONAL_TAGS = ("iframe",) # IRC CommandBot settings @@ -373,33 +398,122 @@ def gettext(s): return s ALDRYN_NEWSBLOG_DEFAULT_PUBLISHED = True -BLEACH_ALLOWED_TAGS = ['a', 'address', 'em', 'strong', 'b', 'i', 'big', 'small', 'sub', 'sup', - 'cite', 'code', 'img', 'ul', 'ol', 'li', 'dl', 'lh', 'dt', 'dd', 'br', - 'p', 'table', 'th', 'td', 'tr', 'pre', 'blockquote', 'nowiki', 'h1', 'h2', - 'h3', 'h4', 'h5', 'h6', 'hr', 'iframe', 'div', 's', 'u', 'span', 'tbody'] - -BLEACH_ALLOWED_STYLES = ['background', 'background-attachment', 'background-color', - 'background-image', - 'background-position', 'background-repeat', 'border', 'border-bottom', - 'border-bottom-color', 'border-bottom-style', 'border-bottom-width', - 'border-color', 'border-left', 'border-left-color', - 'border-left-style', 'border-left-width', 'border-right', - 'border-right-color', - 'border-right-style', 'border-right-width', 'border-style', 'border-top', - 'border-top-color', - 'border-top-style', 'border-top-width', 'border-width', 'clear', 'clip', - 'color', 'cursor', - 'display', 'filter', 'float', 'font', 'font-family', 'font-size', - 'font-variant', 'font-weight', - 'height', 'left', 'letter-spacing', 'line-height', 'list-style', - 'list-style-image', - 'list-style-position', 'list-style-type', 'margin', 'margin-bottom', - 'margin-left', 'margin-right', - 'margin-top', 'overflow', 'padding', 'padding-bottom', 'padding-left', - 'padding-right', - 'padding-top', 'page-break-after', 'page-break-before', 'position', - 'stroke-dasharray', - 'stroke-dashoffset', 'stroke-width', 'text-align', 'text-decoration', - 'text-indent', - 'text-transform', 'top', 'vertical-align', 'visibility', 'width', - 'z-index'] +BLEACH_ALLOWED_TAGS = [ + "a", + "address", + "em", + "strong", + "b", + "i", + "big", + "small", + "sub", + "sup", + "cite", + "code", + "img", + "ul", + "ol", + "li", + "dl", + "lh", + "dt", + "dd", + "br", + "p", + "table", + "th", + "td", + "tr", + "pre", + "blockquote", + "nowiki", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "iframe", + "div", + "s", + "u", + "span", + "tbody", +] + +BLEACH_ALLOWED_STYLES = [ + "background", + "background-attachment", + "background-color", + "background-image", + "background-position", + "background-repeat", + "border", + "border-bottom", + "border-bottom-color", + "border-bottom-style", + "border-bottom-width", + "border-color", + "border-left", + "border-left-color", + "border-left-style", + "border-left-width", + "border-right", + "border-right-color", + "border-right-style", + "border-right-width", + "border-style", + "border-top", + "border-top-color", + "border-top-style", + "border-top-width", + "border-width", + "clear", + "clip", + "color", + "cursor", + "display", + "filter", + "float", + "font", + "font-family", + "font-size", + "font-variant", + "font-weight", + "height", + "left", + "letter-spacing", + "line-height", + "list-style", + "list-style-image", + "list-style-position", + "list-style-type", + "margin", + "margin-bottom", + "margin-left", + "margin-right", + "margin-top", + "overflow", + "padding", + "padding-bottom", + "padding-left", + "padding-right", + "padding-top", + "page-break-after", + "page-break-before", + "position", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-width", + "text-align", + "text-decoration", + "text-indent", + "text-transform", + "top", + "vertical-align", + "visibility", + "width", + "z-index", +] diff --git a/gsoc/sitemaps.py b/gsoc/sitemaps.py index 94b7da8a..796dec72 100644 --- a/gsoc/sitemaps.py +++ b/gsoc/sitemaps.py @@ -13,17 +13,19 @@ class BlogListSitemap(Sitemap): priority = 0.5 def items(self): - urls = ['/'] + urls = ["/"] blogs = NewsBlogConfig.objects.all() for blog in blogs: try: - p = Page.objects.get(application_namespace=blog.namespace, publisher_is_draft=False) + p = Page.objects.get( + application_namespace=blog.namespace, publisher_is_draft=False + ) urls.append(p.get_absolute_url()) articles = Article.objects.filter(app_config=blog).all() for i in range(len(articles) // 5): - urls.append(f'{p.get_absolute_url()}?page={i + 2}') + urls.append(f"{p.get_absolute_url()}?page={i + 2}") for article in articles: - urls.append(f'{p.get_absolute_url()}{article.slug}/') + urls.append(f"{p.get_absolute_url()}{article.slug}/") except Exception as e: continue return urls diff --git a/gsoc/templatetags/app_tag.py b/gsoc/templatetags/app_tag.py index 8a57371a..fc99800d 100644 --- a/gsoc/templatetags/app_tag.py +++ b/gsoc/templatetags/app_tag.py @@ -22,7 +22,7 @@ def time_zone(context, flag=0): gmtTime = "+00:00" localTime = tz.now() - utcTimeZone = pytz.timezone('UTC') + utcTimeZone = pytz.timezone("UTC") utcTime = datetime.datetime.now(tz=utcTimeZone) all_timezones = pytz.common_timezones TIME_ZONE = "UTC" @@ -30,8 +30,8 @@ def time_zone(context, flag=0): timeZone = pytz.timezone(i) timeFromUTC = str(datetime.datetime.now(tz=timeZone))[-6:] time = datetime.datetime.now(tz=timeZone) - if(time.hour == localTime.hour): - if(abs(time.minute - localTime.minute) <= 1): + if time.hour == localTime.hour: + if abs(time.minute - localTime.minute) <= 1: TIME_ZONE = i gmtTime = timeFromUTC break diff --git a/gsoc/urls.py b/gsoc/urls.py index 3831c5ab..6589f157 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -19,74 +19,97 @@ admin.autodiscover() urlpatterns = [ - url(r'^sitemap.xml', sitemap, { - 'sitemaps': { - 'blog_sitemaps': sitemaps.BlogListSitemap, - } - }), - url(r'^robots.txt', TemplateView.as_view(template_name="robots.txt", - content_type="text/plain"), name="robots_file"), - url(r'^favicon.ico', RedirectView.as_view(url=settings.STATIC_URL + 'favicon.ico')), + url( + r"^sitemap.xml", + sitemap, + {"sitemaps": {"blog_sitemaps": sitemaps.BlogListSitemap}}, + ), + url( + r"^robots.txt", + TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), + name="robots_file", + ), + url(r"^favicon.ico", RedirectView.as_view(url=settings.STATIC_URL + "favicon.ico")), ] # Add Django site authentication urls (for login, logout, password management) urlpatterns += [ - url('accounts/', include('django.contrib.auth.urls')), - url('accounts/new', gsoc.views.new_account_view, name='new_account'), - url('accounts/register', gsoc.views.register_view, name='register'), - url('accounts/change_password', gsoc.views.change_password, name='change_password'), + url("accounts/", include("django.contrib.auth.urls")), + url("accounts/new", gsoc.views.new_account_view, name="new_account"), + url("accounts/register", gsoc.views.register_view, name="register"), + url("accounts/change_password", gsoc.views.change_password, name="change_password"), ] urlpatterns += i18n_patterns( - path('admin/', admin.site.urls), - url(r'^blogs/$', gsoc.views.redirect_blogs_list), - url(r'^blogs/(?P[\w-]+)/$', gsoc.views.redirect_blogs), - url(r'^blogs/(?P[\w-]+)/(?P[\w-]+)/', - gsoc.views.redirect_articles), - url(r'^', include('cms.urls')), + path("admin/", admin.site.urls), + url(r"^blogs/$", gsoc.views.redirect_blogs_list), + url(r"^blogs/(?P[\w-]+)/$", gsoc.views.redirect_blogs), + url( + r"^blogs/(?P[\w-]+)/(?P[\w-]+)/", + gsoc.views.redirect_articles, + ), + url(r"^", include("cms.urls")), ) # This is only needed when using runserver. if settings.DEBUG: import debug_toolbar - urlpatterns = [ - # For django versions before 2.0: - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + urlpatterns urlpatterns = [ - url(r'^media/(?P.*)$', serve, - {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), - ] + staticfiles_urlpatterns() + urlpatterns + # For django versions before 2.0: + url(r"^__debug__/", include(debug_toolbar.urls)) + ] + urlpatterns + urlpatterns = ( + [ + url( + r"^media/(?P.*)$", + serve, + {"document_root": settings.MEDIA_ROOT, "show_indexes": True}, + ) + ] + + staticfiles_urlpatterns() + + urlpatterns + ) # Add upload proposal page and after-login check urlpatterns += [ - url('after-login/', gsoc.views.after_login_view, name='after-login'), - url('upload-proposal/', gsoc.views.upload_proposal_view, name='upload-proposal'), - url('cancel_proposal_upload/', gsoc.views.cancel_proposal_upload_view, name='cancel-proposal-upload'), - url('confirm_proposal/', gsoc.views.confirm_proposal_view, name='confirm-proposal'), + url("after-login/", gsoc.views.after_login_view, name="after-login"), + url("upload-proposal/", gsoc.views.upload_proposal_view, name="upload-proposal"), + url( + "cancel_proposal_upload/", + gsoc.views.cancel_proposal_upload_view, + name="cancel-proposal-upload", + ), + url("confirm_proposal/", gsoc.views.confirm_proposal_view, name="confirm-proposal"), ] # Add comment routes urlpatterns += [ - url('comment/new/', gsoc.views.new_comment, name='new_comment'), - url('comment/delete/', gsoc.views.delete_comment, name='delete_comment') + url("comment/new/", gsoc.views.new_comment, name="new_comment"), + url("comment/delete/", gsoc.views.delete_comment, name="delete_comment"), ] # Review article routes urlpatterns += [ - url(r'^article/review/(?P[0-9]+)/', gsoc.views.review_article, - name='review_article'), - url(r'^article/unpublish/(?P[0-9]+)/', gsoc.views.unpublish_article, - name='unpublish_article'), - url(r'^article/publish/(?P[0-9]+)/', gsoc.views.publish_article, - name='publish_article') + url( + r"^article/review/(?P[0-9]+)/", + gsoc.views.review_article, + name="review_article", + ), + url( + r"^article/unpublish/(?P[0-9]+)/", + gsoc.views.unpublish_article, + name="unpublish_article", + ), + url( + r"^article/publish/(?P[0-9]+)/", + gsoc.views.publish_article, + name="publish_article", + ), ] # Upload images -urlpatterns += [ - url(r'^upload/', gsoc.views.upload_file) -] +urlpatterns += [url(r"^upload/", gsoc.views.upload_file)] # Readd user details urlpatterns += [ - url(r'^readd/(?P[\w-]+)/', gsoc.views.readd_users, name='readd_users') + url(r"^readd/(?P[\w-]+)/", gsoc.views.readd_users, name="readd_users") ] diff --git a/gsoc/views.py b/gsoc/views.py index 93d0cfa5..7091da11 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -2,8 +2,14 @@ from .common.utils.memcached_stats import MemcachedStats from .forms import ProposalUploadForm -from .models import (RegLink, ProposalTextValidator, Comment, ArticleReview, - GsocYear, ReaddUser) +from .models import ( + RegLink, + ProposalTextValidator, + Comment, + ArticleReview, + GsocYear, + ReaddUser, +) import io import os @@ -37,39 +43,38 @@ # handle file upload + @csrf_exempt def upload_file(request): - file = request.FILES['upload'] - filename = str(uuid.uuid4()) + '.' + file.name.split('.')[-1] - filepath = os.path.join('media/uploads', filename) - fileurl = os.path.join('/', filepath) + file = request.FILES["upload"] + filename = str(uuid.uuid4()) + "." + file.name.split(".")[-1] + filepath = os.path.join("media/uploads", filename) + fileurl = os.path.join("/", filepath) abspath = os.path.join(settings.BASE_DIR, filepath) if not os.path.exists(os.path.dirname(abspath)): os.makedirs(os.path.dirname(abspath)) - with open(abspath, 'wb+') as destination: + with open(abspath, "wb+") as destination: for chunk in file.chunks(): destination.write(chunk) - return JsonResponse({ - 'uploaded': 1, - 'fileName': filename, - 'url': fileurl - }) + return JsonResponse({"uploaded": 1, "fileName": filename, "url": fileurl}) + # handle redirect to blogs def redirect_blogs_list(request): - return HttpResponseRedirect(f'/') + return HttpResponseRedirect(f"/") def redirect_blogs(request, blog_name): - return HttpResponseRedirect(f'/{blog_name}/') + return HttpResponseRedirect(f"/{blog_name}/") def redirect_articles(request, blog_name, article_name): - return HttpResponseRedirect(f'/{blog_name}/{article_name}/') + return HttpResponseRedirect(f"/{blog_name}/{article_name}/") + # handle proposal upload @@ -78,12 +83,12 @@ def convert_pdf_to_txt(f): rsrcmgr = PDFResourceManager() retstr = io.StringIO() laparams = LAParams() - device = TextConverter(rsrcmgr, retstr, codec='utf-8', laparams=None) + device = TextConverter(rsrcmgr, retstr, codec="utf-8", laparams=None) interpreter = PDFPageInterpreter(rsrcmgr, device) pagenos = set() - for page in PDFPage.get_pages(f, pagenos, maxpages=0, - caching=True, - check_extractable=True): + for page in PDFPage.get_pages( + f, pagenos, maxpages=0, caching=True, check_extractable=True + ): interpreter.process_page(page) text = retstr.getvalue() f.close() @@ -107,7 +112,7 @@ def scan_proposal(file): try: text = convert_pdf_to_txt(file) except BaseException: - text = '' + text = "" try: v = ProposalTextValidator() v.validate(text) @@ -119,37 +124,33 @@ def scan_proposal(file): @decorators.login_required def after_login_view(request): user = request.user - return shortcuts.redirect('/myprofile') + return shortcuts.redirect("/myprofile") @decorators.login_required @decorators.user_passes_test(is_user_accepted_student) def upload_proposal_view(request): resp = { - 'private_data': { - "emails": [], - "possible_phone_numbers": [], - "locations": [], - }, - 'file_type_valid': False, - 'file_not_too_large': False, - } - if request.method == 'POST': - file = request.FILES.get('accepted_proposal_pdf') - resp['file_type_valid'] = file and file.name.endswith('.pdf') - if len(file.name) > 100 and resp['file_type_valid']: - file.name = str(uuid.uuid4()) + '.pdf' + "private_data": {"emails": [], "possible_phone_numbers": [], "locations": []}, + "file_type_valid": False, + "file_not_too_large": False, + } + if request.method == "POST": + file = request.FILES.get("accepted_proposal_pdf") + resp["file_type_valid"] = file and file.name.endswith(".pdf") + if len(file.name) > 100 and resp["file_type_valid"]: + file.name = str(uuid.uuid4()) + ".pdf" print(file.name) - resp['file_type_valid'] = file and file.name.endswith('.pdf') - resp['file_not_too_large'] = file.size < 20 * 1024 * 1024 - if resp['file_type_valid'] and resp['file_not_too_large']: + resp["file_type_valid"] = file and file.name.endswith(".pdf") + resp["file_not_too_large"] = file.size < 20 * 1024 * 1024 + if resp["file_type_valid"] and resp["file_not_too_large"]: profile = request.user.student_profile() form = ProposalUploadForm(request.POST, request.FILES, instance=profile) if form.is_valid(): form.save() scan_result = scan_proposal(file) if scan_result: - resp['private_data'] = scan_result.message_dict + resp["private_data"] = scan_result.message_dict return JsonResponse(resp) @@ -171,18 +172,18 @@ def confirm_proposal_view(request): def new_account_view(request): - if request.method == 'POST': - email = request.POST.get('email', None) + if request.method == "POST": + email = request.POST.get("email", None) gsoc_year = GsocYear.objects.first() if email: - RegLink.objects.create(user_role=0, - user_gsoc_year=gsoc_year, - email=email) - messages.success(request, 'You will get the registration link sent to your email soon') + RegLink.objects.create(user_role=0, user_gsoc_year=gsoc_year, email=email) + messages.success( + request, "You will get the registration link sent to your email soon" + ) else: - messages.error(request, 'An error occured, try again!') - return shortcuts.redirect('/') - return shortcuts.render(request, 'registration/new_account.html') + messages.error(request, "An error occured, try again!") + return shortcuts.redirect("/") + return shortcuts.render(request, "registration/new_account.html") def register_view(request): @@ -190,7 +191,7 @@ def register_view(request): messages.info(request, "You have been logged out.") logout(request) - reglink_id = request.GET.get('reglink_id', request.POST.get('reglink_id', '')) + reglink_id = request.GET.get("reglink_id", request.POST.get("reglink_id", "")) try: reglink = RegLink.objects.get(reglink_id=reglink_id) reglink_usable = reglink.is_usable() @@ -198,42 +199,47 @@ def register_view(request): reglink_usable = False reglink = None context = { - 'can_register': True, - 'done_registration': False, - 'warning': '', - 'reglink_id': reglink_id, - 'email': getattr(reglink, 'email', 'EMPTY') - } - if reglink_usable is False or request.method == 'GET': - user = User.objects.filter(email=context['email']).first() + "can_register": True, + "done_registration": False, + "warning": "", + "reglink_id": reglink_id, + "email": getattr(reglink, "email", "EMPTY"), + } + if reglink_usable is False or request.method == "GET": + user = User.objects.filter(email=context["email"]).first() if user: reglink.create_user(username=user.username) reglink.is_used = True reglink.save() - messages.success(request, f'A user with {user.email} already exists in our database. ' - f'A new profile has been created. Please login with your ' - f'existing credentials.') - return shortcuts.redirect('/') + messages.success( + request, + f"A user with {user.email} already exists in our database. " + f"A new profile has been created. Please login with your " + f"existing credentials.", + ) + return shortcuts.redirect("/") if reglink_usable is False: - context['can_register'] = False - context['warning'] = 'Your registration link is invalid! Please check again!' - return shortcuts.render(request, 'registration/register.html', context) - if request.method == 'POST': - username = request.POST.get('username', '') - password = request.POST.get('password', '') - password2 = request.POST.get('password2', '') - github_handle = request.POST.get('github_handle', '') - email_opt_in = request.POST.get('email_opt_in') - reminder_disabled = False if email_opt_in == 'on' else True + context["can_register"] = False + context[ + "warning" + ] = "Your registration link is invalid! Please check again!" + return shortcuts.render(request, "registration/register.html", context) + if request.method == "POST": + username = request.POST.get("username", "") + password = request.POST.get("password", "") + password2 = request.POST.get("password2", "") + github_handle = request.POST.get("github_handle", "") + email_opt_in = request.POST.get("email_opt_in") + reminder_disabled = False if email_opt_in == "on" else True info_valid = True registration_success = True if password != password2: - context['warning'] += 'Your password didn\'t match!
    ' + context["warning"] += "Your password didn't match!
    " info_valid = False try: User.objects.get(username=username) info_valid = False - context['warning'] += 'Your username has been used!
    ' + context["warning"] += "Your username has been used!
    " except User.DoesNotExist: pass @@ -241,17 +247,20 @@ def register_view(request): try: password_validation.validate_password(password) except ValidationError as e: - context['warning'] += f'{"
    ".join(e.messages)}
    ' + context["warning"] += f'{"
    ".join(e.messages)}
    ' info_valid = False try: validators.UnicodeUsernameValidator()(username) except ValidationError as e: - context['warning'] += f'{"
    ".join(e.messages)}
    ' + context["warning"] += f'{"
    ".join(e.messages)}
    ' info_valid = False if info_valid: - user = reglink.create_user(username=username, reminder_disabled=reminder_disabled, - github_handle=github_handle) + user = reglink.create_user( + username=username, + reminder_disabled=reminder_disabled, + github_handle=github_handle, + ) user.set_password(password) user.save() else: @@ -262,63 +271,63 @@ def register_view(request): if registration_success: reglink.is_used = True reglink.save() - context['done_registration'] = True - context['warning'] = '' + context["done_registration"] = True + context["warning"] = "" else: - context['done_registration'] = False + context["done_registration"] = False - return shortcuts.render(request, 'registration/register.html', context) + return shortcuts.render(request, "registration/register.html", context) @decorators.login_required def change_password(request): - if request.method == 'POST': + if request.method == "POST": form = PasswordChangeForm(request.user, request.POST) if form.is_valid(): user = form.save() update_session_auth_hash(request, user) - messages.success(request, 'Your password was successfully updated!') - return redirect('change_password') + messages.success(request, "Your password was successfully updated!") + return redirect("change_password") else: - messages.error(request, 'Please correct the error below.') + messages.error(request, "Please correct the error below.") else: form = PasswordChangeForm(request.user) - return shortcuts.render(request, 'registration/change_password.html', { - 'form': form - }) + return shortcuts.render( + request, "registration/change_password.html", {"form": form} + ) @never_cache def new_comment(request): - if request.method == 'POST': + if request.method == "POST": # set environment variable `DISABLE_RECAPTCHA` to disable recaptcha # verification and delete the variable to enable recaptcha verification - disable_recaptcha = os.getenv('DISABLE_RECAPTCHA', None) + disable_recaptcha = os.getenv("DISABLE_RECAPTCHA", None) flag = True if not disable_recaptcha: - recaptcha_response = request.POST.get('g-recaptcha-response') - url = 'https://www.google.com/recaptcha/api/siteverify' + recaptcha_response = request.POST.get("g-recaptcha-response") + url = "https://www.google.com/recaptcha/api/siteverify" payload = { - 'secret': settings.RECAPTCHA_PRIVATE_KEY, - 'response': recaptcha_response - } + "secret": settings.RECAPTCHA_PRIVATE_KEY, + "response": recaptcha_response, + } data = urllib.parse.urlencode(payload).encode() req = urllib.request.Request(url, data=data) response = urllib.request.urlopen(req) result = json.loads(response.read().decode()) - flag = result['success'] + flag = result["success"] if flag: # if score greater than threshold allow to add - comment = request.POST.get('comment') - article_pk = request.POST.get('article') + comment = request.POST.get("comment") + article_pk = request.POST.get("article") article = Article.objects.get(pk=article_pk) - user_pk = request.POST.get('user', None) - parent_pk = request.POST.get('parent', None) + user_pk = request.POST.get("user", None) + parent_pk = request.POST.get("parent", None) if parent_pk: parent = Comment.objects.get(pk=parent_pk) @@ -330,23 +339,31 @@ def new_comment(request): username = user.username else: user = None - username = request.POST.get('username') + username = request.POST.get("username") pf = ProfanityFilter() if pf.is_clean(comment) and pf.is_clean(username): - c = Comment(username=username, content=comment, - user=user, article=article, - parent=parent) + c = Comment( + username=username, + content=comment, + user=user, + article=article, + parent=parent, + ) c.save() else: - messages.add_message(request, messages.ERROR, - 'Abusive content detected! Please refrain\ - from using any indecent words while commenting.') + messages.add_message( + request, + messages.ERROR, + "Abusive content detected! Please refrain\ + from using any indecent words while commenting.", + ) else: - messages.add_message(request, messages.ERROR, - 'reCAPTCHA verification failed.') + messages.add_message( + request, messages.ERROR, "reCAPTCHA verification failed." + ) - redirect_path = request.POST.get('redirect') + redirect_path = request.POST.get("redirect") cache.clear() @@ -360,14 +377,14 @@ def new_comment(request): if redirect_path: return redirect(redirect_path) else: - return redirect('/') + return redirect("/") @decorators.user_passes_test(is_superuser) def delete_comment(request): - if request.method == 'POST': - pk = request.POST.get('comment_pk') - redirect_path = request.POST.get('redirect') + if request.method == "POST": + pk = request.POST.get("comment_pk") + redirect_path = request.POST.get("redirect") if pk: comment = Comment.objects.get(pk=pk) @@ -376,12 +393,12 @@ def delete_comment(request): if redirect_path: return redirect(redirect_path) else: - return redirect('/') + return redirect("/") @decorators.user_passes_test(is_superuser) def review_article(request, article_id): - if request.method == 'GET': + if request.method == "GET": a = Article.objects.get(id=article_id) try: ar = ArticleReview.objects.get(article=a) @@ -390,53 +407,57 @@ def review_article(request, article_id): ar.save() except ArticleReview.DoesNotExist: pass - admin_request = request.GET.get('admin') - if admin_request == 'true': - return redirect(reverse('admin:gsoc_articlereview_change', args=[ar.id])) - return redirect(reverse('{}:article-detail'.format(a.app_config.namespace), args=[a.slug])) + admin_request = request.GET.get("admin") + if admin_request == "true": + return redirect(reverse("admin:gsoc_articlereview_change", args=[ar.id])) + return redirect( + reverse("{}:article-detail".format(a.app_config.namespace), args=[a.slug]) + ) @decorators.login_required def unpublish_article(request, article_id): - if request.method == 'GET': + if request.method == "GET": a = Article.objects.get(id=article_id) if request.user == a.owner or request.user.is_superuser: a.is_published = False a.save() else: - messages.error(request, 'User does not have permission to unpublish article') - return redirect(reverse('{}:article-detail'.format(a.app_config.namespace), args=[a.slug])) + messages.error( + request, "User does not have permission to unpublish article" + ) + return redirect( + reverse("{}:article-detail".format(a.app_config.namespace), args=[a.slug]) + ) @decorators.login_required def publish_article(request, article_id): - if request.method == 'GET': + if request.method == "GET": a = Article.objects.get(id=article_id) if request.user == a.owner or request.user.is_superuser: a.is_published = True a.save() else: - messages.error(request, 'User does not have permission to publish article') - return redirect(reverse('{}:article-detail'.format(a.app_config.namespace), args=[a.slug])) + messages.error(request, "User does not have permission to publish article") + return redirect( + reverse("{}:article-detail".format(a.app_config.namespace), args=[a.slug]) + ) def readd_users(request, uuid): - if request.method == 'GET': + if request.method == "GET": readds = ReaddUser.objects.filter(uuid=uuid) - email = request.GET.get('email') - context = { - "success": False - } + email = request.GET.get("email") + context = {"success": False} if len(readds) > 0: readd = readds.first() if email: readd.readd_user_details(email) - context = { - "success": True - } + context = {"success": True} else: - messages.error('Please provide your email') + messages.error("Please provide your email") else: - messages.error('Incorrect token, please use the correct token') - - return shortcuts.render(request, "readd.html", context) \ No newline at end of file + messages.error("Incorrect token, please use the correct token") + + return shortcuts.render(request, "readd.html", context) diff --git a/manage.py b/manage.py index db0d00f1..080bc15f 100644 --- a/manage.py +++ b/manage.py @@ -2,8 +2,8 @@ import os import sys -if __name__ == '__main__': - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gsoc.settings') +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gsoc.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -11,5 +11,5 @@ "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" - ) from exc + ) from exc execute_from_command_line(sys.argv) diff --git a/settings_local.py.template b/settings_local.py.template index 1ecbf08b..ba7263fe 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -5,22 +5,22 @@ DEBUG = True TEMPLATE_DEBUG = True # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'set to some long unique string' +SECRET_KEY = "set to some long unique string" -#ADMINS = ( +# ADMINS = ( # ('Ad Min', 'admin@example.com'), # ) DATABASES = { - 'default': { - 'CONN_MAX_AGE': 0, - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'NAME': 'project.db', # Or path to database file if using sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - 'USER': '' # Not used with sqlite3. - }, + "default": { + "CONN_MAX_AGE": 0, + "ENGINE": "django.db.backends.sqlite3", # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + "HOST": "", # Set to empty string for localhost. Not used with sqlite3. + "NAME": "project.db", # Or path to database file if using sqlite3. + "PASSWORD": "", # Not used with sqlite3. + "PORT": "", # Set to empty string for default. Not used with sqlite3. + "USER": "", # Not used with sqlite3. + } } # EMAIL CONFIGURATION @@ -28,29 +28,27 @@ DATABASES = { # See: https://docs.djangoproject.com/en/2.1/ref/settings/#email # TODO: Update it with real settings -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_SUBJECT_PREFIX = '[Python-GSoC] ' +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_SUBJECT_PREFIX = "[Python-GSoC] " EMAIL_USE_TLS = False -SERVER_EMAIL = 'no-reply@python-gsoc.org' -EMAIL_HOST = 'localhost' +SERVER_EMAIL = "no-reply@python-gsoc.org" +EMAIL_HOST = "localhost" EMAIL_PORT = 25 -#EMAIL_HOST_USER = "" -#EMAIL_HOST_PASSWORD = "" -REPLY_EMAIL = 'gsoc-admins@python.org' +# EMAIL_HOST_USER = "" +# EMAIL_HOST_PASSWORD = "" +REPLY_EMAIL = "gsoc-admins@python.org" # Admins -ADMINS = 'gsoc-admins@python.org' +ADMINS = "gsoc-admins@python.org" # reCAPTCHA settings # update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually -RECAPTCHA_PRIVATE_KEY = '6LcL0q8UAAAAAFPz31u0Ce9gnbEjhFou19c4MhnQ' -RECAPTCHA_PUBLIC_KEY = '6LcL0q8UAAAAALYynEklThsKgSVZ2B1kubc-Y6br' +RECAPTCHA_PRIVATE_KEY = "6LcL0q8UAAAAAFPz31u0Ce9gnbEjhFou19c4MhnQ" +RECAPTCHA_PUBLIC_KEY = "6LcL0q8UAAAAALYynEklThsKgSVZ2B1kubc-Y6br" # google oauth client_config -GOOGLE_API_SCOPES = [ - 'https://www.googleapis.com/auth/calendar', -] +GOOGLE_API_SCOPES = ["https://www.googleapis.com/auth/calendar"] GOOGLE_API_CLIENT_CONFIG = { "installed": { "client_id": "", @@ -59,22 +57,19 @@ GOOGLE_API_CLIENT_CONFIG = { "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_secret": "p62APV3jXWciaWRzk9D9YAMm", - "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"] + "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"], } } # GITHUB SETTINGS -STATIC_SITE_REPO = 'python-gsoc/python-gsoc.github.io' -GITHUB_ACCESS_TOKEN = '' -GITHUB_FILE_PATH = { - 'deadlines.html': 'deadlines.html', - 'index.html': 'index.html' -} +STATIC_SITE_REPO = "python-gsoc/python-gsoc.github.io" +GITHUB_ACCESS_TOKEN = "" +GITHUB_FILE_PATH = {"deadlines.html": "deadlines.html", "index.html": "index.html"} -#memcached use django.core.cache.backends.memcached.PyLibMCCache +# memcached use django.core.cache.backends.memcached.PyLibMCCache CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', - 'LOCATION': '127.0.0.1:11211', + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + "LOCATION": "127.0.0.1:11211", } } diff --git a/suborg/apps.py b/suborg/apps.py index f8a138be..ea2b7380 100644 --- a/suborg/apps.py +++ b/suborg/apps.py @@ -2,4 +2,4 @@ class SuborgApplicationConfig(AppConfig): - name = 'suborg' + name = "suborg" diff --git a/suborg/urls.py b/suborg/urls.py index a79ae9b9..eb072f25 100644 --- a/suborg/urls.py +++ b/suborg/urls.py @@ -3,19 +3,39 @@ from . import views urlpatterns = [ - url('^$', views.home, name='home'), - url('^application/', include([ - url('^$', views.application_list, name='application_list'), - url('^new/', views.register_suborg, name='register_suborg'), - url('^update/(?P[0-9]+)/', views.update_application, - name='update_application'), - url('^thanks/', views.post_register, name='post_register'), - url(r'^accept/(?P[0-9]+)/', views.accept_application, - name='accept_application'), - # url(r'^reject/(?P[0-9]+)/', views.reject_application, - # name='reject_application'), - ])), - url('^mentor/', include([ - url('^add/(?P[0-9]+)/', views.add_mentor, name='add_mentor') - ])), + url("^$", views.home, name="home"), + url( + "^application/", + include( + [ + url("^$", views.application_list, name="application_list"), + url("^new/", views.register_suborg, name="register_suborg"), + url( + "^update/(?P[0-9]+)/", + views.update_application, + name="update_application", + ), + url("^thanks/", views.post_register, name="post_register"), + url( + r"^accept/(?P[0-9]+)/", + views.accept_application, + name="accept_application", + ), + # url(r'^reject/(?P[0-9]+)/', views.reject_application, + # name='reject_application'), + ] + ), + ), + url( + "^mentor/", + include( + [ + url( + "^add/(?P[0-9]+)/", + views.add_mentor, + name="add_mentor", + ) + ] + ), + ), ] diff --git a/suborg/views.py b/suborg/views.py index 7a4ebd54..66bd0e85 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -19,18 +19,16 @@ def is_suborg_admin(user): def home(request): - return redirect(reverse('suborg:application_list')) + return redirect(reverse("suborg:application_list")) @decorators.login_required def application_list(request): applications = SubOrgDetails.objects.filter(suborg_admin_email=request.user.email) if len(applications) == 0: - return redirect(reverse('suborg:register_suborg')) + return redirect(reverse("suborg:register_suborg")) - return render(request, 'application_list.html', { - 'applications': applications, - }) + return render(request, "application_list.html", {"applications": applications}) @decorators.login_required @@ -38,11 +36,12 @@ def register_suborg(request): email = request.user.email gsoc_year = GsocYear.objects.first() - if request.method == 'GET': - form = SubOrgApplicationForm(initial={'gsoc_year': gsoc_year, - 'suborg_admin_email': email}) + if request.method == "GET": + form = SubOrgApplicationForm( + initial={"gsoc_year": gsoc_year, "suborg_admin_email": email} + ) - elif request.method == 'POST': + elif request.method == "POST": form = SubOrgApplicationForm(request.POST, request.FILES) if form.is_valid(): suborg_details = form.save() @@ -50,11 +49,9 @@ def register_suborg(request): suborg_details.created_at = timezone.now() suborg_details.save() suborg_details.send_update_notification() - return redirect(reverse('suborg:post_register')) + return redirect(reverse("suborg:post_register")) - return render(request, 'register_suborg.html', { - 'form': form - }) + return render(request, "register_suborg.html", {"form": form}) @decorators.login_required @@ -62,15 +59,15 @@ def update_application(request, application_id): email = request.user.email instance = SubOrgDetails.objects.get(id=application_id) if instance.suborg_admin_email != email: - messages.error(request, 'You do not have access to the application') - return redirect(reverse('suborg:application_list')) + messages.error(request, "You do not have access to the application") + return redirect(reverse("suborg:application_list")) message = instance.last_message if instance else None - if request.method == 'GET': + if request.method == "GET": form = SubOrgApplicationForm(instance=instance) - elif request.method == 'POST': + elif request.method == "POST": form = SubOrgApplicationForm(request.POST, request.FILES, instance=instance) if form.is_valid(): suborg_details = form.save() @@ -78,26 +75,26 @@ def update_application(request, application_id): suborg_details.updated_at = timezone.now() suborg_details.save() suborg_details.send_update_notification() - return redirect(reverse('suborg:post_register')) + return redirect(reverse("suborg:post_register")) - return render(request, 'update_suborg.html', { - 'form': form, - 'message': message, - 'id': application_id, - }) + return render( + request, + "update_suborg.html", + {"form": form, "message": message, "id": application_id}, + ) def post_register(request): - if request.method == 'GET': - return render(request, 'post_register.html') + if request.method == "GET": + return render(request, "post_register.html") @decorators.user_passes_test(is_superuser) def accept_application(request, application_id): - if request.method == 'GET': + if request.method == "GET": application = SubOrgDetails.objects.get(id=application_id) application.accept() - return redirect(reverse('admin:gsoc_suborgdetails_change', args=[application_id])) + return redirect(reverse("admin:gsoc_suborgdetails_change", args=[application_id])) # @decorators.user_passes_test(is_superuser) @@ -113,16 +110,18 @@ def add_mentor(request, application_id): application = SubOrgDetails.objects.get(id=application_id) if not application.accepted: - messages.error(request, 'Application not accepted yet! Can not add mentors.') - return redirect(reverse('suborg:application_list')) + messages.error(request, "Application not accepted yet! Can not add mentors.") + return redirect(reverse("suborg:application_list")) if application.suborg_admin_email != request.user.email: - messages.error(request, 'You are not authorized to add mentors for this suborg.') - return redirect(reverse('suborg:application_list')) + messages.error( + request, "You are not authorized to add mentors for this suborg." + ) + return redirect(reverse("suborg:application_list")) - MentorFormSet = modelformset_factory(RegLink, fields=('email', ), extra=4) + MentorFormSet = modelformset_factory(RegLink, fields=("email",), extra=4) - if request.method == 'POST': + if request.method == "POST": formset = MentorFormSet(request.POST) if formset.is_valid(): instances = formset.save(commit=False) @@ -132,14 +131,14 @@ def add_mentor(request, application_id): instance.user_role = 2 instance.save() else: - return render(request, 'add_mentor.html', { - 'formset': formset, - }) + return render(request, "add_mentor.html", {"formset": formset}) - formset = MentorFormSet(queryset=RegLink.objects.filter(user_gsoc_year=application.gsoc_year, - user_suborg=application.suborg, - user_role=2)) + formset = MentorFormSet( + queryset=RegLink.objects.filter( + user_gsoc_year=application.gsoc_year, + user_suborg=application.suborg, + user_role=2, + ) + ) - return render(request, 'add_mentor.html', { - 'formset': formset, - }) + return render(request, "add_mentor.html", {"formset": formset}) From 340e2f9c42f41aed86b6dc0565b6a8ed934ac5c2 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 16 Aug 2019 10:13:40 +0530 Subject: [PATCH 0491/1137] Lint code --- gsoc/admin.py | 28 ++++++++++++++++------------ gsoc/common/utils/irc.py | 2 +- gsoc/settings.py | 6 +++++- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 15f022d8..0ff7f42c 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -139,18 +139,22 @@ def Article_change_view(self, request, object_id, *args, **kwargs): except Person.DoesNotExist: person = Person.objects.create(user=request.user) post_data["author"] = person.pk - post_data[ - "publishing_date_0" - ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" - post_data[ - "initial-publishing_date_0" - ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" - post_data[ - "publishing_date_1" - ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" - post_data[ - "initial-publishing_date_1" - ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" + post_data["publishing_date_0"] = ( + f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-" + f"{str(timenow.day).zfill(2)}" + ) + post_data["initial-publishing_date_0"] = ( + f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-" + f"{str(timenow.day).zfill(2)}" + ) + post_data["publishing_date_1"] = ( + f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:" + f"{str(timenow.second).zfill(2)}" + ) + post_data["initial-publishing_date_1"] = ( + f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:" + f"{str(timenow.second).zfill(2)}" + ) post_data["owner"] = request.user.pk request.GET = data request.POST = post_data diff --git a/gsoc/common/utils/irc.py b/gsoc/common/utils/irc.py index f18d78fd..afb8c42f 100644 --- a/gsoc/common/utils/irc.py +++ b/gsoc/common/utils/irc.py @@ -40,7 +40,7 @@ def parse_data(data): data = json.loads(data) chunk_size = 150 chunks = [ - data["message"][i : i + chunk_size] + data["message"][i:i + chunk_size] for i in range(0, len(data["message"]), chunk_size) ] num_chunks = len(chunks) diff --git a/gsoc/settings.py b/gsoc/settings.py index c97da93a..ef00bb78 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -309,7 +309,11 @@ def gettext(s): CKEDITOR_SETTINGS = { "disableNativeSpellChecker": False, "language": "{{ language }}", - "extraPlugins": "button,clipboard,dialog,dialogui,image2,lineutils,notification,toolbar,widget,widgetselection,youtube,codesnippet,notificationaggregator,filetools,uploadwidget,uploadfile,uploadimage", + "extraPlugins": ( + "button,clipboard,dialog,dialogui,image2,lineutils,notification," + "toolbar,widget,widgetselection,youtube,codesnippet,notificationaggregator," + "filetools,uploadwidget,uploadfile,uploadimage" + ), "uploadUrl": "/upload/", "toolbar": [ { From 50dab028ccc1f00f8edd2e62d7f729a578e318d0 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 16 Aug 2019 10:15:34 +0530 Subject: [PATCH 0492/1137] Lint code --- gsoc/admin.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 0ff7f42c..19bfacd8 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -179,18 +179,22 @@ def Article_add_view(self, request, *args, **kwargs): person = Person.objects.create(user=request.user) post_data["author"] = person.pk - post_data[ - "publishing_date_0" - ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" - post_data[ - "initial-publishing_date_0" - ] = f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-{str(timenow.day).zfill(2)}" - post_data[ - "publishing_date_1" - ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" - post_data[ - "initial-publishing_date_1" - ] = f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:{str(timenow.second).zfill(2)}" + post_data["publishing_date_0"] = ( + f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-" + f"{str(timenow.day).zfill(2)}" + ) + post_data["initial-publishing_date_0"] = ( + f"{str(timenow.year)}-{str(timenow.month).zfill(2)}-" + f"{str(timenow.day).zfill(2)}" + ) + post_data["publishing_date_1"] = ( + f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:" + f"{str(timenow.second).zfill(2)}" + ) + post_data["initial-publishing_date_1"] = ( + f"{str(timenow.hour).zfill(2)}:{str(timenow.minute).zfill(2)}:" + f"{str(timenow.second).zfill(2)}" + ) post_data["owner"] = request.user.pk post_data["slug"] = "" post_data["meta_title"] = "" From 425099b018da602cde5d284b74a06c5b3ad90fb6 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 16 Aug 2019 11:17:28 +0530 Subject: [PATCH 0493/1137] Make PR instead of direct commit --- gsoc/common/utils/commands.py | 11 +++++++++-- gsoc/common/utils/irc.py | 2 +- gsoc/common/utils/tools.py | 24 ++++++++++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index e4f1ee78..4b23796e 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -1,5 +1,6 @@ import json from smtplib import SMTPResponseException, SMTPSenderRefused +from datetime import datetime from django.contrib.auth.models import User, Permission from django.conf import settings @@ -21,6 +22,8 @@ push_site_template, archive_current_gsoc_files, push_images, + create_branch, + create_pull_request, ) @@ -128,6 +131,7 @@ def update_site_template(scheduler: Scheduler): try: template = json.loads(scheduler.data)["template"] gsoc_year = GsocYear.objects.first() + branch = "master" if template == "deadlines.html": context = { "events": Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), @@ -142,13 +146,14 @@ def update_site_template(scheduler: Scheduler): gsoc_year=gsoc_year, accepted=True ).all() suborg_list = [] + branch = create_branch(str(datetime.now())) for suborg in suborgs: f = open(suborg.logo.path, "rb") lines = f.readlines() content = b"" for line in lines: content = content + line - push_images(suborg.logo.name, content) + push_images(suborg.logo.name, content, branch) _ = { "name": suborg.suborg.suborg_name, "description": suborg.description, @@ -167,7 +172,9 @@ def update_site_template(scheduler: Scheduler): suborg_list.append(_) context = {"suborgs": suborg_list} content = render_site_template(template, context) - push_site_template(settings.GITHUB_FILE_PATH[template], content) + push_site_template(settings.GITHUB_FILE_PATH[template], content, branch) + if branch != "master": + create_pull_request(branch) except Exception as e: return str(e) diff --git a/gsoc/common/utils/irc.py b/gsoc/common/utils/irc.py index afb8c42f..f18d78fd 100644 --- a/gsoc/common/utils/irc.py +++ b/gsoc/common/utils/irc.py @@ -40,7 +40,7 @@ def parse_data(data): data = json.loads(data) chunk_size = 150 chunks = [ - data["message"][i:i + chunk_size] + data["message"][i : i + chunk_size] for i in range(0, len(data["message"]), chunk_size) ] num_chunks = len(chunks) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 4fed1183..e3c5a869 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -62,18 +62,34 @@ def render_site_template(template, context): return template.render(context) -def push_site_template(file_path, content): +def create_branch(target_branch, source_branch="master"): + g = Github(settings.GITHUB_ACCESS_TOKEN) + repo = g.get_repo(settings.STATIC_SITE_REPO) + sb = repo.get_branch(source_branch) + repo.create_git_ref(ref=f"refs/head/{target_branch}", sha=sb.commit.sha) + return target_branch + + +def create_pull_request(source_branch, target_branch="master"): + g = Github(settings.GITHUB_ACCESS_TOKEN) + repo = g.get_repo(settings.STATIC_SITE_REPO) + repo.create_pull( + title="Site Template Update", body="", base=target_branch, head=source_branch + ) + + +def push_site_template(file_path, content, branch): content = content.encode() g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) f = repo.get_contents(file_path) - repo.update_file(f.path, f"Update {file_path}", content, f.sha) + repo.update_file(f.path, f"Update {file_path}", content, f.sha, branch=branch) -def push_images(file_path, content): +def push_images(file_path, content, branch): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) - repo.create_file(file_path, f"Add {file_path} logo", content) + repo.create_file(file_path, f"Add {file_path} logo", content, branch=branch) def is_year(file_name): From 7217f310f492304ca6c85c3ed24c7f9aa089021d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 16 Aug 2019 11:54:57 +0530 Subject: [PATCH 0494/1137] Update PR instead of commit --- gsoc/common/utils/commands.py | 5 +++-- gsoc/common/utils/tools.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 4b23796e..c36b4f65 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -1,9 +1,9 @@ import json from smtplib import SMTPResponseException, SMTPSenderRefused -from datetime import datetime from django.contrib.auth.models import User, Permission from django.conf import settings +from django.utils import timezone from .irc import send_message @@ -146,7 +146,8 @@ def update_site_template(scheduler: Scheduler): gsoc_year=gsoc_year, accepted=True ).all() suborg_list = [] - branch = create_branch(str(datetime.now())) + branch_name = str(timezone.now().timestamp()).replace('.', '-') + branch = create_branch(f'update-template-{branch_name}') for suborg in suborgs: f = open(suborg.logo.path, "rb") lines = f.readlines() diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index e3c5a869..d1998afb 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -66,7 +66,7 @@ def create_branch(target_branch, source_branch="master"): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) sb = repo.get_branch(source_branch) - repo.create_git_ref(ref=f"refs/head/{target_branch}", sha=sb.commit.sha) + repo.create_git_ref(ref=f"refs/heads/{target_branch}", sha=sb.commit.sha) return target_branch From c496177a845b9cdb71c4999edf348fdec5a41437 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 16 Aug 2019 11:55:48 +0530 Subject: [PATCH 0495/1137] lint --- gsoc/common/utils/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index c36b4f65..26cbbcc8 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -146,8 +146,8 @@ def update_site_template(scheduler: Scheduler): gsoc_year=gsoc_year, accepted=True ).all() suborg_list = [] - branch_name = str(timezone.now().timestamp()).replace('.', '-') - branch = create_branch(f'update-template-{branch_name}') + branch_name = str(timezone.now().timestamp()).replace(".", "-") + branch = create_branch(f"update-template-{branch_name}") for suborg in suborgs: f = open(suborg.logo.path, "rb") lines = f.readlines() From 2eb2c533de717daf322e5c90f32f47788da7fe88 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 17 Aug 2019 15:39:03 +0530 Subject: [PATCH 0496/1137] Add modal when archiving pages --- gsoc/common/utils/tools.py | 101 +++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 9 deletions(-) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index d1998afb..b62649d3 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -17,7 +17,7 @@ def build_send_mail_json( raise TypeError( "send_to must be a sequence of email addresses " "or one email address as str!" - ) + ) return json.dumps(locals()) @@ -28,7 +28,7 @@ def build_send_reminder_json( raise TypeError( "send_to must be a sequence of email addresses " "or one email address as str!" - ) + ) return json.dumps(locals()) @@ -48,7 +48,7 @@ def send_mail(send_to, subject, template, context={}): from_email=settings.SERVER_EMAIL, reply_to=settings.REPLY_EMAIL, to=send_to, - ) + ) send_email.content_subtype = "html" send_email.send() @@ -75,7 +75,7 @@ def create_pull_request(source_branch, target_branch="master"): repo = g.get_repo(settings.STATIC_SITE_REPO) repo.create_pull( title="Site Template Update", body="", base=target_branch, head=source_branch - ) + ) def push_site_template(file_path, content, branch): @@ -124,21 +124,104 @@ def update_robots_file(repo, current_year): def archive_current_gsoc_files(current_year): - g = Github(settings.GITHUB_ACCESS_TOKEN) + # g = Github(settings.GITHUB_ACCESS_TOKEN) + g = Github('sounak98', 'Soun@k1998_1965') repo = g.get_repo(settings.STATIC_SITE_REPO) files = get_files(repo) update_robots_file(repo, current_year) for file in files: + decoded_content = file.decoded_content + if file.path.split('.')[-1] == "html": + decoded_content = decoded_content.replace(b"", b""" + + +
    +
    + X +

    Archived

    +

    + This site has been archived, go to + this link to find + more about the latest GSoC program. +

    +
    +
    + + + + """) + print(decoded_content) try: repo.create_file( f"{current_year}/{file.path}", f"Archive GSoC {current_year} files", - file.decoded_content, - ) + decoded_content, + ) except Exception as e: repo.update_file( f"{current_year}/{file.path}", f"Archive GSoC {current_year} files", file.content, - file.sha, - ) + decoded_content, + ) From 53e8a8e2d8a48e8ca711e711d3ce54526be2a933 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sat, 17 Aug 2019 15:41:04 +0530 Subject: [PATCH 0497/1137] Lint code --- gsoc/common/utils/tools.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index b62649d3..f9d378d2 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -17,7 +17,7 @@ def build_send_mail_json( raise TypeError( "send_to must be a sequence of email addresses " "or one email address as str!" - ) + ) return json.dumps(locals()) @@ -28,7 +28,7 @@ def build_send_reminder_json( raise TypeError( "send_to must be a sequence of email addresses " "or one email address as str!" - ) + ) return json.dumps(locals()) @@ -48,7 +48,7 @@ def send_mail(send_to, subject, template, context={}): from_email=settings.SERVER_EMAIL, reply_to=settings.REPLY_EMAIL, to=send_to, - ) + ) send_email.content_subtype = "html" send_email.send() @@ -75,7 +75,7 @@ def create_pull_request(source_branch, target_branch="master"): repo = g.get_repo(settings.STATIC_SITE_REPO) repo.create_pull( title="Site Template Update", body="", base=target_branch, head=source_branch - ) + ) def push_site_template(file_path, content, branch): @@ -125,14 +125,16 @@ def update_robots_file(repo, current_year): def archive_current_gsoc_files(current_year): # g = Github(settings.GITHUB_ACCESS_TOKEN) - g = Github('sounak98', 'Soun@k1998_1965') + g = Github("sounak98", "Soun@k1998_1965") repo = g.get_repo(settings.STATIC_SITE_REPO) files = get_files(repo) update_robots_file(repo, current_year) for file in files: decoded_content = file.decoded_content - if file.path.split('.')[-1] == "html": - decoded_content = decoded_content.replace(b"", b""" + if file.path.split(".")[-1] == "html": + decoded_content = decoded_content.replace( + b"", + b""" - - - - - - - - - - - -
    - -
    -
    -

    Dates and Deadlines

    -

    In general, Python will ask mentors to do things before the Google - deadline. This allows our admins - time to make sure that evaluations, etc. are complete and ready for Google when their deadline - comes. - (The whole organization gets penalized if anyone's late, so we make sure that doesn't happen - unfairly.) - Student deadlines are exactly as Google tells you, although getting things done earlier is never a - bad - idea!

    - -

    Mentor and Sub-Org admin deadlines

    -

    These are also listed on the calendar at the bottom of this page that you can subscribe to or add to your own device.

    -
      - {% for event in events %} - {% if event.start_date == event.end_date %} -
    • {{ event.start_date }} - {{ event.title }}
    • - {% else %} -
    • {{ event.start_date }} to {{ event.end_date }} - {{ event.title }}
    • - {% endif %} - {% endfor %} -
    - -

    Blogging schedule (Student Deadlines)

    -

    Every week, students are asked to post something about their project on their blogs. This helps the python community learn about the work students are doing and also helps the org admins make sure that students still on track to pass and don't need help. There are two types of things that students post: blog posts, which are longer descriptions of the work they're doing, and weekly check ins, which answer a few short questions as a sort of status report. These are due every Monday during the GSoC period, and the schedule is listed below as a list and as a calendar at the bottom of the page that you can export and add to your own calendar.

    -
      - {% for duedate in duedates %} -
    • {{ duedate.date }} - {{ duedate.title }}
    • - {% endfor %} -
    - -
    - + /* Responsive iFrame */ + .responsive-iframe-container { + position: relative; + padding-bottom: 56.25%; + padding-top: 30px; + height: 0; + overflow: hidden; + } + .responsive-iframe-container iframe, + .vresponsive-iframe-container object, + .vresponsive-iframe-container embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + + + + + + + + + + + +
    + -
    - - - - - - - +
    +
    + + + + + + + From ed4d32d6e10f636dc01d62fd785fbdca11d15916 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 24 Aug 2019 04:12:45 -0600 Subject: [PATCH 0535/1137] Update index.html --- gsoc/templates/site/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 8a93d8c8..57130625 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -373,7 +373,7 @@

    {% endfor %}
    From 24f659aca6ec4d1cac4ba2d884f61ecde311c4e8 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 24 Aug 2019 04:14:59 -0600 Subject: [PATCH 0536/1137] Update models.py --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 036041f2..bb0ccbf8 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1029,7 +1029,7 @@ def send_notifications(self): template_data = { "article": self.article.title, "created_at": self.created_at.strftime("%I:%M %p, %d %B %Y"), - "username": self.username, + #"username": self.username, "link": urljoin(settings.INETLOCATION, comment_link), "article_owner": self.article.owner.username, "parent_comment_owner": self.parent.user.username, From fdbdc3bfe658719d458a1c140af87855ee7af8f1 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 24 Aug 2019 04:15:59 -0600 Subject: [PATCH 0537/1137] Update comment_reply_notification.html --- gsoc/templates/email/comment_reply_notification.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/comment_reply_notification.html b/gsoc/templates/email/comment_reply_notification.html index efddcbf3..533cd9c4 100644 --- a/gsoc/templates/email/comment_reply_notification.html +++ b/gsoc/templates/email/comment_reply_notification.html @@ -1,6 +1,6 @@ Hi {{ parent_comment_owner }}

    -Your blog comment {{ article }} has a new reply {{ link }} by {{ username }}
    +Your blog comment {{ article }} has a new reply {{ link }}

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From 2dd321a3ee4e45ee11e62b603c6fe24ae019bca3 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 25 Aug 2019 18:26:05 +0530 Subject: [PATCH 0538/1137] Update stylesheet --- gsoc/static/css/python-gsoc.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index 85d65cab..ce79ce8c 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -279,13 +279,13 @@ html.cms-toolbar-expanded > #layout { /* Alternate coloured ribbon */ .ribbon { background: #16536e; - color: #aaaaaa; + color: #ebebeb; padding: 1em 1em 3em; } .ribbon a:link { - color: white; + color: #73ffff; } .ribbon a:visited { @@ -327,7 +327,7 @@ html.cms-toolbar-expanded > #layout { /* This is the class used for the content sub-headers (

    ) */ .content-subhead { - color: #489eba; + color: #295E70; } .content-subhead i { margin-right: 7px; From 44815a262f8202112e523f80251daac1f429895f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Sun, 25 Aug 2019 18:36:07 +0530 Subject: [PATCH 0539/1137] Cleanup unused templates --- gsoc/settings.py | 7 - gsoc/templates/contact.html | 156 ------------- gsoc/templates/gettingstarted.html | 131 ----------- gsoc/templates/homepage.html | 96 -------- gsoc/templates/ideas.html | 360 ----------------------------- gsoc/templates/mentors.html | 334 -------------------------- gsoc/templates/schedule.html | 57 ----- gsoc/templates/students.html | 280 ---------------------- 8 files changed, 1421 deletions(-) delete mode 100644 gsoc/templates/contact.html delete mode 100644 gsoc/templates/gettingstarted.html delete mode 100644 gsoc/templates/homepage.html delete mode 100644 gsoc/templates/ideas.html delete mode 100644 gsoc/templates/mentors.html delete mode 100644 gsoc/templates/schedule.html delete mode 100644 gsoc/templates/students.html diff --git a/gsoc/settings.py b/gsoc/settings.py index ab702cae..9c50980b 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -191,13 +191,6 @@ def gettext(s): ("fullwidth.html", "Fullwidth"), ("sidebar_left.html", "Sidebar Left"), ("sidebar_right.html", "Sidebar Right"), - ("homepage.html", "Homepage"), - ("gettingstarted.html", "Getting Started"), - ("ideas.html", "Project Ideas"), - ("mentors.html", "Mentors"), - ("schedule.html", "Schedule"), - ("students.html", "Students"), - ("contact.html", "Contact"), ("myprofile.html", "My Profile"), ) diff --git a/gsoc/templates/contact.html b/gsoc/templates/contact.html deleted file mode 100644 index b35a2b98..00000000 --- a/gsoc/templates/contact.html +++ /dev/null @@ -1,156 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Contact -{% endblock %} - -{% block content %} - -
    -
    -

    Getting in Touch

    -

    - Please note that Python has a Community - Code of Conduct and mentors and - students working with the PSF are asked to abide by it as members of the - Python community. -

    -
    -
    -
    -
    -
    -
    -
    -

    - - Mailing Lists. -

    -

    Sign up to the gsoc-general(at)python.org - mailing list to get updates, reminders, and to discuss questions. Please join the list - before you send a message! -

    -

    The most common questions are answered here: -

    -

    -
    -
    -
    -

    - - IRC / Live chat -

    -

    - Our IRC channel is #python-gsoc - on - irc.freenode.net. (Don't know IRC? Learn more at - irchelp.org). -

    - -
    -
    -

    - - Specific sub-orgs -

    -

    To talk with people from a specific sub-org, check their ideas - page listing for their mailing lists, IRC, and other contact information. -

    -
    -
    -
    -

    - - Tips! -

    -
      -
    1. Read first. We've tried to answer the common questions on this site, and - we get asked things like "How do I get started?" and - "Where do I find easy bugs?" a lot. Check the - Frequently Asked Questions (FAQ) on the student page for - more! -
    2. -
    3. Be Patient! Our mentors typically have day jobs and can't always answer - right-away. If you can't hang out on IRC for an answer, send an email instead. -
    4. -
    5. Ask questions directly on IRC. You don't need to introduce - yourself or say hi first, just ask away! -
    6. -
    7. Communicate in public. That lets many mentors read your question so you - can usually get an answer faster. -
    8. -
    -

    For mentors: All the gsoc admins can be reached at - gsoc-admins(at)python(dot)org if you have questions about participating. - (Students should email gsoc-general(at)python.org with all of their - questions, unless they are of a sensitive personal nature.) -

    -
    -
    -

    - - Org admins -

    -

    The 2019 Python Software Foundation (PSF) org admin team:

    -
      -
    • Terri Oda (terri on IRC) - focus areas: figurehead, making final decisions, - website/documentation -
    • -
    • James Lopeman (meflin on IRC) - focus areas: IRC, ideas pages reviews, saying no
    • -
    • John Hawley (warthog9 on IRC) - focus areas: infrastructure, advice, emergency - mentoring/mentor - supervision. -
    • -
    • Matthew Lagoe (Botanic on IRC) - focus areas: student blogs, irc bot, marking sure things - happen on time -
    • -
    • Kushal Das (kushal on IRC) - focus areas: advice, time zone coverage
    • -
    -

    The org admins can be reached at gsoc-admins(at)python(dot)org (for mentors) - Students should almost always visit Getting Started first, and - email - gsoc-general(at)python.org only if you get stuck. -

    -

    We also have some "org admins emeritus" who may be able - to help you: -

    -
      -
    • Florian Fuchs (florianf on IRC)
    • -
    • Stephen Turnbull (yaseppochi on IRC)
    • -
    -
    -
    -
    - -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/gsoc/templates/gettingstarted.html b/gsoc/templates/gettingstarted.html deleted file mode 100644 index 281b2b5c..00000000 --- a/gsoc/templates/gettingstarted.html +++ /dev/null @@ -1,131 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Getting Started -{% endblock %} - -{% block content %} - -
    -
    -
    -

    How do I get started?

    -

    - - Choose an organization. -

    -

    - There's hundreds of thousands of projects that use Python, and you - need to narrow - down the list before you can get help or do much that's useful. - See How - do I choose a project or sub-org? for ideas - on how to do that. -

    Any open source experience will help you prepare for GSoC, - so don't worry too much about what project you try first and don't be afraid - to change your mind! When we know which sub-orgs will be participating, - they'll be listed with the project ideas. -

    -
    -
    -
    -

    - - Set up your own development environment. -

    -

    - Document what you do so you can remember it later, and so you can - help others if they get stuck! And if you get stuck, don't be afraid to ask - for help. -

    -
    -
    -
    -
    -

    - - Start communicating with the developers. -

    -

    - Join the mailing list, IRC channel, or any other communication - channels the developers use. Listen, get to know the people involved, and ask - questions. -

      -
    • Read first to see if your question has already been answered. - We get a lot of repeat questions! -
    • -
    • Communicate in public (not in private). Most open source work is done in the open, - so - demonstrate that you can do that! -
    • -
    -

    -
    -
    -
    -

    - - Try to fix a bug. -

    -

    - Many projects have these tagged as "easy" "bite-size" or - "beginner-friendly" -- do a search to see what comes up. Competition for the easiest - ones can be fierce, so don't be afraid to try something harder if you think - you might know what to do. -

    -

    - Can't find a bug? Other ideas: find typos and fix them. Improve test coverage by - writing new tests. Improve documentation. Use a tool like Pylint or Bandit to see - if you can find new issues. -

    -
    -
    -
    -

    - - Find bugs and report them. -

    -

    - Hopefully you won't encounter too many, but it's always a good idea to get familiar with - your - project's bug reporting process. -

    -
    -
    -
    -
    -

    - - Help with documentation. -

    -

    - As a beginner in your project, you're going to see things that are confusing that more - experienced developers may not notice. Take advantage of your beginner mindset and make - sure to - document anything you think is missing! -

    -
    -
    -
    -

    - - Help others. -

    -

    - Most projects are looking for not just coders, but good community members who people like to - work with. Show your community skills by helping others and make a great impression come - selection time! -

    -
    -
    -
    - -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/gsoc/templates/homepage.html b/gsoc/templates/homepage.html deleted file mode 100644 index 295c991f..00000000 --- a/gsoc/templates/homepage.html +++ /dev/null @@ -1,96 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Splash -{% endblock %} - -{% block content %} -
    -
    -

    Python Summer of Code

    -

    - Students: get paid to work on open source projects! -

    -

    - Projects: find new contributors and mentor the next generation! -

    -
    -
    - -
    -
    -
    -

    What is it?

    -
    -
    -
    -

    - - Python -

    -

    - Python is a popular high-level programming language. It is a general-purpose language used - by - scientists, developers, and many others who want to work more quickly and integrate systems - more effectively. -

    -
    -
    -
    -

    - - Google Summer of Code -

    -

    - Google Summer of Code (GSoC) is a global program that offers post-secondary students an - opportunity to be paid for contributing to an open source project over a three month period. -

    -
    -
    -

    - The Python Software Foundation (PSF) is an organization devoted to advancing open source - technology related to the Python programming language. - Since 2005, the Python Software Foundation has participated in Google Summer of Code, serving - as an "umbrella organization" to a variety of Python-related projects, as well as sponsoring - projects related to the development of the Python language. Python provides mentors, Google - provides the program (and the money!), and students write code! -

    -
    -
    -
    -

    We are hoping to participate in GSoC 2019 and are in the process of gathering ideas for - our application! Sub org ideas pages are due Feb 4, so students can expect project ideas to be ready - for viewing around then. -

    -
    -
    -
    -
    -
    -

    Other Stuff

    -
    -
    -
  • Found a typo? Want to improve this site? The - source code - is on GitHub and we welcome pull requests! -
  • -
  • Want to use some of the text of this site? It is now licensed - under CC-BY-4.0. -
  • -
  • This site was developed using purecss.io which is licensed - under - the BSD license -
  • - -
    -
    -
    -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/gsoc/templates/ideas.html b/gsoc/templates/ideas.html deleted file mode 100644 index 8daf6c5e..00000000 --- a/gsoc/templates/ideas.html +++ /dev/null @@ -1,360 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Project Ideas -{% endblock %} - -{% block content %} - -
    -
    -

    Ideas

    -
    -
    -

    We are hoping to participate in GSoC 2019 and are in the process of gathering ideas for - our application! -

    -

    This section will list all the sub-orgs who have signed up to participate with the - Python Software Foundation for 2019. Applications for sub-orgs are due by Feb 4, - so our initial list should be available by Feb 5th. -

    -

    In the meantime, you may wish to take a look at last year's list - to see who might be involved again. -

    -
    -
    - -
    - - -
    -
    - -
    -

    - MNE-Python -

    -
    -
    -
    MNE is a free and open source software designed for processing electroencephalography (EEG) and magnetoencephalography (MEG) data. EEG and MEG data analysis requires advanced numerics, signal processing, statistics and dedicated visualization tools. MNE-Python is a pure Python package built on top of numpy, scipy, matplotlib and scikit-learn. - -
    Status: Ideas ready
    -

    - - - -
    -
    - -
    -

    - PySAL -

    -
    -
    -
    PySAL is a library for quantitative analysis of geographic data built on top of numpy and scipy.sparse.. The package includes methods for exploratory spatial data analysis and cluster detection, regression models with geographical data, space-time models, and visualization. - -
    Status: Ideas ready
    -

    - - - -
    -
    - -
    -

    - Python Software Foundation GSoC Team -

    -
    -
    -
    Under the Python Software Foundation (PSF) the GSoC Team has ran a GSoC umbrella organization for the Python language. This sub-org is being used to sponsor tools for the PSF GSoC team, specifically tools for managing GSoC from diverse sub-orgs for PSF. At this time, Python is NOT sponsoring any projects related to the development of the language -- but we're looking for mentors if you want to run such a project - -
    Status: Ideas ready
    -

    - - - -
    -
    - -
    -

    - SciPy -

    -
    -
    -
    SciPy is a library that provides fundamental routines for scientific computing: statistics, optimization, integration, linear algebra, Fourier transforms, signal processing, and more. - -
    Status: Ideas ready
    -

    - - - -
    -
    - -
    -

    - StarKit -

    -
    -
    -
    StarKit is a library to infer stellar parameters from astrophysical observations - -
    Status: Ideas ready
    -

    - - - -
    -
    - -
    -

    - Nuitka -

    -
    -
    -
    Nuitka is a Python compiler written in Python. The mission - is make Python a C speed programming language with graceful - degradation, i.e. simple Python is very fast, while using all - the features is still faster. - -

    This is a chance to join this extra ordinary project and - make a difference to the programming world by taking Python - where it currently is not. - -

    Nuitka currently does not yet live up to the task, but with - your help it will get there sooner and you will have been - a part of it. - -

    -
    Status: Ideas ready
    -

    - - -
    -
    - -
    -

    - MSS - Mission Support System -

    -
    -
    -
    MSS is a web service based client/server application to plan atmospheric research flights. - -
    Status: Ideas ready.
    -

    - - -
    -
    - -
    -

    - EOS Design System -

    -
    -
    -

    EOS is the first open source and customizable Design System to help open source, SMEs, and all sizes of organizations deliver outstanding user interfaces and consistent user experience.

    -

    Design Systems serve as a centralized source of information for UX, UI, and other brand-related guidelines that help not only developers find the UI element or component they need, but also designers to build faster prototypes while streamlining the collaboration between the two.

    - -
    Status: Ideas ready.
    -
    -
    - - - -
    -
    - -
    -

    - Scrapy -

    -
    -
    -

    Scrapy is the world's best-loved scraping framework, helping people to efficiently extract and use web-data using Python.

    -

    Using Scrapy, you can travel through sites at remarkable speed, and using only a few lines of readable code, extract high-quality - data for use in research, for enterprise, for archival, and more.

    -

    Scrapinghub, as lead maintainers of Scrapy and its constellation of supporting libraries, is open to mentoring motivated students - who would like to work on Scrapy, or on a new Open Source library for quality-assurance in Scrapy spiders, Spidermon. -

    - - - - -
    -
    Status: Ideas ready.
    -
    -
    - - - -
    -
    - -
    -

    - Statsmodels -

    -
    -
    -

    Statsmodels is a general purpose statistics and econometrics package written in Python and some Cython. - It is part of the scientific Python stack that is oriented towards data analysis, data science and statistics, but is also used for statistical analysis in science, engineering and other fields.

    -

    Statsmodels provides a large range of statistical methods, estimation and prediction models and hypothesis tests, among them linear, generalized linear and robust linear regression models, models for discrete data, and a large group of forecasting models in time series analysis.

    -

    Statsmodels has participated for many years in GSOC. This provided a large contribution to improving the coverage of both basic and advanced methods in statistics and econometrics.

    - -
    Status: Ideas in progress.
    -
    -
    - - -
    -
    - -
    -

    - Buildbot -

    -
    -
    -

    Buildbot is an open-source framework for automating software build, test, and release processes. -

    Buildbot can automate all aspects of the software development cycle: Continuous Integration, Continuous Deployment, Release Management -

    Buildbot is a framework in which you implement a system that matches your workflow and grows with your organization. -

    Buildbot has participated for many years in GSoC, and some of its core features were developed during GSoC.

    - -
    Status: Ideas ready.
    -
    -
    - - - -
    -
    - -
    -

    - CVE Binary Tool -

    -
    -
    -

    The CVE Binary Tool is a security tool that scans for a number of common, vulnerable open source components (openssl, libpng, libxml2, expat and a few others) to let you know if your system includes common libraries with known vulnerabilities.

    - -
    Status: Ideas ready.
    -
    -
    - - - -
    -
    - -
    -

    - Mercurial -

    -
    -
    -
    Mercurial is a free, distributed source control management tool. It efficiently handles projects of any size and offers an easy and intuitive interface. -
    - - -

    #mercurial on Freenode

    - -
    -
    Status: Ideas page in progress
    -

    - - -
    - -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/gsoc/templates/mentors.html b/gsoc/templates/mentors.html deleted file mode 100644 index 2cda44c2..00000000 --- a/gsoc/templates/mentors.html +++ /dev/null @@ -1,334 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Mentors -{% endblock %} -{% block content %} -
    -
    -

    Mentors

    -

    Interested in volunteering with the Python Software Foundation?

    -

    The biggest job is mentoring students: Mentoring a - student as a primary mentor can be a pretty big time commitment (see - "What does it take to be a mentor?" for more information) but it's a very - rewarding chance to give a student an open source apprenticeship. - We mentor in teams, so if all you can handle is a few code reviews or taking - over for a week while someone's on vacation, you can team up with someone with more time.

    - -

    The easiest way to become a mentor is to be part of one of the sub-orgs - that plan to be involved, so get in touch with them directly if you want to - help. If you're part of a group that would like to participate as a sub-org, - please read the section for sub-orgs below.

    - -

    If you're not already part of a group - that wants to participate, we can try to match you with one, but be aware that to do - the best job of mentoring you're going to need to know the open source project pretty - well yourself. If you're not already a developer, you should be prepared to become - an active community member. -

    - -

    But we often need other volunteers! We're also looking for - friendly community members to help with other tasks! We'd love to have more - people available on IRC/Mailing lists to answer student and mentor - questions in various time zones. We are particularly looking for volunteers - who can read and comment on student blogs, remind students if they haven't - posted, and promote the work our students do to the larger Python community. - Or maybe you have another skillset you'd like to contribute? (Proofreading? - Recruiting diverse student applicants?) If you want to help, we can try to - find a way to make that happen.

    - -

    If you'd like to volunteer, get in touch with a sub-org admin or - email the Python org admins at gsoc-admins(at)python(dot)org

    -
    -
    - -
    -
    -
    -

    What does it take to be a mentor?

    - -
    -

    - - Time commitment -

    -

    We expect around a 0-10hr/week commitment, which sounds scary, but it's not - actually that - variable. You usually spend up to lots of time for the first few weeks, where you're - fleshing out your ideas page, discussing projects with many students, and selecting - students from their proposals. After students are selected and settled in, it becomes more - like - a 1hr - commitment per week for a weekly meeting, and maybe a few more hours here and there for - code review or questions. (That depends on your student: experienced students may need - very little supervision, inexperienced students may need more. It also depends on you: You - and your co-mentor(s) select the student and project you mentor, so you can choose - according to the time commitment you have. Some mentors even do pair programming with - their students!)

    -

    I recommend at least one mentor has a weekly 1hr meeting with the student - so - they get to - know each other, keep everyone on track, and give a chance to talk about other stuff. Lots - of students have questions about jobs, courses, architecture, open source, etc. and it's - nice for students to have someone to talk to especially since many of them will not have - worked - remotely - on their own for any length of time before. Some weeks this meeting may be the only - mentoring - time - needed.

    -
    -
    -

    - - Work Together -

    -

    We want at least two mentors per project, so hopefully no one ever gets - overwhelmed and - feels like they're always on call (Google does ask that we try to answer questions within - 48h so students can't get stuck for too long), and no one mentor has to know all the - answers.

    -
    -
    -

    - - Knowledge required -

    -

    Our most successful mentors are those who are already developers or community - members of - their open source project. If you're joining a new project for GSoC, expect to - take time - to learn the ropes yourself so you can help students.

    -

    Mentors don't have to be the Best At Everything. A lot of what mentors do is keep students - on track and keep them from getting stuck for too long. Sometimes that means just knowing - who to ask or where to look rather than knowing the answer yourself.

    -

    In an ideal world, at - least one mentor can answer at least basic architectural questions and knows how to get - code accepted upstream. Not every mentor has to be a coder: experienced users can help - students understand why features make sense (or dont!), system administrators can help - student - understand how deployment works in practice, experts in areas like accessibility, - usability, - and security could help guide students in their areas of expertise.

    -
    -
    -

    - - Evaluating students -

    -

    Mentors do have to do multiple evaluations on the student, two mid-terms and one at the end. - Usually the mentors discuss - and then the "primary" mentor fills in the evaluation with input from all mentors. - There's a - few questions about how the student is doing and then a pass/fail that determines if the - student gets paid and continues in the program.

    -
    - -
    -
    -
    - -
    -
    -

    Sub Orgs

    -

    To participate under the Python umbrella, a sub-org must do the following: - -

      -
    1. Be a Python-based open source project that meets - Google's requirements - for GSoC. - -
    2. Have one sub-org admin and at least two mentors who are willing to commit - to the full GSoC period. (More is awesome!) -
        -
      • If you want to connect with more potential volunteers, - email gsoc-admins@python.org - to see if we can match you with volunteers who don't have a project.
      • -
      - -
    3. Accept the Python - Community Code of Conduct for the duration of the - program. - -
    4. Send an email indicating interest to gsoc-admins(at)python(dot)org before - the Python deadline (exceptions can be made if you get an amazing - student applicant later and want to sign up just for them). - - -
    5. Have a good ideas page. We have a template below. Getting a - really - great page - sometimes takes a few rounds of revisions; Meflin will work with you to make - sure your page is ready! Once you're ready for review, you can send - a pull request to get - added to this page - -
    6. Be able to handle meeting deadlines and following both Google - and Python's rules. We try to send important reminders for big deadlines, but we only - have limited volunteer time for nagging and cajoling. Groups that cause repeated problems - may be asked to take time off to limit core volunteer burnout. - -
    7. Disclose all potential conflicts of interest to the Python admins BEFORE accepting a - student. If you are unsure, ask. If a conflict is found after the fact the student and - sub-org may be dropped from the program. (Examples: student is involved in your research - group, - student is your child, student owes you money, etc.) - -
    - - -

    We can't promise to take everyone who meets those criteria, but we do try to - take any group that we feel will give the students a great experience. - Terri has final say in which projects participate under the Python - umbrella, but please send any queries to all the admins at - gsoc-admins(at)python(dot)org to make sure we're all on the same page.

    - -

    Python projects are welcome and encouraged to apply as separate - mentoring organizations directly with Google. We're happy to help - you in any way we can and we don't mind being your backup plan. We're also - happy to help advertise python based organizations not under our umbrella: we - want students to find projects that best suit them!

    - -

    Please note: The funds Google gives Python as mentor stipends are given to the - PSF grants program rather than - dispensed per sub-org. -

    -
    -
    - -
    -
    -
    -

    Python Sub-org Ideas Template -

    - -

    There are not very many strict requirements for Google Summer of Code Ideas pages, but - there are some things that students often ask us for. This page is intended as a starting - template for organizations so you don't forget those things. - -

    Warning: In 2014, many orgs got rejected because their ideas pages were offline when - Google checked. Make sure your ideas page is hosted somewhere that Google's Open - Source - Programs Office will be able to access when they check! - -

    About MyOrg

    - -

    Tell the students a bit about your organization. Here's some questions you might want to - answer: - -

      -
    • What software are you creating? -
    • Why is it interesting? -
    • Who uses it? -
    • What languages is it written in? -
    • How is it going to change the world? -
    - - -

    Contacting MyOrg

    - -
      -
    • IRC channel: -
    • Mailing list(s): -
    • List contact methods you actually use and will have mentors monitoring! -
    - - -

    Include any special instructions/info about communicating: e.g. what time zones are - your - mentors in? do you prefer it if gsoc students introduce themselves first or just - dive - in? - are there any common mistakes students make when making a first impression? - -

    Getting Started

    - -

    Links to setup instructions go here. Some suggested things to answer: - -

      -
    • Where is the link to a setup guide for new developers? -
    • Are there any unusual libraries/applications that need to be installed - first? -
    • What type of source control do you use? (include links to help and - setup - guides!) -
    • What's the process for submitting your first bug fix? -
    • Where should students look to find easy bugs to try out? -
    - -

    Writing your GSoC application

    - -

    Links to advice about applications and the application template goes here. - -

    - -

    Project Ideas

    - -

    You should usually have a couple of project ideas, ranging in difficulty - from beginner to - expert. Please do try to have at least one, preferably several beginner - tasks: GSoC gets a - lot of students with minimal open source experience who feel very - discouraged (and - sometimes even complain to Google) if orgs don't any have projects at - their - level. - -

    1. Project name

    - -
      -
    • Project description: Make sure you have a - high-level description that any student can - understand, as well as deeper details - -
    • Skills: programming languages? specific domain - knowledge? - -
    • Difficulty level: Easy/Intermediate/Hard - classification (students ask for this info - frequently to help them narrow down their choices. Difficulty - levels are something Google - wants to see, so they aren't optional; make your best guess.) - -
    • Related Readings/Links: was there a mailing - list - discussion about this topic? - standards you want the students to read first? bugs/feature - requests? - -
    • Potential mentors: A list of mentors likely to - be - involved with this project, so - students know who to look for on IRC/mailing lists if they have - questions. (If you've had - trouble with students overwhelming specific mentors, feel free - to - re-iterate here if - students should contact the mailing list to reach all mentors.) -
    - -

    2. Project name

    - -

    As above. etc. Unless there's a compelling reason to sort in some - other - order, ideas - should be ordered approximately from easiest to hardest. - -

    -
    -
    -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/gsoc/templates/schedule.html b/gsoc/templates/schedule.html deleted file mode 100644 index de61407c..00000000 --- a/gsoc/templates/schedule.html +++ /dev/null @@ -1,57 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Schedule -{% endblock %} - -{% block content %} -{% load app_tag %} - - -
    -

    Schedule

    -
    -
    -

    Next Deadline - Students can start signing up March 12th.

    -
    -
    -
    - Dates - - -
      -
    • January 19 - Sub-org applications due. See - the sub-org section for details. If you're an organization that tried to get in as - a top-level organization and didn't get in and would like to participate with us instead, - please get in touch by March 5. -
    • February 12 - Google Announces list of accepted mentoring organizations -
    • March 12 - Student sign-ups start -
    • March 27 - Student sign-ups finish -
    - -

    Please note Google's GSoC - dates and deadlines. - -

    In general, Python will ask mentors to do things 48h before the Google - deadline. This allows our admins time to make sure that evaluations, etc. are - complete and ready for Google when their deadline comes. Student deadlines - remain the same, although getting things done earlier is never a bad - idea!

    - -

    We have a Python-specific calendar here: Python - GSoC calendar in ical format

    - -
    - You are viewing calendar at GMT {% time_zone 1 %} - -
    -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/gsoc/templates/students.html b/gsoc/templates/students.html deleted file mode 100644 index 0ea194e3..00000000 --- a/gsoc/templates/students.html +++ /dev/null @@ -1,280 +0,0 @@ -{% extends "base.html" %} - -{% block title %} -Students -{% endblock %} - -{% block content %} - -
    -
    -

    Students

    -

    GSoC is basically an open source apprenticeship: students will be paid by - Google to work under the guidance of mentors from an open source community. - It's a really great opportunity to build new skills, make connections in your - community, get experience working with a larger and often distributed team, - learn, and, of course, get paid. - -

    Students are expected to work around 40 hours a week on their GSoC project. - This is essentially a full-time job. Ideally, you should not attempt to do - another internship, job, or full-time schooling while you're doing GSoC. - -

    Remember that Google intends this to be a way for new contributors to join - the world of open source. The students most likely to be selected are those - who are engaged with the community and hoping to continue their involvement - for more than just a few months. - -

    To apply, you need to take a look at the mentoring - organizations and the ideas that they are willing to sponsor. Typically, - you'll choose one of their ideas and work with a mentor to create a project - proposal that's good for both you and your chosen open source community. - Sometimes, projects are open to new ideas from students, but if you propose - something new make especially sure that you work with a mentor to make sure - it's a good fit for your community. Unsolicited, undiscussed ideas are less - likely to get accepted. - -

    Note that Python is an "umbrella organization" which means that our team is - actually a group of python projects that work together to do Google Summer of - Code. If you're going to apply with us, you'll need to choose from one of - those teams, because that defines which mentors will be helping you with your - applications. Applications without any sub-org and mentor to - evaluate them will be rejected. You can work with more than one - sub-org while you're figuring out what you want to do, but you can only - accept one job offer. - - Here's some resources so you can read up more on how to be an awesome - student: -

    -
    -
    - -
    -
    -
    -

    How to apply

    -

    Short application checklist:

    -
      -
    1. Read the links and instructions given on this site -- All of it! we've - tried - to give you all - the information you need to be an awesome student applicant. -
    2. Choose a sub-org (check the list here). Applications - not - associated with a sub-org typically get rejected. -
    3. Talk with your prospective mentors about what they expect of student - applicants and get help from them to refine your project ideas. Listening to - your mentors' recommendations is very important at this stage! -
    4. -
    5. Prepare a patch for that sub-org. Usually we expect students to fix a bug - and - have made a pull - request (or equivalent). Your code doesn't have to be - accepted and merged, but it does have to be visible to the public and it does have to be - your - own work - (mentor help is ok, code you didn't write is not). -
    6. -
    7. - Write your application (with help from your mentors!) We'll have a - template up - when applications - open. All applications must go through Google's application system; we can't - accept - any application - unless it is submitted there. -
        -
      • Use a descriptive title and include your sub-org name in Google's system. Good - example: - "Mailman: - Improve - archive search" Bad example: "My gsoc project" -
      • Make it easy for your mentors to give you feedback. If you're using Google docs, - enable comments and submit a "draft" (we can't see the "final" versions until - applications close). - If you're using a format that doesn't accept comments, make sure your email is on - the - document and don't forget to check for - feedback! -
      • -
      -
    8. -
    9. Submit your application to Google before the deadline. We actually - recommend you submit a few days early in case you have internet problems or - the system is down. Google does not extend this deadline, so it's best to be - prepared early! You can edit your application up until the system - closes. -
    10. -
    -
    -

    - - Tip -

    -

    Communication is probably the most - important part of the application process. Talk to the mentors and other - developers, listen when they give you advice, - and demonstrate that you've understood by incorporating their feedback into - what you're proposing. We reject a lot of students who haven't listened to mentor - feedback. If your mentors tell you that a project idea won't work for them, you're - probably not going to get accepted unless you change it. -

    -
    -
    -

    - - What goes in an application? -

    - An ideal application will contain 5 things: -
      -
    1. A descriptive title including the name of the sub-org - you - want to work with - (if this is missing, your application may be rejected!) -
    2. -
    3. Information about you, including contact information.
    4. -
    5. Link to a code contribution you have made to your organization. - (Usually this is a link to a pull request.) -
    6. -
    7. Information about your proposed project. This should be fairly - detailed - and include - a timeline. -
    8. -
    9. Information about other commitments that might affect your ability to - work - during the GSoC period. - (exams, classes, holidays, other jobs, weddings, etc.) We can work around a lot of - things, - but - it helps - to know in advance. -
    10. -
    -
    -
    -
    -
    - - - -{% endblock %} - -{% block js %} - -{% endblock %} From db1ff671c6b176cbfd4bc4ec5619b8d3f3d82931 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 25 Aug 2019 07:17:59 -0600 Subject: [PATCH 0540/1137] Update python-gsoc.css --- gsoc/static/css/python-gsoc.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index ce79ce8c..c224041f 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -530,5 +530,5 @@ pre { } .rss-icon { - color: orange; -} \ No newline at end of file + color: #f26522; +} From 091a6cd3845f8f6239eddc24591d1f9fbf60aac9 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 26 Aug 2019 09:57:16 +0530 Subject: [PATCH 0541/1137] Mark article content safe to be rendered --- gsoc/models.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index bb0ccbf8..5624db11 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -161,6 +161,7 @@ def save(self, *args, **kwargs): defaults={"name": " ".join((self.owner.first_name, self.owner.last_name))}, )[0] # slug would be generated by TranslatedAutoSlugifyMixin + self.lead_in = mark_safe(self.lead_in) super(Article, self).save(*args, **kwargs) @@ -1321,17 +1322,17 @@ def add_review(sender, instance, **kwargs): ar.save() -# Add ArticleReveiw object when new Article is created -@receiver(models.signals.post_save, sender=Article) -def add_review(sender, instance, **kwargs): - ar = ArticleReview.objects.filter(article=instance).all() - if not ar: - ArticleReview.objects.create(article=instance) +# # Add ArticleReveiw object when new Article is created +# @receiver(models.signals.post_save, sender=Article) +# def add_review(sender, instance, **kwargs): +# ar = ArticleReview.objects.filter(article=instance).all() +# if not ar: +# ArticleReview.objects.create(article=instance) - if ar: - ar = ar.first() - ar.is_reviewed = False - ar.save() +# if ar: +# ar = ar.first() +# ar.is_reviewed = False +# ar.save() # Add BlogPostHistory object when new Article is created From f76bb8769d3619ffad8e35faa4ff76595ec98f4d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 26 Aug 2019 10:02:44 +0530 Subject: [PATCH 0542/1137] lint --- gsoc/models.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 5624db11..b2587ab8 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1030,7 +1030,7 @@ def send_notifications(self): template_data = { "article": self.article.title, "created_at": self.created_at.strftime("%I:%M %p, %d %B %Y"), - #"username": self.username, + # "username": self.username, "link": urljoin(settings.INETLOCATION, comment_link), "article_owner": self.article.owner.username, "parent_comment_owner": self.parent.user.username, @@ -1322,19 +1322,6 @@ def add_review(sender, instance, **kwargs): ar.save() -# # Add ArticleReveiw object when new Article is created -# @receiver(models.signals.post_save, sender=Article) -# def add_review(sender, instance, **kwargs): -# ar = ArticleReview.objects.filter(article=instance).all() -# if not ar: -# ArticleReview.objects.create(article=instance) - -# if ar: -# ar = ar.first() -# ar.is_reviewed = False -# ar.save() - - # Add BlogPostHistory object when new Article is created @receiver(models.signals.post_save, sender=Article) def add_history(sender, instance, **kwargs): From 100cd9a5ee658adc5959f3ba7cf310c5d41ed1b8 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 26 Aug 2019 21:54:43 +0530 Subject: [PATCH 0543/1137] Handle exception on wrong input year in feed --- blogs_list/feeds.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py index 362b5a39..1251d86c 100644 --- a/blogs_list/feeds.py +++ b/blogs_list/feeds.py @@ -99,14 +99,19 @@ def add_root_elements(self, handler): class BlogsFeed(Feed): - year = GsocYear.objects.first().gsoc_year - title = f"GSoC {year} PSF Blogs" link = settings.INETLOCATION feed_type = CorrectMimeTypeFeed description = "Updates on different student blogs of GSoC@PSF" def get_object(self, request): - year = int(request.GET.get("y", self.year)) + current_year = GsocYear.objects.first().gsoc_year + gsoc_year = int(request.GET.get("y", current_year)) + year_qs = GsocYear.objects.filter(gsoc_year=gsoc_year) + if len(year_qs) == 0: + raise ObjectDoesNotExist + else: + self.year = year_qs.first() + self.title = f"GSoC {self.year} PSF Blogs" year_start = timezone.datetime(year, 1, 1) year_end = timezone.datetime(year, 12, 31) articles_all = list( From ce9da691817b2a0ff429490570e2659e8dc29a76 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 27 Aug 2019 10:13:46 +0530 Subject: [PATCH 0544/1137] Fix minor bug --- blogs_list/feeds.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/blogs_list/feeds.py b/blogs_list/feeds.py index 1251d86c..6536360a 100644 --- a/blogs_list/feeds.py +++ b/blogs_list/feeds.py @@ -105,15 +105,18 @@ class BlogsFeed(Feed): def get_object(self, request): current_year = GsocYear.objects.first().gsoc_year - gsoc_year = int(request.GET.get("y", current_year)) + try: + gsoc_year = int(request.GET.get("y", current_year)) + except ValueError: + raise ObjectDoesNotExist year_qs = GsocYear.objects.filter(gsoc_year=gsoc_year) if len(year_qs) == 0: raise ObjectDoesNotExist else: - self.year = year_qs.first() + self.year = year_qs.first().gsoc_year self.title = f"GSoC {self.year} PSF Blogs" - year_start = timezone.datetime(year, 1, 1) - year_end = timezone.datetime(year, 12, 31) + year_start = timezone.datetime(self.year, 1, 1) + year_end = timezone.datetime(self.year, 12, 31) articles_all = list( Article.objects.filter( publishing_date__gte=year_start, publishing_date__lte=year_end @@ -130,7 +133,10 @@ def get_object(self, request): return articles_all self.show_all_articles = False - self.page = int(page) + try: + self.page = int(page) + except ValueError: + raise ObjectDoesNotExist count = len(articles_all) self.last_page = count < self.page * 15 and count >= (self.page - 1) * 15 self.last_page = math.ceil(count / 15) From 754ffd316359cb6171dea319e9dd0f38298169db Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 27 Aug 2019 17:40:53 +0530 Subject: [PATCH 0545/1137] fix sort by blog title --- blogs_list/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blogs_list/views.py b/blogs_list/views.py index b2ae2a95..cce231a9 100644 --- a/blogs_list/views.py +++ b/blogs_list/views.py @@ -49,7 +49,7 @@ def list_blogs(request): ) if flag: - blogset = sorted(blogset, key=lambda i: (i["title"])) + blogset = sorted(blogset, key=lambda i: (i["title"].lower())) blogsets.append((year.gsoc_year, blogset)) if not blogsets: From ef63e63fe8f5aea9f579dd7092e12a15af807c41 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 27 Aug 2019 17:44:06 +0530 Subject: [PATCH 0546/1137] change heading color to match www --- gsoc/static/css/python-gsoc.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css index c224041f..c30daff0 100644 --- a/gsoc/static/css/python-gsoc.css +++ b/gsoc/static/css/python-gsoc.css @@ -1,5 +1,5 @@ body { - color: #777; + color: #666; } .pure-img-responsive { @@ -262,7 +262,8 @@ h4, h5, h6, label { - color: #489eba; + /* color: #489eba; */ + color: #295E70; } html.cms-toolbar-expanded > #layout { From c84570350a6804101ebd32fb84d0a5d9df3c137d Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Tue, 27 Aug 2019 18:29:00 +0530 Subject: [PATCH 0547/1137] change colors --- gsoc/static/css/article.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gsoc/static/css/article.css b/gsoc/static/css/article.css index 322f813a..6669f54c 100644 --- a/gsoc/static/css/article.css +++ b/gsoc/static/css/article.css @@ -61,13 +61,13 @@ article > heading { .aldryn-newsblog-pagination > ul > li { padding: 5px 15px; float: left; - background: lightseagreen; + background: #E0FFF1; } .aldryn-newsblog-pagination > ul > li:first-child { border-radius: 30px 0 0 30px; padding-left: 20px; -} +} .aldryn-newsblog-pagination > ul > li:last-child { border-radius: 0 30px 30px 0; @@ -76,7 +76,7 @@ article > heading { .aldryn-newsblog-pagination > ul > li a { text-decoration: none; - color: white; + color: #4760FF; } .aldryn-newsblog-pager { @@ -94,7 +94,7 @@ article > heading { .aldryn-newsblog-pager > ul > li { padding: 5px 15px; float: left; - background: lightseagreen; + background: #E0FFF1; } .aldryn-newsblog-pager > ul > li:first-child { @@ -109,7 +109,7 @@ article > heading { .aldryn-newsblog-pager > ul > li a { text-decoration: none; - color: white; + color: #4760FF; } .aldryn-newsblog-comments { From a49cd5559a441416240f0fcb24a7f2a6c95cbede Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 30 Aug 2019 17:48:30 +0530 Subject: [PATCH 0548/1137] Fix error when a user is added with role others --- gsoc/models.py | 30 +++++++++++++++++++----------- gsoc/templates/email/invite.html | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index b2587ab8..3dce8b7a 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -963,21 +963,29 @@ def create_user( def create_scheduler(self, trigger_time=timezone.now()): validate_email(self.email) role = {0: "Others", 1: "Suborg Admin", 2: "Mentor", 3: "Student"} - scheduler_data = build_send_mail_json( - self.email, - template="invite.html", - subject=( + template_data = { + "register_link": settings.INETLOCATION + self.url, + "role": self.user_role, + "gsoc_year": self.user_gsoc_year.gsoc_year, + } + if self.user_role == 0: + subject = ( + f"You have been invited to join for GSoC " + f"{self.user_gsoc_year.gsoc_year} with PSF" + ) + else: + subject = ( f"You have been invited to join " f"{self.user_suborg.suborg_name.strip()}" f" as a {role[self.user_role]} for GSoC " f"{self.user_gsoc_year.gsoc_year} with PSF" - ), - template_data={ - "register_link": settings.INETLOCATION + self.url, - "role": self.user_role, - "gsoc_year": self.user_gsoc_year.gsoc_year, - "suborg": self.user_suborg.suborg_name.strip(), - }, + ) + template_data["suborg"] = self.user_suborg.suborg_name.strip() + scheduler_data = build_send_mail_json( + self.email, + template="invite.html", + subject=subject, + template_data=template_data ) s = Scheduler.objects.create( command="send_email", activation_date=trigger_time, data=scheduler_data diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index 520f4feb..a7a694d1 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -1,7 +1,7 @@ Welcome to GSoC with the Python Software Foundation!

    {% if role is not 3 %} -You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% else %}Other{% endif %} with {{suborg}} for Google Summer of Code {{gsoc_year}} with the Python Software Foundation.
    +You have been invitied to be a {% if role == 1 %}Suborg Admin{% elif role == 2 %}Mentor{% else %}Other{% endif %} {% if role != 0 %}with {{suborg}} {% endif %}for Google Summer of Code {{gsoc_year}} with the Python Software Foundation.

    You will need to register using the link below to be invited by Google.
    {% else %} From 18dc14f2979356d8bbda90417cf16095b0cf3ace Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 30 Aug 2019 17:48:40 +0530 Subject: [PATCH 0549/1137] Update db --- docs/README.md | 2 +- project.db | Bin 1568768 -> 1761280 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 1c946446..36b022c8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,7 +31,7 @@ You can then access the site with the login bar with http://127.0.0.1:8000/en/?e Default user/pass is `admin` for the superuser -Default student users are `Test-Student1`, `Test-Student2` with pass `^vM7d5*wK2R77V` +Default student users are `student-1`, `student-2`, `student-3` and `student-4` with pass `^vM7d5*wK2R77V` ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/project.db b/project.db index 361c21a134d8149bf2ec8c55b8b19210bc5fae36..f58f68748e624bab96d0564979babc785d908a6d 100644 GIT binary patch literal 1761280 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

    7pGM)1dV!CzCz8+Ik_B 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 z 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 QC?3dK6r%q>Z2JHI54-Y@4*&oF delta 11011 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 P$M7H_0JN0R*52{oxj0#C From cf897cfb29995ddcc29c4ed09d3e0b84f476bd2f Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Fri, 30 Aug 2019 17:52:06 +0530 Subject: [PATCH 0550/1137] Lint code --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 3dce8b7a..1fbca85b 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -985,7 +985,7 @@ def create_scheduler(self, trigger_time=timezone.now()): self.email, template="invite.html", subject=subject, - template_data=template_data + template_data=template_data, ) s = Scheduler.objects.create( command="send_email", activation_date=trigger_time, data=scheduler_data From 32ca4fa997faaf425044583c89b14ccc10501bf0 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Mon, 2 Sep 2019 00:11:10 +0530 Subject: [PATCH 0551/1137] Fix comment errors --- gsoc/models.py | 2 +- gsoc/templates/aldryn_newsblog/includes/comments.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 1fbca85b..872baab6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1041,7 +1041,6 @@ def send_notifications(self): # "username": self.username, "link": urljoin(settings.INETLOCATION, comment_link), "article_owner": self.article.owner.username, - "parent_comment_owner": self.parent.user.username, } scheduler_data = build_send_mail_json( self.article.owner.email, @@ -1052,6 +1051,7 @@ def send_notifications(self): Scheduler.objects.create(command="send_email", data=scheduler_data) if self.parent and self.parent.user: + template_data["parent_comment_owner"] = self.parent.username scheduler_data = build_send_mail_json( self.parent.user.email, template="comment_reply_notification.html", diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index 08b00560..96cd1c22 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -61,9 +61,9 @@
    {% if user.is_authenticated %} - + {% else %} - + {% endif %} From 1b515d4604de0073b88b225c0f1b91461307184e Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 8 Sep 2019 04:47:56 -0600 Subject: [PATCH 0552/1137] Update sitemaps.py --- gsoc/sitemaps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/sitemaps.py b/gsoc/sitemaps.py index 5d51f113..098dbb4c 100644 --- a/gsoc/sitemaps.py +++ b/gsoc/sitemaps.py @@ -23,7 +23,7 @@ def items(self): ) urls.append(p.get_absolute_url()) articles = Article.objects.filter(app_config=blog).all() - for i in range(len(articles) // 5): + for i in range((len(articles)-1) // 5): urls.append(f"{p.get_absolute_url()}?page={i + 2}") for article in articles: urls.append(f"{p.get_absolute_url()}{article.slug}/") From 776589c0000175766f49a37e476ffa4b88fbe805 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 8 Sep 2019 05:17:08 -0600 Subject: [PATCH 0553/1137] Update sitemaps.py --- gsoc/sitemaps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/sitemaps.py b/gsoc/sitemaps.py index 098dbb4c..a8c8e6c8 100644 --- a/gsoc/sitemaps.py +++ b/gsoc/sitemaps.py @@ -22,7 +22,7 @@ def items(self): application_namespace=blog.namespace, publisher_is_draft=False ) urls.append(p.get_absolute_url()) - articles = Article.objects.filter(app_config=blog).all() + articles = Article.objects.filter(app_config=blog, is_published=True).all() for i in range((len(articles)-1) // 5): urls.append(f"{p.get_absolute_url()}?page={i + 2}") for article in articles: From 25244813f40bcc17187363f68f7c05edf98109ef Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 8 Sep 2019 05:38:35 -0600 Subject: [PATCH 0554/1137] Update admin.py --- gsoc/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/admin.py b/gsoc/admin.py index 2347456e..4dbe0bdf 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -94,6 +94,7 @@ def return_func(self, request, obj=None, **kwargs): "is_featured", "featured_image", "lead_in", + "is_published", ) }, ), From ef64902cfa8603e86d68b7f6cf33b34565a71411 Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Wed, 25 Sep 2019 10:53:33 +0530 Subject: [PATCH 0555/1137] Update to dajngo 2.2, django cms 3.7.0rc2 --- project.db | Bin 1761280 -> 1859584 bytes requirements.txt | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project.db b/project.db index f58f68748e624bab96d0564979babc785d908a6d..e7291db585b498cdf9e8ad8a3d787dd783855e4b 100644 GIT binary patch delta 3269 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.2 #django debug django-debug-toolbar>=1.11 #django cms -django-cms==3.7.0rc1 +django-cms==3.7.0rc2 djangocms-text-ckeditor>=3.7.0 djangocms-file>=2.2.0 djangocms-column>=1.9.0 From e05692bab750c0ae3fe3b458af8ca0924056e38d Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 25 Sep 2019 09:53:59 -0600 Subject: [PATCH 0556/1137] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 64efc635..7968cf77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,11 @@ #django -django>=2.2 +django~=2.2.0 #django debug django-debug-toolbar>=1.11 #django cms -django-cms==3.7.0rc2 +django-cms~=3.7.0 djangocms-text-ckeditor>=3.7.0 djangocms-file>=2.2.0 djangocms-column>=1.9.0 From 192c87ba72ae6947914ddf4208fc7b0a66bee1a2 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 25 Sep 2019 10:08:56 -0600 Subject: [PATCH 0557/1137] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7968cf77..c64885c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,6 @@ django-debug-toolbar>=1.11 django-cms~=3.7.0 djangocms-text-ckeditor>=3.7.0 djangocms-file>=2.2.0 -djangocms-column>=1.9.0 djangocms-link>=2.3.1 djangocms-picture>=2.1.3 djangocms-style>=2.1.0 From 91091bdbd87b74ad8fe78a90746db38000be4914 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 25 Sep 2019 10:14:56 -0600 Subject: [PATCH 0558/1137] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c64885c6..4779cca8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ djangocms-video>=2.1.1 djangocms-audio>=1.1.0 djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 +djangocms_column>=1.9 #gsoc irc requirements fredirc==0.3.0 From 9ac4174ad713cad5ce1287abb28b15ff897f2875 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 25 Sep 2019 10:19:04 -0600 Subject: [PATCH 0559/1137] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4779cca8..a790fe7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ djangocms-video>=2.1.1 djangocms-audio>=1.1.0 djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 -djangocms_column>=1.9 +#djangocms_column>=1.9 #gsoc irc requirements fredirc==0.3.0 From 1f0a7aa248de9fd7b2a326ad10ce79d0da14a9ad Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 25 Sep 2019 10:19:36 -0600 Subject: [PATCH 0560/1137] Update settings.py --- gsoc/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 9c50980b..ab3231aa 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -135,7 +135,7 @@ def gettext(s): "djangocms_video", "djangocms_file", "djangocms_picture", - "djangocms_column", +# "djangocms_column", "djangocms_link", "djangocms_style", "djangocms_snippet", From 7185b2fb2fdee9204e11263fd086dbabb537bf4e Mon Sep 17 00:00:00 2001 From: Sounak Pradhan Date: Thu, 5 Dec 2019 01:55:16 +0530 Subject: [PATCH 0561/1137] Minor typo fixes --- gsoc/common/utils/tools.py | 1 - gsoc/templates/site/index.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 8b92dd3d..d7533979 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -213,7 +213,6 @@ def archive_current_gsoc_files(current_year): """, ) - print(decoded_content) try: repo.create_file( f"{current_year}/{file.path}", diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 57130625..ac13b1df 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -378,7 +378,7 @@


    - {% endfor % + {% endfor %} From 87b6890501f6ddf763a7d1186e28b013b2ea6df5 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jan 2020 10:52:43 -0800 Subject: [PATCH 0562/1137] Revert "Make PR instead of direct commit" This reverts commit 425099b018da602cde5d284b74a06c5b3ad90fb6. --- gsoc/common/utils/commands.py | 11 ++--------- gsoc/common/utils/irc.py | 2 +- gsoc/common/utils/tools.py | 24 ++++-------------------- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 26cbbcc8..7290cf1f 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -22,8 +22,6 @@ push_site_template, archive_current_gsoc_files, push_images, - create_branch, - create_pull_request, ) @@ -131,7 +129,6 @@ def update_site_template(scheduler: Scheduler): try: template = json.loads(scheduler.data)["template"] gsoc_year = GsocYear.objects.first() - branch = "master" if template == "deadlines.html": context = { "events": Event.objects.filter(timeline__gsoc_year=gsoc_year).all(), @@ -146,15 +143,13 @@ def update_site_template(scheduler: Scheduler): gsoc_year=gsoc_year, accepted=True ).all() suborg_list = [] - branch_name = str(timezone.now().timestamp()).replace(".", "-") - branch = create_branch(f"update-template-{branch_name}") for suborg in suborgs: f = open(suborg.logo.path, "rb") lines = f.readlines() content = b"" for line in lines: content = content + line - push_images(suborg.logo.name, content, branch) + push_images(suborg.logo.name, content) _ = { "name": suborg.suborg.suborg_name, "description": suborg.description, @@ -173,9 +168,7 @@ def update_site_template(scheduler: Scheduler): suborg_list.append(_) context = {"suborgs": suborg_list} content = render_site_template(template, context) - push_site_template(settings.GITHUB_FILE_PATH[template], content, branch) - if branch != "master": - create_pull_request(branch) + push_site_template(settings.GITHUB_FILE_PATH[template], content) except Exception as e: return str(e) diff --git a/gsoc/common/utils/irc.py b/gsoc/common/utils/irc.py index f18d78fd..afb8c42f 100644 --- a/gsoc/common/utils/irc.py +++ b/gsoc/common/utils/irc.py @@ -40,7 +40,7 @@ def parse_data(data): data = json.loads(data) chunk_size = 150 chunks = [ - data["message"][i : i + chunk_size] + data["message"][i:i + chunk_size] for i in range(0, len(data["message"]), chunk_size) ] num_chunks = len(chunks) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index d7533979..0797a37d 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -62,34 +62,18 @@ def render_site_template(template, context): return template.render(context) -def create_branch(target_branch, source_branch="master"): - g = Github(settings.GITHUB_ACCESS_TOKEN) - repo = g.get_repo(settings.STATIC_SITE_REPO) - sb = repo.get_branch(source_branch) - repo.create_git_ref(ref=f"refs/heads/{target_branch}", sha=sb.commit.sha) - return target_branch - - -def create_pull_request(source_branch, target_branch="master"): - g = Github(settings.GITHUB_ACCESS_TOKEN) - repo = g.get_repo(settings.STATIC_SITE_REPO) - repo.create_pull( - title="Site Template Update", body="", base=target_branch, head=source_branch - ) - - -def push_site_template(file_path, content, branch): +def push_site_template(file_path, content): content = content.encode() g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) f = repo.get_contents(file_path) - repo.update_file(f.path, f"Update {file_path}", content, f.sha, branch=branch) + repo.update_file(f.path, f"Update {file_path}", content, f.sha) -def push_images(file_path, content, branch): +def push_images(file_path, content): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) - repo.create_file(file_path, f"Add {file_path} logo", content, branch=branch) + repo.create_file(file_path, f"Add {file_path} logo", content) def is_year(file_name): From dbe9dc1e3405e971b669ec5286fd35f84bb0d5ec Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 12:05:00 -0700 Subject: [PATCH 0563/1137] Update invite.html --- gsoc/templates/email/invite.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/email/invite.html b/gsoc/templates/email/invite.html index a7a694d1..96cca0cc 100644 --- a/gsoc/templates/email/invite.html +++ b/gsoc/templates/email/invite.html @@ -16,7 +16,7 @@ {% endif %} Blogs and suborgs are handled by our new GSoC blogging platform, so please report any bugs, issues, or suggestions to https://github.com/python-gsoc/python-blogs/issues or email gsoc-admins@python.org

    -To register you will need to go to {{ register_link }}
    +To register you will need to go to {{ register_link }}

    If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org

    From 126479efecb454010464308b84de24c614a8a438 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jan 2020 12:22:08 -0800 Subject: [PATCH 0564/1137] fixed github api issue --- gsoc/common/utils/tools.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 0797a37d..9bbebb50 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -73,7 +73,11 @@ def push_site_template(file_path, content): def push_images(file_path, content): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) - repo.create_file(file_path, f"Add {file_path} logo", content) + f = repo.get_contents(file_path) + if f.sha is not None: + repo.update_file(file_path, f"Add {file_path} logo", content, f.sha) + else: + repo.create_file(file_path, f"Add {file_path} logo", content) def is_year(file_name): From 435df6fb0cb34ed28b04a3b0f15d6b88b4027dcf Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:45:45 -0700 Subject: [PATCH 0565/1137] Update models.py --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 872baab6..38263be5 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -302,7 +302,7 @@ class SubOrgDetails(models.Model): mailing_list = models.CharField(max_length=80, null=True, blank=True) twitter_url = models.URLField(null=True, blank=True) blog_url = models.URLField(null=True, blank=True) - link = models.URLField(null=True, blank=True, verbose_name="Any other link") + #link = models.URLField(null=True, blank=True, verbose_name="Any other link") last_message = models.TextField(null=True, blank=True) last_reviewed_at = models.DateTimeField(null=True, blank=True) From 25be665ed9dcd5dbba5f0454050cd9c89f0ec00c Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:46:40 -0700 Subject: [PATCH 0566/1137] Update models.py --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 38263be5..dcb2738c 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -302,7 +302,7 @@ class SubOrgDetails(models.Model): mailing_list = models.CharField(max_length=80, null=True, blank=True) twitter_url = models.URLField(null=True, blank=True) blog_url = models.URLField(null=True, blank=True) - #link = models.URLField(null=True, blank=True, verbose_name="Any other link") + link = models.URLField(null=True, blank=True, verbose_name="Homepage") last_message = models.TextField(null=True, blank=True) last_reviewed_at = models.DateTimeField(null=True, blank=True) From 1f6e03cf35efdd80628d232ac80a340d22a4fa8d Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:49:46 -0700 Subject: [PATCH 0567/1137] Update commands.py --- gsoc/common/utils/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index 7290cf1f..bd1823f0 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -138,7 +138,7 @@ def update_site_template(scheduler: Scheduler): } elif template == "index.html": # change this if the number of contact fields increase - contact_fields = ("chat", "mailing_list", "twitter_url", "blog_url", "link") + contact_fields = ("chat", "mailing_list", "twitter_url", "blog_url", "homepage") suborgs = SubOrgDetails.objects.filter( gsoc_year=gsoc_year, accepted=True ).all() From afe2de0cd773bc1521b9426efa6184b9dd94abe0 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:50:56 -0700 Subject: [PATCH 0568/1137] Update models.py --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index dcb2738c..43be364e 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -302,7 +302,7 @@ class SubOrgDetails(models.Model): mailing_list = models.CharField(max_length=80, null=True, blank=True) twitter_url = models.URLField(null=True, blank=True) blog_url = models.URLField(null=True, blank=True) - link = models.URLField(null=True, blank=True, verbose_name="Homepage") + homepage = models.URLField(null=True, blank=True, verbose_name="Homepage") last_message = models.TextField(null=True, blank=True) last_reviewed_at = models.DateTimeField(null=True, blank=True) From 29eeed49181d3bb299a1aaf6694be51ec59d06dc Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:51:33 -0700 Subject: [PATCH 0569/1137] Update forms.py --- gsoc/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/forms.py b/gsoc/forms.py index 294aed86..cdb88555 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -106,7 +106,7 @@ def clean(self): cd.get("mailing_list", None), cd.get("twitter_url", None), cd.get("blog_url", None), - cd.get("link", None), + cd.get("homepage", None), ] contact = list(filter(lambda a: a is not None, contact)) From a7ee67c3c8b12981d23dff28debc0eeb49022aac Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:52:11 -0700 Subject: [PATCH 0570/1137] Update admin.py --- gsoc/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 4dbe0bdf..9acfb31f 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -609,7 +609,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): "mailing_list", "twitter_url", "blog_url", - "link", + "homepage", "accepted", "changed", "last_reviewed_at", From e92b5cf3fdf3e3e0385994c1195a5d88565c2167 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 13:52:58 -0700 Subject: [PATCH 0571/1137] Update admin.py --- gsoc/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 9acfb31f..532508bf 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -641,7 +641,7 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): "mailing_list", "twitter_url", "blog_url", - "link", + "homepage", "changed", "accepted", "last_reviewed_at", From 2952819b01da0f8af16f4634e6df3308062169b9 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jan 2020 12:53:47 -0800 Subject: [PATCH 0572/1137] change link to homepage --- gsoc/migrations/0055_auto_20200109_2053.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 gsoc/migrations/0055_auto_20200109_2053.py diff --git a/gsoc/migrations/0055_auto_20200109_2053.py b/gsoc/migrations/0055_auto_20200109_2053.py new file mode 100644 index 00000000..930c1781 --- /dev/null +++ b/gsoc/migrations/0055_auto_20200109_2053.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.7 on 2020-01-09 20:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0054_auto_20190817_1209'), + ] + + operations = [ + migrations.RemoveField( + model_name='suborgdetails', + name='link', + ), + migrations.AddField( + model_name='suborgdetails', + name='homepage', + field=models.URLField(blank=True, null=True, verbose_name='Homepage'), + ), + ] From ea63ff164887112707dab37f7c4d54db81bd5015 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 14:03:11 -0700 Subject: [PATCH 0573/1137] Update index.html --- gsoc/templates/site/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index ac13b1df..735ebfce 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -349,6 +349,7 @@

    Ideas

    +
    {% for suborg in suborgs %}
    From 9d6266f197dc500b7a6e6f08a0b0926fb32dc5a5 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 14:07:12 -0700 Subject: [PATCH 0574/1137] Update index.html --- gsoc/templates/site/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 735ebfce..53c3d1a2 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -346,10 +346,10 @@

    Ideas

    - +
    -
    + {% for suborg in suborgs %}
    From a01957f692cd4be920911e492cf6049e8933439f Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 14:10:49 -0700 Subject: [PATCH 0575/1137] Update index.html --- gsoc/templates/site/index.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 53c3d1a2..77b777c3 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -450,9 +450,6 @@

  • Matrix room at #python-gsoc:matrix.python-gsoc.org (Includes Android client!)
  • -
  • Get a Slack - invite (Our Slack Workspace is: python-gsoc.slack.com) -
  • Old freenode webchat: #python-gsoc on irc.freenode.net From 64330ca14162f0fe85efcaf76d7bc120cef36858 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 14:45:01 -0700 Subject: [PATCH 0576/1137] Update deadlines.html --- gsoc/templates/site/deadlines.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index 0b208e37..1160c4dd 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -122,7 +122,7 @@

    Blogging schedule (Student Deadlines)

    - iCal Link + iCal Link

    Please note Google's GSoC dates and deadlines.

    @@ -140,9 +140,11 @@

    Blogging schedule (Student Deadlines)

    }; var offset = new Date().getTimezoneOffset(); var timezone = moment.tz.guess(offset).replaceAll('/', '%2F') - cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=uhambjin8qdb9af4vt03c4djg4%40group.calendar.google.com&color=%23853104&ctz=${timezone}` + cal1Url = ` + https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23ffffff&ctz=America%2FDenver&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23B08B59&showTitle=0&showNav=1&showDate=1&showPrint=1&showTabs=1&showCalendars=0&showTz=1 + https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}` document.getElementById('cal1').src = cal1Url; - cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=uhambjin8qdb9af4vt03c4djg4%40group.calendar.google.com&color=%23853104&ctz=${timezone}&mode=AGENDA` + cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}&mode=AGENDA` document.getElementById('cal2').src = cal2Url; From 57655b192d93cb1109e6d1d0361887cf56692907 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 9 Jan 2020 14:45:57 -0700 Subject: [PATCH 0577/1137] Update deadlines.html --- gsoc/templates/site/deadlines.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index 1160c4dd..d068af92 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -140,9 +140,7 @@

    Blogging schedule (Student Deadlines)

    }; var offset = new Date().getTimezoneOffset(); var timezone = moment.tz.guess(offset).replaceAll('/', '%2F') - cal1Url = ` - https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23ffffff&ctz=America%2FDenver&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23B08B59&showTitle=0&showNav=1&showDate=1&showPrint=1&showTabs=1&showCalendars=0&showTz=1 - https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}` + cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}` document.getElementById('cal1').src = cal1Url; cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}&mode=AGENDA` document.getElementById('cal2').src = cal2Url; From 8d787a85c1fd7967923e3544b2fa15c21aa71f65 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jan 2020 14:57:56 -0800 Subject: [PATCH 0578/1137] always delete --- gsoc/models.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 43be364e..2f339b9c 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1230,7 +1230,10 @@ def event_publish_to_github_pages(sender, instance, **kwargs): # Delete Event from Calendar when obj is deleted @receiver(models.signals.pre_delete, sender=Event) def event_delete_from_calendar(sender, instance, **kwargs): - instance.delete_from_calendar() + try: + instance.delete_from_calendar() + except Exception: + pass # Add respective Schedulers and Builders @@ -1282,7 +1285,10 @@ def duedate_publish_to_github_pages(sender, instance, **kwargs): # Delete BlogPostDueDate from Calendar when obj is deleted @receiver(models.signals.pre_delete, sender=BlogPostDueDate) def due_date_delete_from_calendar(sender, instance, **kwargs): - instance.delete_from_calendar() + try: + instance.delete_from_calendar() + except Exception: + pass # Add Send RegLink Schedulers when RegLink is created From 679c77fa4806137bace2ef6221b60d63ff593020 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Tue, 3 Mar 2020 11:56:14 -0700 Subject: [PATCH 0579/1137] Update models.py --- gsoc/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 2f339b9c..c23de56a 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -896,8 +896,8 @@ def create_user( ) if not status: profiles = user.userprofile_set.all() - for _ in profiles: - github_handle = profile.github_handle + # for _ in profiles: + # github_handle = profile.github_handle role = {k: v for v, k in UserProfile.ROLES} profile = UserProfile.objects.create( From 21bc4c47ea051f32e1b19450fd9e26538c06889f Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Sun, 26 Apr 2020 23:50:10 -0700 Subject: [PATCH 0580/1137] Added "gsoc invited" checkbox for mentors --- gsoc/admin.py | 2 ++ .../0056_userprofile_gsoc_invited.py | 18 ++++++++++++++++++ gsoc/models.py | 1 + 3 files changed, 21 insertions(+) create mode 100644 gsoc/migrations/0056_userprofile_gsoc_invited.py diff --git a/gsoc/admin.py b/gsoc/admin.py index 532508bf..047ce457 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -372,6 +372,7 @@ class HiddenUserProfileAdmin(admin.ModelAdmin): "hidden", "reminder_disabled", "current_blog_count", + "gsoc_invited", ) list_filter = ("hidden", "reminder_disabled") readonly_fields = ( @@ -397,6 +398,7 @@ class HiddenUserProfileAdmin(admin.ModelAdmin): "blog_link", "current_blog_count", "github_handle", + "gsoc_invited", ) }, ), diff --git a/gsoc/migrations/0056_userprofile_gsoc_invited.py b/gsoc/migrations/0056_userprofile_gsoc_invited.py new file mode 100644 index 00000000..52188c14 --- /dev/null +++ b/gsoc/migrations/0056_userprofile_gsoc_invited.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-04-27 06:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0055_auto_20200109_2053'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='gsoc_invited', + field=models.BooleanField(default=False), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index c23de56a..c6f55d15 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -451,6 +451,7 @@ class UserProfile(models.Model): reminder_disabled = models.BooleanField(default=False) current_blog_count = models.IntegerField(default=0) github_handle = models.TextField(null=True, blank=True, max_length=100) + gsoc_invited = models.BooleanField(default=False) objects = UserProfileManager() all_objects = models.Manager() From 9fffae5c183e79427d9fdf456e17293ed2526aff Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Mon, 27 Apr 2020 00:27:19 -0700 Subject: [PATCH 0581/1137] Added a UniqueConstraint for UserProfiles, partially fixes #365 --- gsoc/migrations/0057_auto_20200427_0720.py | 17 +++++++++++++++++ gsoc/models.py | 7 +++++++ 2 files changed, 24 insertions(+) create mode 100644 gsoc/migrations/0057_auto_20200427_0720.py diff --git a/gsoc/migrations/0057_auto_20200427_0720.py b/gsoc/migrations/0057_auto_20200427_0720.py new file mode 100644 index 00000000..15a0ad58 --- /dev/null +++ b/gsoc/migrations/0057_auto_20200427_0720.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.12 on 2020-04-27 07:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0056_userprofile_gsoc_invited'), + ] + + operations = [ + migrations.AddConstraint( + model_name='userprofile', + constraint=models.UniqueConstraint(fields=('suborg_full_name', 'user', 'gsoc_year'), name='unique_draft_user'), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index c6f55d15..515e696d 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -456,6 +456,13 @@ class UserProfile(models.Model): objects = UserProfileManager() all_objects = models.Manager() + class Meta: + constraints = [ + models.UniqueConstraint(fields=['suborg_full_name', 'user', + 'gsoc_year'], + name='unique_draft_user') + ] + def confirm_proposal(self): self.proposal_confirmed = True self.save() From c9ce65df4ed89abf52fc8665b6f5cf988577a443 Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Wed, 27 May 2020 12:48:35 -0700 Subject: [PATCH 0582/1137] No longer displays suborg applications menu choices for students, fixes #382 --- gsoc/cms_toolbars.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 87d09d57..99eacfee 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -22,7 +22,7 @@ from cms.models import Page -from gsoc.models import ArticleReview +from gsoc.models import ArticleReview, UserProfile def add_admin_menu(self): @@ -51,6 +51,11 @@ def add_admin_menu(self): user = getattr(self.request, "user", None) + if user: + user_profile = UserProfile.objects.filter(user=user)[0] + role = user_profile.get_role_display() + # role is 'Others', 'Suborg Admin', 'Mentor', or 'Student' + # admin self._admin_menu.add_sideframe_item( _("Administration"), url=admin_reverse("index") @@ -83,7 +88,7 @@ def add_admin_menu(self): on_close=None, ) - if user: + if user and role != 'Student': self._admin_menu.add_link_item( _("New Suborg Application"), reverse("suborg:register_suborg") ) From 8c995057d9d3edebc1ab1725c16548a7c356b381 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:33:45 -0600 Subject: [PATCH 0583/1137] Create .anylint --- .anylint | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .anylint diff --git a/.anylint b/.anylint new file mode 100644 index 00000000..ae44bdd8 --- /dev/null +++ b/.anylint @@ -0,0 +1,3 @@ +{ + "disable": ["html"] +} From f21d545adf8620d463f7d68eb92c4bc42414012a Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:37:40 -0600 Subject: [PATCH 0584/1137] Update CODE_OF_CONDUCT.md --- docs/CODE_OF_CONDUCT.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 485fcf23..d6075ead 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -1,35 +1,22 @@ # Python Community Code of Conduct - The Python community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences great successes and continued growth. When you're working with members of the community, we encourage you to follow these guidelines which help steer our interactions and strive to keep Python a positive, successful, and growing community. - A member of the Python community is: - Open - ==== - Members of the community are open to collaboration, whether it's on PEPs, patches, problems, or otherwise. We're receptive to constructive comment and criticism, as the experiences and skill sets of other members contribute to the whole of our efforts. We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate and everyone can make a difference. - Considerate - =========== - Members of the community are considerate of their peers -- other Python users. We're thoughtful when addressing the efforts of others, keeping in mind that often times the labor was completed simply for the good of the community. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. - Respectful - ========== - Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments, and their efforts. We're respectful of the volunteer efforts that permeate the Python community. We're respectful of the processes set forth in the community, and we work within them. When we disagree, we are courteous in raising our issues. - - Overall, we're good to each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. From 49978bd963b1f3c6e337c96b2e69dfbc179ebbf7 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:46:20 -0600 Subject: [PATCH 0585/1137] Update CODE_OF_CONDUCT.md --- docs/CODE_OF_CONDUCT.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index d6075ead..dcf25ea2 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -4,18 +4,15 @@ The Python community is made up of members from around the globe with a diverse A member of the Python community is: -Open -==== +## Open Members of the community are open to collaboration, whether it's on PEPs, patches, problems, or otherwise. We're receptive to constructive comment and criticism, as the experiences and skill sets of other members contribute to the whole of our efforts. We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate and everyone can make a difference. -Considerate -=========== +## Considerate Members of the community are considerate of their peers -- other Python users. We're thoughtful when addressing the efforts of others, keeping in mind that often times the labor was completed simply for the good of the community. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. -Respectful -========== +## Respectful Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments, and their efforts. We're respectful of the volunteer efforts that permeate the Python community. We're respectful of the processes set forth in the community, and we work within them. When we disagree, we are courteous in raising our issues. From 6e245aecbe567f66b787cacf32558b6ae471ef31 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:49:07 -0600 Subject: [PATCH 0586/1137] Update .anylint --- .anylint | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.anylint b/.anylint index ae44bdd8..2fb48126 100644 --- a/.anylint +++ b/.anylint @@ -1,3 +1,4 @@ { - "disable": ["html"] + "disable": ["html"], + "ignore": ["docs"] } From 3f0c8f9567b3fdeb970b7b411822249e77574828 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:50:16 -0600 Subject: [PATCH 0587/1137] Update .anylint --- .anylint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.anylint b/.anylint index 2fb48126..727770e3 100644 --- a/.anylint +++ b/.anylint @@ -1,4 +1,4 @@ { "disable": ["html"], - "ignore": ["docs"] + "ignore": ["docs/"] } From d1ddb1bce54e7ef1efc78e46a7c189ba62e47118 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:52:58 -0600 Subject: [PATCH 0588/1137] Update .anylint --- .anylint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.anylint b/.anylint index 727770e3..9fbdaba8 100644 --- a/.anylint +++ b/.anylint @@ -1,4 +1,4 @@ { "disable": ["html"], - "ignore": ["docs/"] + "ignore": ["docs/", "gsoc/static/"] } From 72e593d559427d195e84a62a13ead01ab4a211a6 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:56:48 -0600 Subject: [PATCH 0589/1137] Update .anylint --- .anylint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.anylint b/.anylint index 9fbdaba8..6c8e290e 100644 --- a/.anylint +++ b/.anylint @@ -1,4 +1,4 @@ { "disable": ["html"], - "ignore": ["docs/", "gsoc/static/"] + "ignore": ["docs/", "gsoc/static/djangocms_text_ckeditor/ckeditor/plugins/"] } From 4c8562222efeca694af5c055da410fb403df71c7 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:57:29 -0600 Subject: [PATCH 0590/1137] Update builddb.bat --- scripts/builddb.bat | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/builddb.bat b/scripts/builddb.bat index 821c93e5..b43b1548 100644 --- a/scripts/builddb.bat +++ b/scripts/builddb.bat @@ -1,3 +1,5 @@ +#!/bin/bash + del project.db del users.db python manage.py migrate --database default From 23a45be66ee4b61355463bd5f69cbe51cf412092 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:57:36 -0600 Subject: [PATCH 0591/1137] Update builddb.sh --- scripts/builddb.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/builddb.sh b/scripts/builddb.sh index 79eb2e08..73064c6d 100644 --- a/scripts/builddb.sh +++ b/scripts/builddb.sh @@ -1,3 +1,5 @@ +#!/bin/bash + rm project.db rm users.db python manage.py migrate --database default From 141493ce1ddf81259017d0296caa96bf741174f6 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:57:43 -0600 Subject: [PATCH 0592/1137] Update cleangit.sh --- scripts/cleangit.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/cleangit.sh b/scripts/cleangit.sh index e5d89837..95219c55 100644 --- a/scripts/cleangit.sh +++ b/scripts/cleangit.sh @@ -1,3 +1,5 @@ +#!/bin/bash + git checkout master git clean -fxd && git reset --hard git checkout master From a2ea2f3920b0132523ddf7242e0399a389ba4830 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 16:57:50 -0600 Subject: [PATCH 0593/1137] Update migrate.sh --- scripts/migrate.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/migrate.sh b/scripts/migrate.sh index 7064e9ad..c9909786 100644 --- a/scripts/migrate.sh +++ b/scripts/migrate.sh @@ -1,3 +1,5 @@ +#!/bin/bash + python manage.py makemigrations python manage.py migrate python manage.py makemigrations gsoc From bbb6736ab22f6dc0d926951fcb6f63a65217b495 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 17:04:26 -0600 Subject: [PATCH 0594/1137] Update comments.js --- gsoc/static/js/comments.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/gsoc/static/js/comments.js b/gsoc/static/js/comments.js index d44399d3..c9f0f783 100644 --- a/gsoc/static/js/comments.js +++ b/gsoc/static/js/comments.js @@ -1,6 +1,7 @@ +/*jshint esversion: 6 */ const sleep = (milliseconds) => { - return new Promise(resolve => setTimeout(resolve, milliseconds)) -} + return new Promise(resolve => setTimeout(resolve, milliseconds)); +}; function showCommentForm(commentPk) { var form = document.getElementById('form-' + commentPk); @@ -9,7 +10,7 @@ function showCommentForm(commentPk) { } function copyCommentUrl(commentPk) { - var href = window.location.href.split('#')[0] + var href = window.location.href.split('#')[0]; var str = href + '#comment-' + commentPk; var el = document.createElement('textarea'); el.value = str; @@ -21,10 +22,10 @@ function copyCommentUrl(commentPk) { document.body.removeChild(el); var share = document.getElementById(`share-${commentPk}`); - share.innerHTML = "Link copied!" + share.innerHTML = "Link copied!"; sleep(3000).then(() => { share.innerHTML = `Share`; - }) + }); } function deleteComment(commentPk) { @@ -33,7 +34,7 @@ function deleteComment(commentPk) { del.innerHTML = 'Confirm Delete?'; sleep(3000).then(() => { del.innerHTML = 'Delete'; - }) + }); } else if (del.innerHTML == 'Confirm Delete?') { document.getElementById(`delete-form-${commentPk}`).submit(); @@ -41,11 +42,11 @@ function deleteComment(commentPk) { } (function markSelected() { - var parts = window.location.href.split('#') + var parts = window.location.href.split('#'); if (parts.length == 2 && parts[1].split('-')[0] == 'comment') { var comment = document.getElementById(parts[1]); if (comment) { - comment.classList.add('selected') + comment.classList.add('selected'); } } }()); @@ -53,7 +54,7 @@ function deleteComment(commentPk) { function updateCharCount(formId) { var textAreaEl = document.getElementById(`comment-textarea-${formId}`); var remainingCharEl = document.getElementById(`remaining-chars-${formId}`); - len = textAreaEl.value.length; + var len = textAreaEl.value.length; var remaining = 1000 - len; remainingCharEl.innerHTML = `${remaining} characters left`; } From e768d133299c58dffc044f26604d76a6b949be2d Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 27 May 2020 17:10:36 -0600 Subject: [PATCH 0595/1137] cleanup js --- gsoc/static/js/proposal.js | 51 +++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/gsoc/static/js/proposal.js b/gsoc/static/js/proposal.js index 04dea14d..6d17bf44 100644 --- a/gsoc/static/js/proposal.js +++ b/gsoc/static/js/proposal.js @@ -1,10 +1,11 @@ +/*jshint esversion: 6 */ function cancelProposalUpload() { axios.get('/cancel_proposal_upload/').then( function(resp) { inPageInfo("Proposal upload canceled."); setProposalUploadingStatus(false); } - ) + ); } function setProposalUploadingStatus(status) { const button = document.querySelector("#upload-proposal-button"); @@ -48,11 +49,11 @@ function hideInfoBox() { } function beforeUpload() { const offlineCancel = function(){ - inPageInfo("Proposal upload canceled.") + inPageInfo("Proposal upload canceled."); }; - const infoText = 'Please make sure there is no private data in your pdf file as this WILL be shown publically on the internet. Confirm?' + const infoText = 'Please make sure there is no private data in your pdf file as this WILL be shown publically on the internet. Confirm?'; inPageInfo(infoText, false); - showInfoBoxBtns(uploadProposal, offlineCancel) + showInfoBoxBtns(uploadProposal, offlineCancel); } function hideInfoBoxBtns() { const btn1 = document.querySelector('#infoBtn1'); @@ -65,8 +66,12 @@ function hideInfoBoxBtns() { function showInfoBoxBtns(callback1, callback2) { const btn1 = document.querySelector('#infoBtn1'); const btn2 = document.querySelector('#infoBtn2'); - btn1.onclick = function(){hideInfoBoxBtns(); hideInfoBox(); callback1()}; - btn2.onclick = function(){hideInfoBoxBtns(); hideInfoBox(); callback2()}; + btn1.onclick = function(){ + hideInfoBoxBtns(); + hideInfoBox(); + callback1(); + }; + btn2.onclick = function(){hideInfoBoxBtns(); hideInfoBox(); callback2();}; btn1.style.display = 'inline'; btn2.style.display = 'inline'; } @@ -85,32 +90,32 @@ function uploadProposal() { const uploadForm = document.querySelector('#upload-proposal-form'); axios.post( '/upload-proposal/', - new FormData(uploadForm), + new FormData(uploadForm) ) .then(function(resp) { - if(!resp.data['file_type_valid']) { + if(!resp.data.file_type_valid) { inPageInfo("Your file doesn't seem to be a pdf file. Please check again!"); setProposalUploadingStatus(false); - return + return; } - if(!resp.data['file_not_too_large']) { + if(!resp.data.file_not_too_large) { inPageInfo("Your file is larger than 20MB. Please make it smaller!"); setProposalUploadingStatus(false); - return + return; } - const privateData = resp.data['private_data']; - if(privateData["emails"].length > 0 || - privateData["possible_phone_numbers"].length > 0 || - privateData["locations"].length > 0) { + const privateData = resp.data.private_data; + if(privateData.emails.length > 0 || + privateData.possible_phone_numbers.length > 0 || + privateData.locations.length > 0) { let confirmText = "We seemed to have found private data in your pdf file. WE DO NOT RECOMEND UPLOADING A PDF WITH PHONE NUMBERS, PHYSICAL ADDRESS, OR EMAIL ADDRESSES AS THIS WILL BE SHOWN PUBLICALLY ON THE INTERNET. Are you sure to proceed?"; - if (privateData['emails'].length > 0) - confirmText += `
    Email addresses: ${privateData['emails'].toString()}` - if(privateData['possible_phone_numbers'].length > 0) - confirmText += `
    Possible phone numbers: ${privateData['possible_phone_numbers'].toString()}` - if(privateData['locations'].length > 0) - confirmText += `
    Locations: ${privateData['locations'].toString()}` + if (privateData.emails.length > 0) + confirmText += `
    Email addresses: ${privateData.emails.toString()}`; + if(privateData.possible_phone_numbers.length > 0) + confirmText += `
    Possible phone numbers: ${privateData.possible_phone_numbers.toString()}`; + if(privateData.locations.length > 0) + confirmText += `
    Locations: ${privateData.locations.toString()}`; onFindPrivateData(confirmText); - return + return; } axios.get('/confirm_proposal'); setProposalUploadingStatus(false); @@ -119,5 +124,5 @@ function uploadProposal() { .catch(function(err) { setProposalUploadingStatus(false); console.log(err); - }) + }); } From 796fc916258d143751e2aab2734ac097dbaf6f37 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 27 May 2020 17:15:01 -0700 Subject: [PATCH 0596/1137] only count current year --- gsoc/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index 515e696d..06f1e346 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1156,7 +1156,8 @@ def update_blog_counter(sender, instance, **kwargs): if not instance.pk: # increase blog counter date = timezone.now() + datetime.timedelta(days=6) - due_dates = BlogPostDueDate.objects.filter(date__lt=date).all() + currentYear = datetime.datetime.now().year + due_dates = BlogPostDueDate.objects.filter(date__year=currentYear, date__lt=date).all() instance.current_blog_count = len(due_dates) From a22366bd2e1d1c1c00964331a1b988ac7f069576 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 May 2020 15:12:47 -0600 Subject: [PATCH 0597/1137] Update settings.py --- gsoc/settings.py | 135 ++++++++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 55 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index ab3231aa..61685f85 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -95,7 +95,7 @@ def gettext(s): } ] -MIDDLEWARE = ( +MIDDLEWARE = [ "debug_toolbar.middleware.DebugToolbarMiddleware", "django.middleware.cache.UpdateCacheMiddleware", "cms.middleware.utils.ApphookReloadMiddleware", @@ -111,56 +111,73 @@ def gettext(s): "cms.middleware.toolbar.ToolbarMiddleware", "cms.middleware.language.LanguageCookieMiddleware", "django.middleware.cache.FetchFromCacheMiddleware", -) +] -INSTALLED_APPS = ( - "djangocms_admin_style", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.admin", - "django.contrib.sites", - "django.contrib.sitemaps", - "django.contrib.staticfiles", - "django.contrib.messages", - "cms", - "menus", - "treebeard", - "sekizai", - "djangocms_text_ckeditor", - "djangocms_history", - "easy_thumbnails", - "filer", - "djangocms_audio", - "djangocms_video", - "djangocms_file", - "djangocms_picture", -# "djangocms_column", - "djangocms_link", - "djangocms_style", - "djangocms_snippet", - "aldryn_apphooks_config", - "aldryn_categories", - "aldryn_common", - "aldryn_newsblog", - "aldryn_people", - "aldryn_translation_tools", - "parler", - "sortedm2m", - "taggit", - "gsoc", - "blogs_list", - "suborg", - "debug_toolbar", - "django_simple_cookie_consent", -) +INSTALLED_APPS = [ + 'djangocms_admin_style', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.admin', + 'django.contrib.sites', + 'django.contrib.sitemaps', + 'django.contrib.staticfiles', + 'django.contrib.messages', + 'cms', + 'menus', + 'sekizai', + 'treebeard', + 'djangocms_text_ckeditor', + 'djangocms_history', + 'easy_thumbnails', + 'filer', + + 'easy_thumbnails', + 'djangocms_bootstrap4', + 'djangocms_bootstrap4.contrib.bootstrap4_alerts', + 'djangocms_bootstrap4.contrib.bootstrap4_badge', + 'djangocms_bootstrap4.contrib.bootstrap4_card', + 'djangocms_bootstrap4.contrib.bootstrap4_carousel', + 'djangocms_bootstrap4.contrib.bootstrap4_collapse', + 'djangocms_bootstrap4.contrib.bootstrap4_content', + 'djangocms_bootstrap4.contrib.bootstrap4_grid', + 'djangocms_bootstrap4.contrib.bootstrap4_jumbotron', + 'djangocms_bootstrap4.contrib.bootstrap4_link', + 'djangocms_bootstrap4.contrib.bootstrap4_listgroup', + 'djangocms_bootstrap4.contrib.bootstrap4_media', + 'djangocms_bootstrap4.contrib.bootstrap4_picture', + 'djangocms_bootstrap4.contrib.bootstrap4_tabs', + 'djangocms_bootstrap4.contrib.bootstrap4_utilities', + 'djangocms_file', + 'djangocms_icon', + 'djangocms_link', + 'djangocms_picture', + 'djangocms_style', + 'djangocms_snippet', + 'djangocms_googlemap', + 'djangocms_video', + + 'djangocms_audio', + 'aldryn_apphooks_config', + 'aldryn_categories', + 'aldryn_common', + 'aldryn_newsblog', + 'aldryn_people', + 'aldryn_translation_tools', + 'parler', + 'sortedm2m', + 'taggit', + 'gsoc', + 'blogs_list', + 'suborg', + 'debug_toolbar', + 'django_simple_cookie_consent', +] THUMBNAIL_PROCESSORS = ( - "easy_thumbnails.processors.colorspace", - "easy_thumbnails.processors.autocrop", - # 'easy_thumbnails.processors.scale_and_crop', - "filer.thumbnail_processors.scale_and_crop_with_subject_location", - "easy_thumbnails.processors.filters", - "easy_thumbnails.processors.background", + 'easy_thumbnails.processors.colorspace', + 'easy_thumbnails.processors.autocrop', + 'filer.thumbnail_processors.scale_and_crop_with_subject_location', + 'easy_thumbnails.processors.filters' ) LANGUAGES = ( @@ -194,6 +211,8 @@ def gettext(s): ("myprofile.html", "My Profile"), ) +X_FRAME_OPTIONS = 'SAMEORIGIN' + CMS_PERMISSION = True CMS_PLACEHOLDER_CONF = {} @@ -204,12 +223,18 @@ def gettext(s): # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" - }, - {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, ] LOGIN_REDIRECT_URL = "/after-login/" From f2a8ca196a28160ee1e9224c2e12098424ca1355 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 May 2020 15:21:47 -0600 Subject: [PATCH 0598/1137] Update requirements.txt --- requirements.txt | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index a790fe7e..dd7215a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,28 @@ #django -django~=2.2.0 +django~=3.0.0 +django-filer>=1.3 +django-treebeard>=4.0,<5.0 #django debug django-debug-toolbar>=1.11 #django cms -django-cms~=3.7.0 -djangocms-text-ckeditor>=3.7.0 -djangocms-file>=2.2.0 -djangocms-link>=2.3.1 -djangocms-picture>=2.1.3 -djangocms-style>=2.1.0 -djangocms-snippet>=2.1.0 -djangocms-googlemap>=1.2.0 -djangocms-video>=2.1.1 +django-cms~=3.7.3 +djangocms-admin-style>=1.5,<1.6 +djangocms-text-ckeditor>=3.7,<4.0 +djangocms-file>=2.3,<2.5 +djangocms-link>=2.5,<2.7 +djangocms-icon>=1.4,<1.6 +djangocms-picture>=2.3,<2.5 +djangocms-bootstrap4>=1.5,<1.7 +djangocms-style>=2.2,<2.4 +djangocms-snippet>=2.2,<2.4 +djangocms-googlemap>=1.3,<1.5 +djangocms-video>=2.1,<2.4 djangocms-audio>=1.1.0 djangocms_history>=1.0.0 aldryn-newsblog>=2.2.1 +easy_thumbnails #djangocms_column>=1.9 #gsoc irc requirements @@ -48,3 +54,8 @@ django-simple-cookie-consent>=0.1.1 mysqlclient>=1.4.4 importlib-resources>=1.0.2 + +html5lib>=1.0.1 +Pillow>=3.0 +six +pytz From 2fdcdc914eedd6235b4bf6522da54693ac3d8033 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 May 2020 15:30:38 -0600 Subject: [PATCH 0599/1137] Update settings.py --- gsoc/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index 61685f85..361e2451 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -132,7 +132,6 @@ def gettext(s): 'easy_thumbnails', 'filer', - 'easy_thumbnails', 'djangocms_bootstrap4', 'djangocms_bootstrap4.contrib.bootstrap4_alerts', 'djangocms_bootstrap4.contrib.bootstrap4_badge', From 9d418914a18bff48ec58a35f777cf3c23930e339 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 May 2020 15:45:41 -0600 Subject: [PATCH 0600/1137] Update README.md --- docs/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/README.md b/docs/README.md index 36b022c8..6bb5150a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -92,4 +92,12 @@ Once you are done with the work, you can deactivate the virtual environment by t Now you will be back to system’s default Python installation. +notes: +for django 3 need to change in aldryn_newsblog + +from six import python_2_unicode_compatible + +instead of + +from django.utils.six import python_2_unicode_compatible From 79222d4a3ce700164fdd4491004841d234e8b562 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 May 2020 15:54:03 -0600 Subject: [PATCH 0601/1137] Update author.html --- gsoc/templates/aldryn_newsblog/includes/author.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/aldryn_newsblog/includes/author.html b/gsoc/templates/aldryn_newsblog/includes/author.html index 863020b8..e3bc43d6 100644 --- a/gsoc/templates/aldryn_newsblog/includes/author.html +++ b/gsoc/templates/aldryn_newsblog/includes/author.html @@ -1,4 +1,4 @@ -{% load i18n staticfiles thumbnail apphooks_config_tags %} +{% load i18n static thumbnail apphooks_config_tags %} {% if author %}

    From b2990c116fc973600e454d344353a3994fea1b1e Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sat, 30 May 2020 15:55:08 -0600 Subject: [PATCH 0602/1137] Update article.html --- gsoc/templates/aldryn_newsblog/includes/article.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/aldryn_newsblog/includes/article.html b/gsoc/templates/aldryn_newsblog/includes/article.html index 25b7befe..9918e1f2 100644 --- a/gsoc/templates/aldryn_newsblog/includes/article.html +++ b/gsoc/templates/aldryn_newsblog/includes/article.html @@ -1,4 +1,4 @@ -{% load i18n apphooks_config_tags cms_tags sekizai_tags staticfiles thumbnail %} +{% load i18n apphooks_config_tags cms_tags sekizai_tags static thumbnail %}

  • - iCal Link + iCal Link

    Please note Google's GSoC dates and deadlines.

    @@ -140,9 +140,9 @@

    Blogging schedule (Student Deadlines)

    }; var offset = new Date().getTimezoneOffset(); var timezone = moment.tz.guess(offset).replaceAll('/', '%2F') - cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}` + cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=oivfirmu8r2mc15kv1uhmmr01g%40group.calendar.google.com&color=%23853104&ctz=${timezone}` document.getElementById('cal1').src = cal1Url; - cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=NjQwOG9zOWlqOXV0b2xiZW5jMms0cXZ2OTRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ&color=%23853104&ctz=${timezone}&mode=AGENDA` + cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=oivfirmu8r2mc15kv1uhmmr01g%40group.calendar.google.com&color=%23853104&ctz=${timezone}&mode=AGENDA` document.getElementById('cal2').src = cal2Url; From c86880f6d2b3bed7d7dee801d38486815ed13936 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 22 Nov 2020 23:55:29 -0700 Subject: [PATCH 0638/1137] Update index.html --- gsoc/templates/site/index.html | 134 --------------------------------- 1 file changed, 134 deletions(-) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index 77b777c3..ce64017e 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -396,140 +396,6 @@

    Friends of the PSF

    - - -
    -
    -
    -

    Getting in Touch

    -

    - Please note that Python has a Community - Code of Conduct and mentors and - students working with the PSF are asked to abide by it as members of the - Python community. -

    -
    -
    -
    -
    -

    - - Mailing Lists. -

    -

    Sign up to the gsoc-general(at)python.org - mailing list to get updates, reminders, and to discuss questions. Please join the list - before you send a message! -

    -

    The most common questions are answered here:

    - -
    -
    -
    -

    - - IRC / Live chat -

    -

    - Our IRC channel is #python-gsoc - on - irc.freenode.net. (Don't know IRC? Learn more at - irchelp.org). -

    - -
    -
    -

    - - Specific sub-orgs -

    -

    To talk with people from a specific sub-org, check their ideas - page listing for their mailing lists, IRC, and other contact information. -

    -
    -
    -
    -

    - - Tips! -

    -
      -
    1. Read first. We've tried to answer the common questions on this site, and - we get asked things like "How do I get started?" and - "Where do I find easy bugs?" a lot. Check the - Frequently Asked Questions (FAQ) on the student page for - more! -
    2. -
    3. Be Patient! Our mentors typically have day jobs and can't always answer - right-away. If you can't hang out on IRC for an answer, send an email instead. -
    4. -
    5. Ask questions directly on IRC. You don't need to introduce - yourself or say hi first, just ask away! -
    6. -
    7. Communicate in public. That lets many mentors read your question so you - can usually get an answer faster. -
    8. -
    -

    For mentors: All the gsoc admins can be reached at - gsoc-admins(at)python(dot)org if you have questions about participating. - (Students should email gsoc-general(at)python.org with all of their - questions, unless they are of a sensitive personal nature.) -

    -
    -
    -

    - - Org admins -

    -

    The 2019 Python Software Foundation (PSF) org admin team:

    -
      -
    • Terri Oda (terri on IRC) - focus areas: figurehead, making final decisions, - website/documentation -
    • -
    • James Lopeman (meflin on IRC) - focus areas: IRC, ideas pages reviews, saying no
    • -
    • John Hawley (warthog9 on IRC) - focus areas: infrastructure, advice, emergency - mentoring/mentor - supervision. -
    • -
    • Matthew Lagoe (Botanic on IRC) - focus areas: student blogs, irc bot, marking sure things - happen on time -
    • -
    • Kushal Das (kushal on IRC) - focus areas: advice, time zone coverage
    • -
    -

    The org admins can be reached at gsoc-admins(at)python(dot)org (for mentors) - Students should almost always visit Getting Started first, and - email - gsoc-general(at)python.org only if you get stuck. -

    -

    We also have some "org admins emeritus" who may be able - to help you: -

    -
      -
    • Florian Fuchs (florianf on IRC)
    • -
    • Stephen Turnbull (yaseppochi on IRC)
    • -
    -
    -
    -

    Other Stuff

    From 5574f1fd681cc578d04837d77dcfa6056bbff01a Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 22 Nov 2020 23:59:16 -0700 Subject: [PATCH 0639/1137] Update index.html --- gsoc/templates/site/index.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/index.html index ce64017e..0205c254 100644 --- a/gsoc/templates/site/index.html +++ b/gsoc/templates/site/index.html @@ -104,8 +104,7 @@

    -

    We're accepting a limited number of new sub-orgs through March 5th! Email - gsoc-admins(at)python.org if interested. +

    We're accepting new sub-orgs! Email gsoc-admins(at)python.org if interested.

    @@ -340,9 +339,7 @@

    Ideas

    -

    We're accepting a limited number of new sub-orgs through March 5th, so a few new names may - appear - here up until around March 7th. +

    We're accepting suborgs until March 16, 2021 so new orgs may appear.

    From 1a11210ad372595ee4e07e2d5969d862d0af4c63 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 00:32:52 -0700 Subject: [PATCH 0640/1137] update subog page --- suborg/templates/application_list.html | 6 +++--- suborg/views.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/suborg/templates/application_list.html b/suborg/templates/application_list.html index 3b36b2c4..522a5e00 100644 --- a/suborg/templates/application_list.html +++ b/suborg/templates/application_list.html @@ -8,12 +8,12 @@

    Select any of the applications to change

  • {% if application.suborg %} - {{ application.suborg.suborg_name }} + {{ application.gsoc_year }} {{ application.suborg.suborg_name }} {% else %} - {{ application.suborg_name }} + {{ application.gsoc_year }} {{ application.suborg_name }} {% endif %} - {% if application.accepted %} + {% if application.accepted and gsoc_year == application.gsoc_year %} - Accepted, Add Mentors {% else %} - Not Accepted diff --git a/suborg/views.py b/suborg/views.py index f33e317d..e7347e5a 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -25,10 +25,11 @@ def home(request): @decorators.login_required def application_list(request): applications = SubOrgDetails.objects.filter(suborg_admin_email=request.user.email) + gsoc_year = GsocYear.objects.first() if len(applications) == 0: return redirect(reverse("suborg:register_suborg")) - return render(request, "application_list.html", {"applications": applications}) + return render(request, "application_list.html", {"applications": applications, "gsoc_year": gsoc_year}) @decorators.login_required From c967375a0ed33e4757794fdc3b1a22b8319c18d8 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 00:34:54 -0700 Subject: [PATCH 0641/1137] Update application_list.html display correct message --- suborg/templates/application_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suborg/templates/application_list.html b/suborg/templates/application_list.html index 522a5e00..fb6d1a2f 100644 --- a/suborg/templates/application_list.html +++ b/suborg/templates/application_list.html @@ -15,7 +15,7 @@

    Select any of the applications to change

    {% if application.accepted and gsoc_year == application.gsoc_year %} - Accepted, Add Mentors - {% else %} + {% elif gsoc_year == application.gsoc_year %} - Not Accepted {% endif %}
  • From 3ca070a5989e6998623c52f6c989c7daacf1e586 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 00:55:45 -0700 Subject: [PATCH 0642/1137] Update tools.py 404 fix --- gsoc/common/utils/tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 9bbebb50..6091bcf0 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -73,10 +73,10 @@ def push_site_template(file_path, content): def push_images(file_path, content): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) - f = repo.get_contents(file_path) - if f.sha is not None: + try: + f = repo.get_contents(file_path) repo.update_file(file_path, f"Add {file_path} logo", content, f.sha) - else: + except Exception as e: repo.create_file(file_path, f"Add {file_path} logo", content) From c575839c422c6fbe590e7d1026a709c332db565e Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 01:11:29 -0700 Subject: [PATCH 0643/1137] update suborg update form to auto submit --- suborg/templates/application_list.html | 21 ++++++++++++++------- suborg/templates/register_suborg.html | 2 +- suborg/views.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/suborg/templates/application_list.html b/suborg/templates/application_list.html index fb6d1a2f..32ddaf9a 100644 --- a/suborg/templates/application_list.html +++ b/suborg/templates/application_list.html @@ -6,13 +6,20 @@

    Select any of the applications to change

      {% for application in applications %}
    • - - {% if application.suborg %} - {{ application.gsoc_year }} {{ application.suborg.suborg_name }} - {% else %} - {{ application.gsoc_year }} {{ application.suborg_name }} - {% endif %} - + {% if gsoc_year == application.gsoc_year %} + + {% if application.suborg %} + {{ application.gsoc_year }} {{ application.suborg.suborg_name }} + {% else %} + {{ application.gsoc_year }} {{ application.suborg_name }} + {% endif %} + + {% elif application.suborg %} + {{ application.gsoc_year }} {{ application.suborg.suborg_name }} + {% else %} + {{ application.gsoc_year }} {{ application.suborg_name }} + {% endif %} + {% if application.accepted and gsoc_year == application.gsoc_year %} - Accepted, Add Mentors {% elif gsoc_year == application.gsoc_year %} diff --git a/suborg/templates/register_suborg.html b/suborg/templates/register_suborg.html index 86be5f57..fd16ec55 100644 --- a/suborg/templates/register_suborg.html +++ b/suborg/templates/register_suborg.html @@ -78,7 +78,7 @@

      Apply for participating in GSoC@PSF as a SubOrg!

      {% csrf_token %} {{ form }} - +
      {% endblock content %} diff --git a/suborg/views.py b/suborg/views.py index e7347e5a..3d4ccf1a 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -76,6 +76,18 @@ def update_application(request, application_id): suborg_details.updated_at = timezone.now() suborg_details.save() suborg_details.send_update_notification() + s = Scheduler.objects.filter( + command="update_site_template", + data=json.dumps({"template": "index.html"}), + success=None, + ).all() + if len(s) == 0: + time = timezone.now() + timezone.timedelta(minutes=5) + Scheduler.objects.create( + command="update_site_template", + data=json.dumps({"template": "index.html"}), + activation_date=time, + ) return redirect(reverse("suborg:post_register")) return render( From b9ef21c76af23d64845a4ea31c95af91c9802f00 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 01:24:44 -0700 Subject: [PATCH 0644/1137] Update views.py --- suborg/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/suborg/views.py b/suborg/views.py index 3d4ccf1a..c16fe32f 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -9,6 +9,9 @@ from django.contrib import messages from django.utils import timezone +from gsoc.models import ( + Scheduler +) def is_superuser(user): return user.is_superuser From e641737dd9bfd0e2fd2a839c2603f486f418e1df Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 01:27:39 -0700 Subject: [PATCH 0645/1137] Update views.py --- suborg/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/suborg/views.py b/suborg/views.py index c16fe32f..4e6cc77f 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -13,6 +13,8 @@ Scheduler ) +import json + def is_superuser(user): return user.is_superuser From 552dc78aab723c5a4b6a689dbae961d531e0b1d3 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 01:44:55 -0700 Subject: [PATCH 0646/1137] remove comments --- gsoc/templates/aldryn_newsblog/article_detail.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/gsoc/templates/aldryn_newsblog/article_detail.html b/gsoc/templates/aldryn_newsblog/article_detail.html index 6f09f7bc..84161867 100644 --- a/gsoc/templates/aldryn_newsblog/article_detail.html +++ b/gsoc/templates/aldryn_newsblog/article_detail.html @@ -28,7 +28,4 @@
    -
    - {% include "aldryn_newsblog/includes/comments.html" with comments=article.get_root_comments article=article user=user csrf_token=csrf_token recaptcha_site_key=recaptcha_site_key only %} -
    {% endblock %} \ No newline at end of file From a80a44ff3479a0d1f8011f2e84bcb232005f1ee9 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 02:02:40 -0700 Subject: [PATCH 0647/1137] add wildcards --- aldryn_newsblog/urls.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/aldryn_newsblog/urls.py b/aldryn_newsblog/urls.py index 5d145ce1..8abe10b2 100644 --- a/aldryn_newsblog/urls.py +++ b/aldryn_newsblog/urls.py @@ -9,17 +9,18 @@ urlpatterns = [ - url(r'^', ArticleList.as_view(), name='article-list'), - url(r'^feed/', LatestArticlesFeed(), name='article-list-feed'), + url(r'^$', + ArticleList.as_view(), name='article-list'), + url(r'^feed/$', LatestArticlesFeed(), name='article-list-feed'), - url(r'^search/', + url(r'^search/$', ArticleSearchResultsList.as_view(), name='article-search'), - url(r'^(?P\d{4})/', + url(r'^(?P\d{4})/$', YearArticleList.as_view(), name='article-list-by-year'), - url(r'^(?P\d{4})/(?P\d{1,2})/', + url(r'^(?P\d{4})/(?P\d{1,2})/$', MonthArticleList.as_view(), name='article-list-by-month'), - url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/', + url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/$', DayArticleList.as_view(), name='article-list-by-day'), # Various permalink styles that we support @@ -28,28 +29,29 @@ # NOTE: We cannot support /year/month/pk, /year/pk, or /pk, since these # patterns collide with the list/archive views, which we'd prefer to # continue to support. - url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\d+)/', + url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\d+)/$', ArticleDetail.as_view(), name='article-detail'), # These support permalinks with - url(r'^(?P\w[-\w]*)/', + url(r'^(?P\w[-\w]*)/$', ArticleDetail.as_view(), name='article-detail'), - url(r'^(?P\d{4})/(?P\w[-\w]*)/', + url(r'^(?P\d{4})/(?P\w[-\w]*)/$', ArticleDetail.as_view(), name='article-detail'), - url(r'^(?P\d{4})/(?P\d{1,2})/(?P\w[-\w]*)/', + url(r'^(?P\d{4})/(?P\d{1,2})/(?P\w[-\w]*)/$', ArticleDetail.as_view(), name='article-detail'), - url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\w[-\w]*)/', # flake8: noqa + url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\w[-\w]*)/$', # flake8: NOQA ArticleDetail.as_view(), name='article-detail'), - url(r'^author/(?P\w[-\w]*)/', + url(r'^author/(?P\w[-\w]*)/$', AuthorArticleList.as_view(), name='article-list-by-author'), - url(r'^category/(?P\w[-\w]*)/', + url(r'^category/(?P\w[-\w]*)/$', CategoryArticleList.as_view(), name='article-list-by-category'), - url(r'^category/(?P\w[-\w]*)/feed/', + url(r'^category/(?P\w[-\w]*)/feed/$', CategoryFeed(), name='article-list-by-category-feed'), - url(r'^tag/(?P\w[-\w]*)/', + url(r'^tag/(?P\w[-\w]*)/$', TagArticleList.as_view(), name='article-list-by-tag'), - url(r'^tag/(?P\w[-\w]*)/feed/', + url(r'^tag/(?P\w[-\w]*)/feed/$', TagFeed(), name='article-list-by-tag-feed'), + ] From 5f0b43f7c09b85c266265c3dc49395179fbce07c Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 02:16:30 -0700 Subject: [PATCH 0648/1137] remove broken tests --- aldryn_categories/tests/__init__.py | 3 - aldryn_categories/tests/base.py | 50 -- aldryn_categories/tests/test_admin.py | 37 - aldryn_categories/tests/test_fields.py | 120 --- aldryn_categories/tests/test_migrations.py | 31 - aldryn_categories/tests/test_models.py | 184 ---- aldryn_newsblog/tests/__init__.py | 335 ------- .../tests/browser/test_aldryn_newsblog.py | 30 - aldryn_newsblog/tests/frontend/.eslintrc.js | 30 - .../tests/frontend/casperjs.conf.js | 27 - .../tests/frontend/fixtures/search.html | 24 - .../tests/frontend/integration/crud.js | 309 ------- .../integration/handlers/externalMissing.js | 12 - .../integration/handlers/loadFailures.js | 12 - .../integration/handlers/missingPages.js | 16 - .../integration/handlers/pageErrors.js | 12 - .../integration/handlers/suiteFailures.js | 22 - .../frontend/integration/related-articles.js | 93 -- .../tests/frontend/integration/setup.js | 13 - aldryn_newsblog/tests/frontend/karma.conf.js | 95 -- .../tests/frontend/unit/test.cl.newsblog.js | 137 --- .../tests/static/featured_image.jpg | Bin 22058 -> 0 bytes .../aldryn_newsblog/dummy/article_detail.html | 1 - .../aldryn_newsblog/dummy/article_list.html | 1 - .../dummy/plugins/latest_articles.html | 1 - aldryn_newsblog/tests/test_admin.py | 43 - aldryn_newsblog/tests/test_cms_wizards.py | 48 - aldryn_newsblog/tests/test_commands.py | 36 - aldryn_newsblog/tests/test_feeds.py | 71 -- aldryn_newsblog/tests/test_i18n.py | 45 - aldryn_newsblog/tests/test_managers.py | 26 - aldryn_newsblog/tests/test_models.py | 244 ------ aldryn_newsblog/tests/test_plugins.py | 413 --------- aldryn_newsblog/tests/test_search.py | 70 -- aldryn_newsblog/tests/test_sitemaps.py | 94 -- aldryn_newsblog/tests/test_utils.py | 74 -- aldryn_newsblog/tests/test_views.py | 823 ------------------ aldryn_people/tests/__init__.py | 301 ------- aldryn_people/tests/frontend/.eslintrc.js | 30 - aldryn_people/tests/frontend/casperjs.conf.js | 27 - .../tests/frontend/fixtures/people.html | 1 - .../tests/frontend/integration/crud.js | 189 ---- .../integration/handlers/externalMissing.js | 12 - .../integration/handlers/loadFailures.js | 12 - .../integration/handlers/missingPages.js | 16 - .../integration/handlers/pageErrors.js | 12 - .../integration/handlers/suiteFailures.js | 22 - .../tests/frontend/integration/setup.js | 13 - aldryn_people/tests/frontend/karma.conf.js | 95 -- .../tests/frontend/unit/test.cl.people.js | 26 - aldryn_people/tests/test_admin.py | 25 - aldryn_people/tests/test_app_hook.py | 34 - aldryn_people/tests/test_migrations.py | 31 - aldryn_people/tests/test_models.py | 249 ------ aldryn_people/tests/test_plugins.py | 197 ----- aldryn_people/tests/test_search_indexes.py | 48 - aldryn_people/tests/test_toolbar.py | 52 -- aldryn_people/tests/test_views.py | 75 -- 58 files changed, 5049 deletions(-) delete mode 100644 aldryn_categories/tests/__init__.py delete mode 100644 aldryn_categories/tests/base.py delete mode 100644 aldryn_categories/tests/test_admin.py delete mode 100644 aldryn_categories/tests/test_fields.py delete mode 100644 aldryn_categories/tests/test_migrations.py delete mode 100644 aldryn_categories/tests/test_models.py delete mode 100644 aldryn_newsblog/tests/__init__.py delete mode 100644 aldryn_newsblog/tests/browser/test_aldryn_newsblog.py delete mode 100644 aldryn_newsblog/tests/frontend/.eslintrc.js delete mode 100644 aldryn_newsblog/tests/frontend/casperjs.conf.js delete mode 100644 aldryn_newsblog/tests/frontend/fixtures/search.html delete mode 100644 aldryn_newsblog/tests/frontend/integration/crud.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/related-articles.js delete mode 100644 aldryn_newsblog/tests/frontend/integration/setup.js delete mode 100644 aldryn_newsblog/tests/frontend/karma.conf.js delete mode 100644 aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js delete mode 100644 aldryn_newsblog/tests/static/featured_image.jpg delete mode 100644 aldryn_newsblog/tests/templates/aldryn_newsblog/dummy/article_detail.html delete mode 100644 aldryn_newsblog/tests/templates/aldryn_newsblog/dummy/article_list.html delete mode 100644 aldryn_newsblog/tests/templates/aldryn_newsblog/dummy/plugins/latest_articles.html delete mode 100644 aldryn_newsblog/tests/test_admin.py delete mode 100644 aldryn_newsblog/tests/test_cms_wizards.py delete mode 100644 aldryn_newsblog/tests/test_commands.py delete mode 100644 aldryn_newsblog/tests/test_feeds.py delete mode 100644 aldryn_newsblog/tests/test_i18n.py delete mode 100644 aldryn_newsblog/tests/test_managers.py delete mode 100644 aldryn_newsblog/tests/test_models.py delete mode 100644 aldryn_newsblog/tests/test_plugins.py delete mode 100644 aldryn_newsblog/tests/test_search.py delete mode 100644 aldryn_newsblog/tests/test_sitemaps.py delete mode 100644 aldryn_newsblog/tests/test_utils.py delete mode 100644 aldryn_newsblog/tests/test_views.py delete mode 100644 aldryn_people/tests/__init__.py delete mode 100644 aldryn_people/tests/frontend/.eslintrc.js delete mode 100644 aldryn_people/tests/frontend/casperjs.conf.js delete mode 100644 aldryn_people/tests/frontend/fixtures/people.html delete mode 100644 aldryn_people/tests/frontend/integration/crud.js delete mode 100644 aldryn_people/tests/frontend/integration/handlers/externalMissing.js delete mode 100644 aldryn_people/tests/frontend/integration/handlers/loadFailures.js delete mode 100644 aldryn_people/tests/frontend/integration/handlers/missingPages.js delete mode 100644 aldryn_people/tests/frontend/integration/handlers/pageErrors.js delete mode 100644 aldryn_people/tests/frontend/integration/handlers/suiteFailures.js delete mode 100644 aldryn_people/tests/frontend/integration/setup.js delete mode 100644 aldryn_people/tests/frontend/karma.conf.js delete mode 100644 aldryn_people/tests/frontend/unit/test.cl.people.js delete mode 100644 aldryn_people/tests/test_admin.py delete mode 100644 aldryn_people/tests/test_app_hook.py delete mode 100644 aldryn_people/tests/test_migrations.py delete mode 100644 aldryn_people/tests/test_models.py delete mode 100644 aldryn_people/tests/test_plugins.py delete mode 100644 aldryn_people/tests/test_search_indexes.py delete mode 100644 aldryn_people/tests/test_toolbar.py delete mode 100644 aldryn_people/tests/test_views.py diff --git a/aldryn_categories/tests/__init__.py b/aldryn_categories/tests/__init__.py deleted file mode 100644 index d8accbf9..00000000 --- a/aldryn_categories/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from .test_models import CategoryTestCaseMixin # noqa diff --git a/aldryn_categories/tests/base.py b/aldryn_categories/tests/base.py deleted file mode 100644 index 2f2098fa..00000000 --- a/aldryn_categories/tests/base.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -import random -import string - -from django.conf import settings -from django.contrib.auth.models import AnonymousUser -from django.test import RequestFactory - -from django.contrib.auth.models import User - - -class CategoryTestCaseMixin(object): - """Mixin class for testing Categories""" - - @staticmethod - def reload(node): - """NOTE: django-treebeard requires nodes to be reloaded via the Django - ORM once its sub-tree is modified for the API to work properly. - - See:: https://tabo.pe/projects/django-treebeard/docs/2.0/caveats.html - - This is a simple helper-method to do that.""" - return node.__class__.objects.get(id=node.id) - - @classmethod - def rand_str(cls, prefix=u'', length=23, chars=string.ascii_letters): - return prefix + u''.join(random.choice(chars) for _ in range(length)) - - @classmethod - def create_user(cls): - return User.objects.create( - username=cls.rand_str(), first_name=cls.rand_str(), - last_name=cls.rand_str()) - - @staticmethod - def get_request(language=None): - """ - Returns a Request instance populated with cms specific attributes. - """ - request_factory = RequestFactory(HTTP_HOST=settings.ALLOWED_HOSTS[0]) - request = request_factory.get("/") - request.session = {} - request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE - # Needed for plugin rendering. - request.current_page = None - request.user = AnonymousUser() - return request diff --git a/aldryn_categories/tests/test_admin.py b/aldryn_categories/tests/test_admin.py deleted file mode 100644 index 2c65bb64..00000000 --- a/aldryn_categories/tests/test_admin.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.test import TransactionTestCase - -from .base import CategoryTestCaseMixin -from ..models import Category - - -class AdminTest(CategoryTestCaseMixin, TransactionTestCase): - - def test_admin_owner_default(self): - """ - Test that the ChangeForm contains Treebeard's MoveNodeForm - """ - from django.contrib import admin - admin.autodiscover() - - user = self.create_user() - user.is_superuser = True - user.save() - - root = Category.add_root(name="test root") - root.save() - root = self.reload(root) - root.add_child(name="test child 1") - root.add_child(name="test child 2") - - admin_inst = admin.site._registry[Category] - - request = self.get_request('en') - request.user = user - request.META['HTTP_HOST'] = 'example.com' - response = admin_inst.add_view(request) - option = '' - self.assertContains(response, option) diff --git a/aldryn_categories/tests/test_fields.py b/aldryn_categories/tests/test_fields.py deleted file mode 100644 index 750104f9..00000000 --- a/aldryn_categories/tests/test_fields.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.core.exceptions import ImproperlyConfigured -from django.test import TestCase - -from parler.utils.context import switch_language - -from aldryn_categories.models import Category -from aldryn_categories.fields import ( - CategoryForeignKey, - CategoryManyToManyField, - CategoryModelChoiceField, - CategoryMultipleChoiceField, - CategoryOneToOneField, -) - -from .base import CategoryTestCaseMixin - - -class TestCategoryField(CategoryTestCaseMixin, TestCase): - - def test_category_model_choice_field(self): - root = Category.add_root(name="root") - root.save() - child1 = root.add_child(name="child1") - child2 = root.add_child(name="child2") - grandchild1 = child1.add_child(name="grandchild1") - bad_grandchild = child1.add_child( - name='bad grandchild') - field = CategoryModelChoiceField(None) - - self.assertEqual( - field.label_from_instance(child2), - "  child2", - ) - self.assertEqual( - field.label_from_instance(grandchild1), - "    grandchild1", - ) - self.assertEqual( - field.label_from_instance(bad_grandchild), - '    bad grandchild<script>alert' - '("bad stuff");</script>', - ) - - # Tests that the field correctly throws an ImproperlyConfigured - # exception if the given object is not a Category (or something that - # acts like one) - with self.assertRaises(ImproperlyConfigured): - field.label_from_instance(object) - - # Check that using an untranslated language does not raise exceptions - with switch_language(child1, 'it'): - try: - field.label_from_instance(child1) - except ImproperlyConfigured: - self.fail("Translating to an unavailable language should not " - "result in an exception.") - - def test_category_multiple_choice_field(self): - root = Category.add_root(name="root") - root.save() - child1 = root.add_child(name="child1") - child2 = root.add_child(name="child2") - grandchild1 = child1.add_child(name="grandchild1") - bad_grandchild = child1.add_child( - name='bad grandchild') - root = self.reload(root) - child1 = self.reload(child1) - field = CategoryMultipleChoiceField(None) - self.assertEqual( - field.label_from_instance(child2), - "  child2", - ) - self.assertEqual( - field.label_from_instance(grandchild1), - "    grandchild1", - ) - self.assertEqual( - field.label_from_instance(bad_grandchild), - '    bad grandchild<script>alert' - '("bad stuff");</script>', - ) - - # Tests that the field correctly throws an ImproperlyConfigured - # exception if the given object is not a Category (or something that - # acts like one) - with self.assertRaises(ImproperlyConfigured): - field.label_from_instance(object) - - # Check that using an untranslated language does not raise exceptions - with switch_language(child1, 'it'): - try: - field.label_from_instance(child1) - except ImproperlyConfigured: - self.fail("Translating to an unavailable language should not " - "result in an exception.") - - def test_category_fk_field(self): - field = CategoryForeignKey(Category) - form_field = field.formfield() - self.assertTrue(isinstance(form_field, CategoryModelChoiceField)) - field_type = field.get_internal_type() - self.assertEquals(field_type, 'ForeignKey') - - def test_category_one_to_one_field(self): - field = CategoryOneToOneField(Category) - form_field = field.formfield() - self.assertTrue(isinstance(form_field, CategoryModelChoiceField)) - field_type = field.get_internal_type() - self.assertEquals(field_type, 'ForeignKey') - - def test_category_many_to_many_field(self): - field = CategoryManyToManyField(Category) - form_field = field.formfield() - self.assertTrue(isinstance(form_field, CategoryMultipleChoiceField)) - field_type = field.get_internal_type() - self.assertEquals(field_type, 'ManyToManyField') diff --git a/aldryn_categories/tests/test_migrations.py b/aldryn_categories/tests/test_migrations.py deleted file mode 100644 index 2b69e62f..00000000 --- a/aldryn_categories/tests/test_migrations.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# original from -# http://tech.octopus.energy/news/2016/01/21/testing-for-missing-migrations-in-django.html -from django.core.management import call_command -from django.test import TestCase, override_settings -from six import text_type -from six.moves import StringIO - - -class MigrationTestCase(TestCase): - - @override_settings(MIGRATION_MODULES={}) - def test_for_missing_migrations(self): - output = StringIO() - options = { - 'interactive': False, - 'dry_run': True, - 'stdout': output, - 'check_changes': True, - } - - try: - call_command('makemigrations', **options) - except SystemExit as e: - status_code = text_type(e) - else: - # the "no changes" exit code is 0 - status_code = '0' - - if status_code == '1': - self.fail('There are missing migrations:\n {}'.format(output.getvalue())) diff --git a/aldryn_categories/tests/test_models.py b/aldryn_categories/tests/test_models.py deleted file mode 100644 index 40006ea7..00000000 --- a/aldryn_categories/tests/test_models.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals -import six - -from django.test import TestCase, TransactionTestCase -from django.utils import translation - -from parler.utils.context import switch_language - -from aldryn_categories.models import Category - -from .base import CategoryTestCaseMixin - - -class TestCategories(CategoryTestCaseMixin, TransactionTestCase): - """Implementation-specific tests""" - - def test_category_slug_creation(self): - name = "Root Node" - root = Category.add_root(name=name) - root.set_current_language("en") - root.save() - self.assertEquals(root.slug, "root-node") - - def test_slug_collision(self): - root = Category.add_root(name="test") - root.save() - root = self.reload(root) - self.assertEquals(root.slug, "test") - child1 = root.add_child(name="test") - self.assertEquals(child1.slug, "test-1") - child2 = root.add_child(name="test") - self.assertEquals(child2.slug, "test-2") - - def test_str(self): - root = Category.add_root(name="test") - root.save() - self.assertEqual(root.name, str(root)) - - def test_str_malicious(self): - malicious = "" - escaped = "<script>alert('hi');</script>" - root = Category.add_root(name=malicious) - root.save() - self.assertEqual(six.u(str(root)), escaped) - - def test_delete(self): - root = Category.add_root(name="test") - root.save() - child1 = root.add_child(name="Child 1") - self.assertIn(child1, root.get_children()) - try: - root.delete() - except TypeError: - self.fail('Deleting a node throws a TypeError.') - except Exception: - self.fail('Deleting a node throws an exception.') - self.assertNotIn(child1, Category.objects.all()) - - def test_non_ascii_slug_generation(self): - """Test slug generation for common non-ASCII types of characters""" - root = Category.add_root(name="Root Node") - root.save() - child1 = root.add_child(name="Germanic umlauts: ä ö ü ß Ä Ö Ü") - self.assertEquals(child1.slug, "germanic-umlauts-a-o-u-ss-a-o-u") - child2 = root.add_child(name="Slavic Cyrillic: смачні пляцки") - self.assertEquals(child2.slug, "slavic-cyrillic-smachni-pliatski") - child3 = root.add_child(name="Simplified Chinese: 美味蛋糕") - self.assertEquals(child3.slug, "simplified-chinese-mei-wei-dan-gao") - # non-ascii only slug - child4 = root.add_child(name="ß ў 美") - self.assertEquals(child4.slug, "ss-u-mei") - - -class TestCategoryTrees(CategoryTestCaseMixin, TestCase): - """django-treebeard related tests""" - - def test_create_in_mem_category(self): - name = "Root Node" - root = Category.add_root(name=name) - root.set_current_language("en") - self.assertEquals(root.name, "Root Node") - - def test_create_in_orm_category(self): - name = "Root Node" - root = Category.add_root(name=name) - root.set_current_language("en") - root.save() - root = self.reload(root) - self.assertEquals(root.name, name) - - def test_tree_depth(self): - a = Category.add_root(name="A") - b = a.add_child(name="B") - c = b.add_child(name="C") - self.assertEqual(c.depth, 3) - - def test_get_children_count(self): - a = Category.add_root(name="A") - a.add_child(name="B") - self.assertEquals(a.get_children_count(), 1) - a.add_child(name="C") - a = self.reload(a) - self.assertEquals(a.get_children_count(), 2) - - def test_get_children(self): - a = Category.add_root(name="A") - b = a.add_child(name="B") - self.assertIn(b, a.get_children()) - c = a.add_child(name="C") - a = self.reload(a) - self.assertIn(c, a.get_children()) - - def test_get_descendants(self): - a = Category.add_root(name="A") - b = a.add_child(name="B") - c = b.add_child(name="C") - self.assertIn(c, a.get_descendants()) - d = b.add_child(name='D') - b = self.reload(b) - self.assertIn(d, b.get_descendants()) - - def test_get_ancestors(self): - a = Category.add_root(name="A") - b = a.add_child(name="B") - c = b.add_child(name="C") - self.assertIn(a, b.get_ancestors()) - self.assertIn(a, c.get_ancestors()) - d = b.add_child(name='D') - self.assertIn(a, d.get_ancestors()) - - def test_move_category(self): - a = Category.add_root(name="A") - b = a.add_child(name="B") - c = a.add_child(name="C") - a = self.reload(a) - b = self.reload(b) - self.assertEqual(a, c.get_parent()) - self.assertNotEqual(b, c.get_parent()) - c.move(b, "first-child") - b = self.reload(b) - c = self.reload(c) - self.assertEqual(b, c.get_parent()) - - -class TestCategoryParler(CategoryTestCaseMixin, TestCase): - """django-parler related tests""" - - def test_add_translations(self): - values = [ - # language code, name, slug - ('en', "Cheese Omelette", "cheese-omelette"), - ('de', "Käseomelett", "kaseomelett"), - ('fr', "Omelette au Fromage", "omelette-au-fromage"), - ] - - node = None - - # Create the translations - for lang, name, slug in values: - if node: - with switch_language(node, lang): - node.name = name - node.save() - else: - with translation.override(lang): - node = Category.add_root(name=name) - node.save() - - # Now test that they exist (and didn't obliterate one another) - for lang, name, slug in values: - with switch_language(node, lang): - self.assertEqual(node.name, name) - self.assertEqual(node.slug, slug) - - # Now test that we gracefully handle languages where there is no - # translation. - with switch_language(node, 'it'): - try: - node.name - except Exception: - self.fail("Translating to an unavailable language should not " - "result in an exception.") diff --git a/aldryn_newsblog/tests/__init__.py b/aldryn_newsblog/tests/__init__.py deleted file mode 100644 index c8871a38..00000000 --- a/aldryn_newsblog/tests/__init__.py +++ /dev/null @@ -1,335 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -import os -import random -import string -import sys - -from django.conf import settings -from django.contrib.auth.models import AnonymousUser, User -from django.core.cache import cache -from django.test import RequestFactory -from django.urls import clear_url_caches -from django.utils.timezone import now -from django.utils.translation import override - -from cms import api -from cms.apphook_pool import apphook_pool -from cms.appresolver import clear_app_resolvers -from cms.exceptions import AppAlreadyRegistered -from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase -from cms.toolbar.toolbar import CMSToolbar -from cms.utils.conf import get_cms_setting - -from aldryn_categories.models import Category -from aldryn_people.models import Person -from parler.utils.context import switch_language - -from aldryn_newsblog.cms_apps import NewsBlogApp -from aldryn_newsblog.models import Article, NewsBlogConfig - - -TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) -TESTS_STATIC_ROOT = os.path.abspath(os.path.join(TESTS_ROOT, 'static')) - - -class NewsBlogTestsMixin(object): - - NO_REDIRECT_CMS_SETTINGS = { - 1: [ - { - 'code': 'de', - 'name': 'Deutsche', - 'fallbacks': ['en', ] # FOR TESTING DO NOT ADD 'fr' HERE - }, - { - 'code': 'fr', - 'name': 'Française', - 'fallbacks': ['en', ] # FOR TESTING DO NOT ADD 'de' HERE - }, - { - 'code': 'en', - 'name': 'English', - 'fallbacks': ['de', 'fr', ] - }, - { - 'code': 'it', - 'name': 'Italiano', - 'fallbacks': ['fr', ] # FOR TESTING, LEAVE AS ONLY 'fr' - }, - ], - 'default': { - 'redirect_on_fallback': False, - } - } - - @staticmethod - def reload(node): - """NOTE: django-treebeard requires nodes to be reloaded via the Django - ORM once its sub-tree is modified for the API to work properly. - - See:: https://tabo.pe/projects/django-treebeard/docs/2.0/caveats.html - - This is a simple helper-method to do that.""" - return node.__class__.objects.get(id=node.id) - - @classmethod - def rand_str(cls, prefix=u'', length=23, chars=string.ascii_letters): - return prefix + u''.join(random.choice(chars) for _ in range(length)) - - @classmethod - def create_user(cls, **kwargs): - kwargs.setdefault('username', cls.rand_str()) - kwargs.setdefault('first_name', cls.rand_str()) - kwargs.setdefault('last_name', cls.rand_str()) - return User.objects.create(**kwargs) - - def create_person(self): - return Person.objects.create( - user=self.create_user(), slug=self.rand_str()) - - def create_article(self, content=None, **kwargs): - try: - author = kwargs['author'] - except KeyError: - author = self.create_person() - try: - owner = kwargs['owner'] - except KeyError: - owner = author.user - - fields = { - 'title': self.rand_str(), - 'slug': self.rand_str(), - 'author': author, - 'owner': owner, - 'app_config': self.app_config, - 'publishing_date': now(), - 'is_published': True, - } - - fields.update(kwargs) - - article = Article.objects.create(**fields) - # save again to calculate article search_data. - article.save() - - if content: - api.add_plugin(article.content, 'TextPlugin', - self.language, body=content) - return article - - def create_tagged_articles(self, num_articles=3, tags=('tag1', 'tag2'), - **kwargs): - """Create num_articles Articles for each tag""" - articles = {} - for tag_name in tags: - tagged_articles = [] - for _ in range(num_articles): - article = self.create_article(**kwargs) - article.save() - article.tags.add(tag_name) - tagged_articles.append(article) - tag_slug = tagged_articles[0].tags.slugs()[0] - articles[tag_slug] = tagged_articles - return articles - - def setup_categories(self): - """ - Sets-up i18n categories (self.category_root, self.category1 and - self.category2) for use in tests - """ - self.language = settings.LANGUAGES[0][0] - - categories = [] - # Set the default language, create the objects - with override(self.language): - code = "{0}-".format(self.language) - self.category_root = Category.add_root( - name=self.rand_str(prefix=code, length=8)) - categories.append(self.category_root) - self.category1 = self.category_root.add_child( - name=self.rand_str(prefix=code, length=8)) - categories.append(self.category1) - self.category2 = self.category_root.add_child( - name=self.rand_str(prefix=code, length=8)) - categories.append(self.category2) - - # We should reload category_root, since we modified its children. - self.category_root = self.reload(self.category_root) - - # Setup the other language(s) translations for the categories - for language, _ in settings.LANGUAGES[1:]: - for category in categories: - with switch_language(category, language): - code = "{0}-".format(language) - category.name = self.rand_str(prefix=code, length=8) - category.save() - - @staticmethod - def get_request(language=None, url="/"): - """ - Returns a Request instance populated with cms specific attributes. - """ - request_factory = RequestFactory(HTTP_HOST=settings.ALLOWED_HOSTS[0]) - request = request_factory.get(url) - request.session = {} - request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE - # Needed for plugin rendering. - request.current_page = None - request.user = AnonymousUser() - request.toolbar = CMSToolbar(request) - return request - - def setUp(self): - self.template = get_cms_setting('TEMPLATES')[0][0] - self.language = settings.LANGUAGES[0][0] - self.root_page = api.create_page( - 'root page', - self.template, - self.language, - published=True, - ) - - try: - # Django-cms 3.5 doesn't set is_home when create_page is called - self.root_page.set_as_homepage() - except AttributeError: - pass - - self.app_config = NewsBlogConfig.objects.language(self.language).create( - app_title='news_blog', - namespace='NBNS', - paginate_by=15, - ) - self.page = api.create_page( - 'page', self.template, self.language, published=True, - parent=self.root_page, - apphook='NewsBlogApp', - apphook_namespace=self.app_config.namespace) - self.plugin_page = api.create_page( - title="plugin_page", template=self.template, language=self.language, - parent=self.root_page, published=True) - self.placeholder = self.page.placeholders.all()[0] - - self.setup_categories() - - for page in self.root_page, self.page: - for language, _ in settings.LANGUAGES[1:]: - api.create_title(language, page.get_slug(), page) - page.publish(language) - - -class CleanUpMixin(object): - apphook_object = None - - def setUp(self): - super(CleanUpMixin, self).setUp() - apphook_object = self.get_apphook_object() - self.reload_urls(apphook_object) - - def tearDown(self): - """ - Do a proper cleanup, delete everything what is preventing us from - clean environment for tests. - :return: None - """ - self.app_config.delete() - self.reset_all() - cache.clear() - super(CleanUpMixin, self).tearDown() - - def get_apphook_object(self): - return self.apphook_object - - def reset_apphook_cmsapp(self, apphook_object=None): - """ - For tests that should not be polluted by previous setup we need to - ensure that app hooks are reloaded properly. One of the steps is to - reset the relation between EventListAppHook and EventsConfig - """ - if apphook_object is None: - apphook_object = self.get_apphook_object() - app_config = getattr(apphook_object, 'app_config', None) - if app_config and getattr(app_config, 'cmsapp', None): - delattr(apphook_object.app_config, 'cmsapp') - if getattr(app_config, 'cmsapp', None): - delattr(app_config, 'cmsapp') - - def reset_all(self): - """ - Reset all that could leak from previous test to current/next test. - :return: None - """ - apphook_object = self.get_apphook_object() - self.delete_app_module(apphook_object.__module__) - self.reload_urls(apphook_object) - self.apphook_clear() - - def delete_app_module(self, app_module=None): - """ - Remove APP_MODULE from sys.modules. Taken from cms. - :return: None - """ - if app_module is None: - apphook_object = self.get_apphook_object() - app_module = apphook_object.__module__ - if app_module in sys.modules: - del sys.modules[app_module] - - def apphook_clear(self): - """ - Clean up apphook_pool and sys.modules. Taken from cms with slight - adjustments and fixes. - :return: None - """ - try: - apphooks = apphook_pool.get_apphooks() - except AppAlreadyRegistered: - # there is an issue with discover apps, or i'm using it wrong. - # setting discovered to True solves it. Maybe that is due to import - # from aldryn_events.cms_apps which registers EventListAppHook - apphook_pool.discovered = True - apphooks = apphook_pool.get_apphooks() - - for name, label in list(apphooks): - if apphook_pool.apps[name].__class__.__module__ in sys.modules: - del sys.modules[apphook_pool.apps[name].__class__.__module__] - apphook_pool.clear() - self.reset_apphook_cmsapp() - - def reload_urls(self, apphook_object=None): - """ - Clean up url related things (caches, app resolvers, modules). - Taken from cms. - :return: None - """ - if apphook_object is None: - apphook_object = self.get_apphook_object() - app_module = apphook_object.__module__ - package = app_module.split('.')[0] - clear_app_resolvers() - clear_url_caches() - url_modules = [ - 'cms.urls', - '{0}.urls'.format(package), - settings.ROOT_URLCONF - ] - - for module in url_modules: - if module in sys.modules: - del sys.modules[module] - - -class NewsBlogTestCase(CleanUpMixin, NewsBlogTestsMixin, CMSTestCase): - apphook_object = NewsBlogApp - pass - - -class NewsBlogTransactionTestCase(CleanUpMixin, - NewsBlogTestsMixin, - TransactionCMSTestCase): - apphook_object = NewsBlogApp - pass diff --git a/aldryn_newsblog/tests/browser/test_aldryn_newsblog.py b/aldryn_newsblog/tests/browser/test_aldryn_newsblog.py deleted file mode 100644 index 2dce4f69..00000000 --- a/aldryn_newsblog/tests/browser/test_aldryn_newsblog.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.test import LiveServerTestCase - -from selenium import webdriver - - -class NewVisitorTest(LiveServerTestCase): - - def setUp(self): - self.browser = webdriver.Firefox() - self.browser.implicitly_wait(2) - - def tearDown(self): - self.browser.quit() - - def test_sees_an_empty_page_message(self): - # visit the home page of the facts appplication - self.browser.get(self.live_server_url) - - # the default title of the home page is "News" - self.assertIn("News", self.browser.title) - - # the default h1 is also "News" - h1_text = self.browser.find_element_by_tag_name("h1").text - self.assertIn("News", h1_text) - - def test_admin_user_can_login(self): - # go to the admin - self.browser.get("http://localhost:8000/admin") - - self.fail("finish the test") diff --git a/aldryn_newsblog/tests/frontend/.eslintrc.js b/aldryn_newsblog/tests/frontend/.eslintrc.js deleted file mode 100644 index ab82b252..00000000 --- a/aldryn_newsblog/tests/frontend/.eslintrc.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - "env": { - "node": true - }, - "globals": { - "$": true, - "module": true, - "process": true, - "it": true, - "CMS": true, - "expect": true, - "jasmine": true, - "describe": true, - "casper": true, - "beforeEach": true, - "afterEach": true, - "beforeAll": true, - "afterAll": true, - "spyOn": true, - "spyOnEvent": true, - "fixture": true, - "pending": true - }, - "rules": { - "no-magic-numbers": 0, - "max-nested-callbacks": [2, 8], - "newline-after-var": 0, - "strict": [2, "global"] - } -}; diff --git a/aldryn_newsblog/tests/frontend/casperjs.conf.js b/aldryn_newsblog/tests/frontend/casperjs.conf.js deleted file mode 100644 index 2e7bdd9b..00000000 --- a/aldryn_newsblog/tests/frontend/casperjs.conf.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -// ############################################################################# -// CasperJS options - -module.exports = { - init: function () { - this.viewportSize(); - this.timeout(20000); - }, - - viewportSize: function (width, height) { - var viewportWidth = width || 1280; - var viewportHeight = height || 1024; - - casper.echo('Current viewport size is ' + viewportWidth + 'x' + viewportHeight + '.', 'INFO'); - - casper.options.viewportSize = { - width: viewportWidth, - height: viewportHeight - }; - }, - - timeout: function (timeout) { - casper.options.waitTimeout = timeout || 10000; - } -}; diff --git a/aldryn_newsblog/tests/frontend/fixtures/search.html b/aldryn_newsblog/tests/frontend/fixtures/search.html deleted file mode 100644 index 9331d315..00000000 --- a/aldryn_newsblog/tests/frontend/fixtures/search.html +++ /dev/null @@ -1,24 +0,0 @@ -
    -
    - -
    -
    - - -
    - - - - - -
    -
    -
    -
      -

      Most recent articles containing "test"

      -
    -
    -
    diff --git a/aldryn_newsblog/tests/frontend/integration/crud.js b/aldryn_newsblog/tests/frontend/integration/crud.js deleted file mode 100644 index 52f839c8..00000000 --- a/aldryn_newsblog/tests/frontend/integration/crud.js +++ /dev/null @@ -1,309 +0,0 @@ -'use strict'; - -var helpers = require('djangocms-casper-helpers'); -var globals = helpers.settings; -var casperjs = require('casper'); -var cms = helpers(casperjs); -var xPath = casperjs.selectXPath; - -casper.test.setUp(function (done) { - casper.start() - .then(cms.login()) - .run(done); -}); - -casper.test.tearDown(function (done) { - casper.start() - .then(cms.logout()) - .run(done); -}); - -casper.test.begin('Creation / deletion of the apphook', function (test) { - casper - .start(globals.adminUrl) - .waitUntilVisible('#content', function () { - test.assertVisible('#content', 'Admin loaded'); - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn News & Blog', - row: 'Sections', - link: 'Add' - })) - ); - }) - .waitForUrl(/add/) - .waitUntilVisible('#newsblogconfig_form') - .then(function () { - test.assertVisible('#newsblogconfig_form', 'Apphook creation form loaded'); - - this.fill('#newsblogconfig_form', { - namespace: 'Test namespace', - app_title: 'Test Blog' - }, true); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The Section "Test Blog" was added successfully.', - 'Apphook config was created' - ); - - test.assertElementCount( - '#result_list tbody tr', - 2, - 'There are 2 apphooks now' - ); - - this.clickLabel('Test Blog', 'a'); - }) - .waitUntilVisible('.deletelink', function () { - this.click('.deletelink'); - }) - .waitForUrl(/delete/, function () { - this.click('input[value="Yes, I\'m sure"]'); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The Section "Test Blog" was deleted successfully.', - 'Apphook config was deleted' - ); - }) - .run(function () { - test.done(); - }); -}); - -cms._modifyPageAdvancedSettings = function _modifyPageAdvancedSettings(opts) { - var that = this; - - return function () { - return this.wait(1000).thenOpen(globals.adminPagesUrl) - .waitUntilVisible('.cms-pagetree-jstree') - .then(that.waitUntilAllAjaxCallsFinish()) - .then(that.expandPageTree()) - .then(function () { - var pageId = that.getPageId(opts.page); - - this.thenOpen(globals.adminPagesUrl + pageId + '/advanced-settings/'); - }) - .waitForSelector('#page_form', function () { - this.fill('#page_form', opts.fields); - }) - .wait(100, function () { - this.click('input.default'); - }) - .waitForUrl(/page/) - .waitUntilVisible('.success') - .then(that.waitUntilAllAjaxCallsFinish()) - .wait(1000); - }; -}; - -casper.test.begin('Creation / deletion of the article', function (test) { - casper - .start() - .then(cms.addPage({ title: 'Blog' })) - .then(cms._modifyPageAdvancedSettings({ - page: 'Blog', - fields: { - application_configs: 1, - application_urls: 'NewsBlogApp' - } - })) - .then(cms.publishPage({ - page: 'Blog' - })) - .thenOpen(globals.editUrl, function () { - test.assertSelectorHasText('p', 'No items available', 'No articles yet'); - }) - .thenOpen(globals.adminUrl) - .waitUntilVisible('#content', function () { - test.assertVisible('#content', 'Admin loaded'); - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn News & Blog', - row: 'Articles', - link: 'Add' - })) - ); - }) - .waitForUrl(/add/) - .waitUntilVisible('#article_form') - .then(function () { - test.assertVisible('#article_form', 'Article creation form loaded'); - - this.fill('#article_form', { - title: 'Test article' - }, true); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The article "Test article" was added successfully.', - 'Article was created' - ); - - test.assertElementCount( - '#result_list tbody tr', - 1, - 'There is 1 article available' - ); - }) - .thenOpen(globals.editUrl, function () { - test.assertSelectorHasText( - '.article.unpublished h2 a', - 'Test article', - 'Article is available on the page' - ); - }) - .thenOpen(globals.adminUrl) - .waitUntilVisible('#content', function () { - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn News & Blog', - row: 'Articles' - })) - ); - }) - .waitForUrl(/article/, function () { - this.clickLabel('Test article', 'a'); - }) - .waitUntilVisible('.deletelink', function () { - this.click('.deletelink'); - }) - .waitForUrl(/delete/, function () { - this.click('input[value="Yes, I\'m sure"]'); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The article "Test article" was deleted successfully.', - 'Article was deleted' - ); - }) - .then(cms.removePage()) - .run(function () { - test.done(); - }); -}); - -casper.test.begin('Latest articles plugin', function (test) { - casper - .start() - .then(cms.addPage({ title: 'Home' })) - .then(cms.addPlugin({ - type: 'NewsBlogLatestArticlesPlugin', - content: { - id_latest_articles: 1 - } - })) - .thenOpen(globals.editUrl, function () { - test.assertSelectorHasText( - 'p.cms-plugin', - 'No items available', - 'No articles yet' - ); - }) - .then(cms.openSideframe()) - // add articles - .withFrame(0, function () { - this.waitForSelector('.cms-pagetree-breadcrumbs') - .then(function () { - this.click('.cms-pagetree-breadcrumbs a:first-child'); - }) - .waitForUrl(/admin/) - .waitForSelector('.dashboard', function () { - this.click(xPath(cms.getXPathForAdminSection({ - section: 'Aldryn News & Blog', - row: 'Articles', - link: 'Add' - }))); - }) - .waitForSelector('#article_form', function () { - this.fill('#article_form', { - title: 'First article' - }, false); - - }) - // wait 3 seconds so the second article is definitely - // created after the first one :) - .wait(3000, function () { - this.click('input[value="Save and add another"]'); - }) - .waitForSelector('.success', function () { - test.assertSelectorHasText( - '.success', - 'The article "First article" was added successfully. You may add another article below.', - 'First article added' - ); - - this.fill('#article_form', { - title: 'Second article' - }, true); - }) - .waitForSelector('.success'); - }) - .thenOpen(globals.editUrl, function () { - test.assertSelectorHasText( - 'p.cms-plugin', - 'No items available', - 'Still no articles yet (no apphooked page yet)' - ); - }) - .then(cms.addPage({ title: 'Blog' })) - .then(cms.addApphookToPage({ - page: 'Blog', - apphook: 'NewsBlogApp' - })) - .then(cms.publishPage({ page: 'Blog' })) - .thenOpen(globals.editUrl, function () { - test.assertSelectorHasText( - '.article h2 a cms-plugin', - 'Second article', - 'Latest article is visible on the page' - ); - test.assertElementCount( - '.article cms-plugin', - 1, - 'Only one latest article is visible on the page' - ); - }) - // remove articles - .then(cms.openSideframe()) - .withFrame(0, function () { - this.waitForSelector('.cms-pagetree-breadcrumbs') - .then(function () { - this.click('.cms-pagetree-breadcrumbs a:first-child'); - }) - .waitForUrl(/admin/) - .waitForSelector('.dashboard', function () { - this.click(xPath(cms.getXPathForAdminSection({ - section: 'Aldryn News & Blog', - row: 'Articles' - }))); - }) - .waitForSelector('#changelist-form', function () { - this.click('th input[type="checkbox"]'); - this.fill('#changelist-form', { - action: 'delete_selected' - }, true); - - }) - .waitForSelector('.delete-confirmation', function () { - this.click('input[value="Yes, I\'m sure"]'); - }) - .waitForSelector('.success', function () { - test.assertSelectorHasText( - '.success', - 'Successfully deleted 2 articles.', - 'Articles deleted' - ); - }); - }) - .then(cms.removePage()) - .then(cms.removePage()) - .run(function () { - test.done(); - }); -}); diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js b/aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js deleted file mode 100644 index 4962b9e6..00000000 --- a/aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles external resources load failures - -module.exports = { - bind: function () { - casper.on('resource.error', function (resource) { - casper.echo('Resource failed to load: ' + resource.url, 'ERROR'); - }); - } -}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js b/aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js deleted file mode 100644 index 9a66119c..00000000 --- a/aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles load failure errors - -module.exports = { - bind: function () { - casper.on('load.failed', function (error) { - casper.echo(JSON.stringify(error), 'ERROR'); - }); - } -}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js b/aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js deleted file mode 100644 index ff6d5351..00000000 --- a/aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles 404 and 500 pages - -module.exports = { - bind: function () { - casper.on('http.status.404', function (resource) { - casper.echo('404 page found: ' + resource.url, 'ERROR'); - }); - - casper.on('http.status.500', function (resource) { - casper.echo('500 page found: ' + resource.url, 'ERROR'); - }); - } -}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js b/aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js deleted file mode 100644 index 77ab8860..00000000 --- a/aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles JavaScript page errors - -module.exports = { - bind: function () { - casper.on('page.error', function (msg) { - casper.echo('Error on page: ' + JSON.stringify(msg), 'ERROR'); - }); - } -}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js b/aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js deleted file mode 100644 index 98f019e9..00000000 --- a/aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles test suite errors (assert and waitFor) - -module.exports = { - bind: function () { - casper.on('step.error', function (error) { - casper.die('assert failed: ' + error.message); - }); - - casper.on('waitFor.timeout', function (timeout, error) { - if (error.selector) { - casper.die('waitFor failed, couldn\'t find ' + error.selector + ' within ' + timeout + 'ms'); - } else if (error.visible) { - casper.die('waitFor failed, couldn\'t find ' + error.visible + ' within ' + timeout + 'ms'); - } else { - casper.die('waitFor failed with error', JSON.stringify(error, null, 4)); - } - }); - } -}; diff --git a/aldryn_newsblog/tests/frontend/integration/related-articles.js b/aldryn_newsblog/tests/frontend/integration/related-articles.js deleted file mode 100644 index 889f8bdb..00000000 --- a/aldryn_newsblog/tests/frontend/integration/related-articles.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -var helpers = require('djangocms-casper-helpers'); -var globals = helpers.settings; -var casperjs = require('casper'); -var cms = helpers(casperjs); -var xPath = casperjs.selectXPath; - -casper.test.setUp(function (done) { - casper.start() - .then(cms.login()) - .run(done); -}); - -casper.test.tearDown(function (done) { - casper.start() - .then(cms.logout()) - .run(done); -}); - -casper.test.begin('Related articles', function (test) { - casper - .then(cms.addPage({ title: 'Home' })) - .thenOpen(globals.editUrl) - .then(cms.openSideframe()) - // add articles - .withFrame(0, function () { - this.waitForSelector('.cms-pagetree-breadcrumbs') - .then(function () { - this.click('.cms-pagetree-breadcrumbs a:first-child'); - }) - .waitForUrl(/admin/) - .waitForSelector('.dashboard', function () { - this.click(xPath(cms.getXPathForAdminSection({ - section: 'Aldryn News & Blog', - row: 'Articles', - link: 'Add' - }))); - }) - .waitForSelector('#article_form', function () { - test.assertDoesntExist('.field-related .add-related', 'Related articles "Add" are not shown'); - - this.fill('#article_form', { - title: 'First article' - }, false); - - }) - // wait 3 seconds so the second article is definitely - // created after the first one :) - .wait(3000, function () { - this.click('input[value="Save and add another"]'); - }) - .waitForSelector('.success', function () { - test.assertSelectorHasText( - '.success', - 'The article "First article" was added successfully. You may add another article below.', - 'First article added' - ); - - test.assertDoesntExist('.field-related .add-related', 'Related articles "Add" are not shown'); - test.assertSelectorHasText( - '.sortedm2m', - 'First article', - 'Correct related articles possibilities are shown' - ); - - this.fill('#article_form', { - title: 'Second article' - }, true); - }) - .waitForSelector('#changelist-form', function () { - this.click('th input[type="checkbox"]'); - this.fill('#changelist-form', { - action: 'delete_selected' - }, true); - - }) - .waitForSelector('.delete-confirmation', function () { - this.click('input[value="Yes, I\'m sure"]'); - }) - .waitForSelector('.success', function () { - test.assertSelectorHasText( - '.success', - 'Successfully deleted 2 articles.', - 'Articles deleted' - ); - }); - }) - .then(cms.removePage()) - .run(function () { - test.done(); - }); -}); diff --git a/aldryn_newsblog/tests/frontend/integration/setup.js b/aldryn_newsblog/tests/frontend/integration/setup.js deleted file mode 100644 index 93f356b1..00000000 --- a/aldryn_newsblog/tests/frontend/integration/setup.js +++ /dev/null @@ -1,13 +0,0 @@ -// ############################################################################# -// Init all settings and event handlers on suite start -'use strict'; - -require('./../casperjs.conf').init(); - -require('./handlers/pageErrors').bind(); -require('./handlers/loadFailures').bind(); -require('./handlers/missingPages').bind(); -require('./handlers/externalMissing').bind(); -require('./handlers/suiteFailures').bind(); - -casper.test.done(); diff --git a/aldryn_newsblog/tests/frontend/karma.conf.js b/aldryn_newsblog/tests/frontend/karma.conf.js deleted file mode 100644 index b2e23f71..00000000 --- a/aldryn_newsblog/tests/frontend/karma.conf.js +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * @author: Divio AG - * @copyright: http://www.divio.ch - */ - -'use strict'; - -// ############################################################################# -// CONFIGURATION -module.exports = function (config) { - var browsers = { - PhantomJS: 'used for local testing' - }; - - var settings = { - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '..', - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine', 'fixture'], - - // list of files / patterns to load in the browser - // tests/${path} - files: [ - // these have to be specified in order since - // dependency loading is not handled yet - '../../aldryn_newsblog/boilerplates/bootstrap3/static/js/libs/*.js', - '../../aldryn_newsblog/boilerplates/bootstrap3/static/js/addons/*.js', - - // tests themselves - 'frontend/unit/*.js', - - // fixture patterns - { - pattern: 'frontend/fixtures/**/*' - } - ], - - // list of files to exclude - exclude: [], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - '../../aldryn_newsblog/boilerplates/bootstrap3/static/js/addons/*.js': ['coverage'], - // for fixtures - '**/*.html': ['html2js'], - '**/*.json': ['json_fixtures'] - }, - - // optionally, configure the reporter - coverageReporter: { - reporters: [ - { type: 'html', dir: 'frontend/coverage/' }, - { type: 'lcov', dir: 'frontend/coverage/' } - ] - }, - - // fixtures dependency - // https://github.com/billtrik/karma-fixture - jsonFixturesPreprocessor: { - variableName: '__json__' - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'coverage'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: - // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: Object.keys(browsers), - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false - }; - - config.set(settings); -}; diff --git a/aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js b/aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js deleted file mode 100644 index 3566b049..00000000 --- a/aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js +++ /dev/null @@ -1,137 +0,0 @@ -/*! - * @author: Divio AG - * @copyright: http://www.divio.ch - */ - -'use strict'; -/* global Cl, $, describe, window, it, expect, beforeEach, afterEach, fixture, spyOn */ - -// ############################################################################# -// UNIT TEST -describe('cl.newsblog.js:', function () { - beforeEach(function () { - fixture.setBase('frontend/fixtures'); - this.markup = fixture.load('search.html'); - this.preventEvent = { preventDefault: function () {} }; - }); - - afterEach(function () { - fixture.cleanup(); - }); - - it('has available Cl namespace', function () { - expect(Cl).toBeDefined(); - }); - - it('has a public method _search', function () { - expect(Cl.newsBlog._search).toBeDefined(); - }); - - describe('Cl.newsBlog.init(): ', function () { - it('returns undefined', function () { - expect(Cl.newsBlog.init()).toEqual(undefined); - }); - - it('runs _search()', function () { - spyOn(Cl.newsBlog, '_search'); - Cl.newsBlog.init(); - - // validate that _search was called inside Cl.newsBlog.init() - expect(Cl.newsBlog._search).toHaveBeenCalled(); - // validate 2 call as 2 js-aldryn-newsblog-article-search is - // specified in search.html - expect(Cl.newsBlog._search.calls.count()).toEqual(2); - }); - }); - - describe('Cl.newsBlog._search: ', function () { - it('returns undefined', function () { - // validate the return of undefined - expect(Cl.newsBlog._search( - $('.js-aldryn-newsblog-article-search').eq(1))) - .toEqual(undefined); - }); - - it('has correct url parameter in ajax request', function () { - spyOn($, 'ajax').and.returnValue({ - always: function () { - return { fail: function () {} }; - } - }); - - Cl.newsBlog._handler.call( - $('.js-aldryn-newsblog-article-search .form-inline')[0], - this.preventEvent); - - var callArgs = $.ajax.calls.allArgs()[0][0]; - - // validate ajax request url - expect(callArgs.url).toEqual( - '/en/blog/search/' - ); - }); - - it('has correct data parameter in ajax request', function () { - spyOn($, 'ajax').and.returnValue({ - always: function () { - return { fail: function () {} }; - } - }); - Cl.newsBlog._handler.call( - $('.js-aldryn-newsblog-article-search .form-inline')[0], - this.preventEvent); - - var callArgs = $.ajax.calls.allArgs()[0][0]; - - // validate ajax request data - expect(callArgs.data).toEqual( - 'csrfmiddlewaretoken=Ui0tGBmn0Thq5LUeUS2m4zAF20H0M8up&' + - 'max_articles=10&q=' - ); - }); - - it('has ajax request with "always" function adding results data ' + - 'correctly', function () { - // emulate always after ajax call - spyOn($, 'ajax').and.returnValue({ - always: function (callback) { - callback('

    Test results

    '); - - return { fail: function () {} }; - } - }); - - Cl.newsBlog._handler.call( - $('.js-aldryn-newsblog-article-search .form-inline')[0], - this.preventEvent); - - // validate search results got updated with new info - expect($('.js-search-results')[0].innerHTML).toEqual( - '

    Test results

    '); - }); - - it('has ajax request with "fail" function alert working ' + - 'correctly', function () { - // emulate fail after always after ajax call - spyOn($, 'ajax').and.returnValue({ - always: function () { - return { - fail: function (callback) { - callback(); - } - }; - } - }); - - spyOn(window, 'alert'); - - Cl.newsBlog._handler.call( - $('.js-aldryn-newsblog-article-search .form-inline')[0], - this.preventEvent); - - // validate alert text - expect(window.alert).toHaveBeenCalledWith('REQUEST TIMEOUT'); - }); - }); - -}); diff --git a/aldryn_newsblog/tests/static/featured_image.jpg b/aldryn_newsblog/tests/static/featured_image.jpg deleted file mode 100644 index 60b16899f2cb7e0bb11f61bdf8cd178646de6ab7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22058 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

    E9<($=W@kd2`Q;ZY5Z>-f)!3ABWtI27{b(EOj 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.7) - # we need to delete one of configs to be sure that it is pre selected - # in the admin view. - if NewsBlogConfig.objects.count() > 1: - # delete the app config that was created during test set up. - NewsBlogConfig.objects.filter(namespace='NBNS').delete() - user = self.create_user() - user.is_superuser = True - user.save() - - Person.objects.create(user=user, name=u' '.join( - (user.first_name, user.last_name))) - - admin_inst = admin.site._registry[Article] - self.request = self.get_request('en') - self.request.user = user - self.request.META['HTTP_HOST'] = 'example.com' - response = admin_inst.add_view(self.request) - option = r'

    diff --git a/aldryn_people/tests/frontend/integration/crud.js b/aldryn_people/tests/frontend/integration/crud.js deleted file mode 100644 index 2eae53c5..00000000 --- a/aldryn_people/tests/frontend/integration/crud.js +++ /dev/null @@ -1,189 +0,0 @@ -'use strict'; - -var helpers = require('djangocms-casper-helpers'); -var globals = helpers.settings; -var casperjs = require('casper'); -var cms = helpers(casperjs); -var xPath = casperjs.selectXPath; - -casper.test.setUp(function (done) { - casper.start() - .then(cms.login()) - .run(done); -}); - -casper.test.tearDown(function (done) { - casper.start() - .then(cms.logout()) - .run(done); -}); - -casper.test.begin('Creation of a new group', function (test) { - casper - .start(globals.adminUrl) - .waitUntilVisible('#content', function () { - test.assertVisible('#content', 'Admin loaded'); - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn_People', - row: 'Groups', - link: 'Add' - })) - ); - }) - .waitForUrl(/add/) - .waitUntilVisible('#group_form') - .then(function () { - test.assertVisible('#group_form', 'Group creation form has been loaded'); - - this.fill('#group_form', { - name: 'Test group' - }, true); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The Group "Test group" was added successfully.', - 'New group has been created' - ); - }) - .run(function () { - test.done(); - }); -}); - -casper.test.begin('Creation of a new people entry', function (test) { - casper - .start(globals.adminUrl) - .waitUntilVisible('#content', function () { - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn_People', - row: 'People', - link: 'Add' - })) - ); - }) - .waitForUrl(/add/) - .waitUntilVisible('#person_form') - .then(function () { - test.assertVisible('#person_form', 'People creation form has been loaded'); - - this.fill('#person_form', { - name: 'Test person' - }, true); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The Person "Test person" was added successfully.', - 'New person has been created' - ); - }) - .run(function () { - test.done(); - }); -}); - -casper.test.begin('Adding a new people block to the page', function (test) { - casper - .start() - .then(cms.addPage({ title: 'People' })) - .then(cms.addPlugin({ - type: 'PeoplePlugin' - })) - .thenOpen(globals.editUrl, function () { - test.assertTitleMatch(/People/, 'The People page has been created'); - }) - .then(cms.publishPage({ page: 'People' })) - .thenOpen(globals.editUrl, function () { - test.assertSelectorHasText( - '.cms-plugin h2', - 'Test person', - 'The newly created "Test person" is displayed on the "People" page' - ); - }) - .run(function () { - test.done(); - }); -}); - -casper.test.begin('Deletion of a group', function (test) { - casper - .start(globals.adminUrl) - .waitUntilVisible('#content', function () { - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn_People', - row: 'Groups', - link: 'Change' - })) - ); - }) - .waitForUrl(/group/) - .waitUntilVisible('#result_list', function () { - test.assertElementCount( - '#result_list tbody tr', - 1, - 'The group is available' - ); - - this.clickLabel('Test group', 'a'); - }) - .waitUntilVisible('.deletelink', function () { - this.click('.deletelink'); - }) - .waitForUrl(/delete/, function () { - this.click('input[value="Yes, I\'m sure"]'); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The Group "Test group" was deleted successfully.', - 'The group has been deleted' - ); - }) - .run(function () { - test.done(); - }); -}); - -casper.test.begin('Deletion of a people entry', function (test) { - casper - .start(globals.adminUrl) - .waitUntilVisible('#content', function () { - this.click( - xPath(cms.getXPathForAdminSection({ - section: 'Aldryn_People', - row: 'People', - link: 'Change' - })) - ); - }) - .waitForUrl(/person/) - .waitUntilVisible('#result_list', function () { - test.assertElementCount( - '#result_list tbody tr', - 1, - 'The people entry is available' - ); - - this.clickLabel('Test person', 'a'); - }) - .waitUntilVisible('.deletelink', function () { - this.click('.deletelink'); - }) - .waitForUrl(/delete/, function () { - this.click('input[value="Yes, I\'m sure"]'); - }) - .waitUntilVisible('.success', function () { - test.assertSelectorHasText( - '.success', - 'The Person "Test person" was deleted successfully.', - 'The person has been deleted' - ); - }) - .run(function () { - test.done(); - }); -}); diff --git a/aldryn_people/tests/frontend/integration/handlers/externalMissing.js b/aldryn_people/tests/frontend/integration/handlers/externalMissing.js deleted file mode 100644 index 4962b9e6..00000000 --- a/aldryn_people/tests/frontend/integration/handlers/externalMissing.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles external resources load failures - -module.exports = { - bind: function () { - casper.on('resource.error', function (resource) { - casper.echo('Resource failed to load: ' + resource.url, 'ERROR'); - }); - } -}; diff --git a/aldryn_people/tests/frontend/integration/handlers/loadFailures.js b/aldryn_people/tests/frontend/integration/handlers/loadFailures.js deleted file mode 100644 index 9a66119c..00000000 --- a/aldryn_people/tests/frontend/integration/handlers/loadFailures.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles load failure errors - -module.exports = { - bind: function () { - casper.on('load.failed', function (error) { - casper.echo(JSON.stringify(error), 'ERROR'); - }); - } -}; diff --git a/aldryn_people/tests/frontend/integration/handlers/missingPages.js b/aldryn_people/tests/frontend/integration/handlers/missingPages.js deleted file mode 100644 index ff6d5351..00000000 --- a/aldryn_people/tests/frontend/integration/handlers/missingPages.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles 404 and 500 pages - -module.exports = { - bind: function () { - casper.on('http.status.404', function (resource) { - casper.echo('404 page found: ' + resource.url, 'ERROR'); - }); - - casper.on('http.status.500', function (resource) { - casper.echo('500 page found: ' + resource.url, 'ERROR'); - }); - } -}; diff --git a/aldryn_people/tests/frontend/integration/handlers/pageErrors.js b/aldryn_people/tests/frontend/integration/handlers/pageErrors.js deleted file mode 100644 index 77ab8860..00000000 --- a/aldryn_people/tests/frontend/integration/handlers/pageErrors.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles JavaScript page errors - -module.exports = { - bind: function () { - casper.on('page.error', function (msg) { - casper.echo('Error on page: ' + JSON.stringify(msg), 'ERROR'); - }); - } -}; diff --git a/aldryn_people/tests/frontend/integration/handlers/suiteFailures.js b/aldryn_people/tests/frontend/integration/handlers/suiteFailures.js deleted file mode 100644 index 98f019e9..00000000 --- a/aldryn_people/tests/frontend/integration/handlers/suiteFailures.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -// ############################################################################# -// Handles test suite errors (assert and waitFor) - -module.exports = { - bind: function () { - casper.on('step.error', function (error) { - casper.die('assert failed: ' + error.message); - }); - - casper.on('waitFor.timeout', function (timeout, error) { - if (error.selector) { - casper.die('waitFor failed, couldn\'t find ' + error.selector + ' within ' + timeout + 'ms'); - } else if (error.visible) { - casper.die('waitFor failed, couldn\'t find ' + error.visible + ' within ' + timeout + 'ms'); - } else { - casper.die('waitFor failed with error', JSON.stringify(error, null, 4)); - } - }); - } -}; diff --git a/aldryn_people/tests/frontend/integration/setup.js b/aldryn_people/tests/frontend/integration/setup.js deleted file mode 100644 index 93f356b1..00000000 --- a/aldryn_people/tests/frontend/integration/setup.js +++ /dev/null @@ -1,13 +0,0 @@ -// ############################################################################# -// Init all settings and event handlers on suite start -'use strict'; - -require('./../casperjs.conf').init(); - -require('./handlers/pageErrors').bind(); -require('./handlers/loadFailures').bind(); -require('./handlers/missingPages').bind(); -require('./handlers/externalMissing').bind(); -require('./handlers/suiteFailures').bind(); - -casper.test.done(); diff --git a/aldryn_people/tests/frontend/karma.conf.js b/aldryn_people/tests/frontend/karma.conf.js deleted file mode 100644 index 348cc51b..00000000 --- a/aldryn_people/tests/frontend/karma.conf.js +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * @author: Divio AG - * @copyright: http://www.divio.ch - */ - -'use strict'; - -// ############################################################################# -// CONFIGURATION -module.exports = function (config) { - var browsers = { - PhantomJS: 'used for local testing' - }; - - var settings = { - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '..', - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine', 'fixture'], - - // list of files / patterns to load in the browser - // tests/${path} - files: [ - // these have to be specified in order since - // dependency loading is not handled yet - '../../aldryn_people/boilerplates/bootstrap3/static/js/libs/*.js', - '../../aldryn_people/boilerplates/bootstrap3/static/js/addons/*.js', - - // tests themselves - 'frontend/unit/*.js', - - // fixture patterns - { - pattern: 'frontend/fixtures/**/*' - } - ], - - // list of files to exclude - exclude: [], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - '../../aldryn_people/boilerplates/bootstrap3/static/js/addons/*.js': ['coverage'], - // for fixtures - '**/*.html': ['html2js'], - '**/*.json': ['json_fixtures'] - }, - - // optionally, configure the reporter - coverageReporter: { - reporters: [ - { type: 'html', dir: 'frontend/coverage/' }, - { type: 'lcov', dir: 'frontend/coverage/' } - ] - }, - - // fixtures dependency - // https://github.com/billtrik/karma-fixture - jsonFixturesPreprocessor: { - variableName: '__json__' - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'coverage'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: - // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: Object.keys(browsers), - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false - }; - - config.set(settings); -}; diff --git a/aldryn_people/tests/frontend/unit/test.cl.people.js b/aldryn_people/tests/frontend/unit/test.cl.people.js deleted file mode 100644 index 8977d9e7..00000000 --- a/aldryn_people/tests/frontend/unit/test.cl.people.js +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * @author: Divio AG - * @copyright: http://www.divio.ch - */ - -'use strict'; -/* global describe, it, expect, beforeEach, afterEach, fixture */ - -// ############################################################################# -// UNIT TEST -describe('cl.people.js:', function () { - beforeEach(function () { - fixture.setBase('frontend/fixtures'); - this.markup = fixture.load('people.html'); - this.preventEvent = { preventDefault: function () {} }; - }); - - afterEach(function () { - fixture.cleanup(); - }); - - it('runs dummy test', function () { - expect('dummy test').toEqual('dummy test'); - }); - -}); diff --git a/aldryn_people/tests/test_admin.py b/aldryn_people/tests/test_admin.py deleted file mode 100644 index b0a5b105..00000000 --- a/aldryn_people/tests/test_admin.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.contrib import admin - -from cms.utils.urlutils import admin_reverse - -from . import BasePeopleTest -from ..admin import PersonAdmin - - -class TestPersonAdmin(BasePeopleTest): - - def test_all_translations(self): - # Check that all the available languages appear in `all_translations` - model_admin = PersonAdmin(self.person1, admin.site) - all_translations = model_admin.all_translations(self.person1) - obj_id = self.person1.id - - change_url = admin_reverse('aldryn_people_person_change', args=[obj_id]) - - self.assertTrue(change_url + '?language=en' in all_translations) - self.assertTrue(change_url + '?language=de' in all_translations) - self.assertTrue(change_url + '?language=fr' in all_translations) diff --git a/aldryn_people/tests/test_app_hook.py b/aldryn_people/tests/test_app_hook.py deleted file mode 100644 index 9446c55e..00000000 --- a/aldryn_people/tests/test_app_hook.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.urls import reverse - -from . import BasePeopleTest -from ..models import Person - - -class TestPersonAppHook(BasePeopleTest): - - def test_add_people_app(self): - """ - We add a person to the app - """ - self.page.application_urls = 'PeopleApp' - self.page.application_namespace = 'aldryn_people' - self.page.save() - self.page.publish(self.language) - - person = Person.objects.create( - name='Michael', phone='0785214521', email='michael@mit.ch', - slug='michael' - ) - # By slug - url = reverse('aldryn_people:person-detail', kwargs={'slug': person.slug}) - response = self.client.get(url) - self.assertContains(response, 'Michael') - - # By pk - url = reverse('aldryn_people:person-detail', kwargs={'pk': person.pk}) - response = self.client.get(url) - self.assertContains(response, 'Michael') diff --git a/aldryn_people/tests/test_migrations.py b/aldryn_people/tests/test_migrations.py deleted file mode 100644 index 2b69e62f..00000000 --- a/aldryn_people/tests/test_migrations.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# original from -# http://tech.octopus.energy/news/2016/01/21/testing-for-missing-migrations-in-django.html -from django.core.management import call_command -from django.test import TestCase, override_settings -from six import text_type -from six.moves import StringIO - - -class MigrationTestCase(TestCase): - - @override_settings(MIGRATION_MODULES={}) - def test_for_missing_migrations(self): - output = StringIO() - options = { - 'interactive': False, - 'dry_run': True, - 'stdout': output, - 'check_changes': True, - } - - try: - call_command('makemigrations', **options) - except SystemExit as e: - status_code = text_type(e) - else: - # the "no changes" exit code is 0 - status_code = '0' - - if status_code == '1': - self.fail('There are missing migrations:\n {}'.format(output.getvalue())) diff --git a/aldryn_people/tests/test_models.py b/aldryn_people/tests/test_models.py deleted file mode 100644 index 060b1353..00000000 --- a/aldryn_people/tests/test_models.py +++ /dev/null @@ -1,249 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.test import TransactionTestCase -from django.utils.encoding import force_text -from django.utils.translation import override - -from . import BasePeopleTest, DefaultApphookMixin -from ..models import Group, Person - - -class TestBasicPeopleModels(DefaultApphookMixin, BasePeopleTest): - - def test_create_person(self): - """We can create a person with a name.""" - name = 'Tom Test' - person = Person.objects.create(name=name) - person.refresh_from_db() - self.assertEqual(person.name, name) - - def test_delete_person(self): - """We can delete a person.""" - name = 'Person Delete' - person = Person.objects.create(name=name) - Person.objects.get(pk=person.pk).delete() - self.assertFalse(Person.objects.filter(pk=person.pk)) - - def test_str(self): - name = 'Person Str' - person = Person.objects.create(name=name) - self.assertEqual(force_text(person), name) - - def test_absolute_url(self): - slug = 'person-slug' - person = Person.objects.create(slug=slug) - # This isn't a translation test, per se, but let's make sure that we - # have a predictable language prefix, regardless of the tester's locale. - with override('en'): - app_hook_url = self.app_hook_page.get_absolute_url() - self.assertEqual( - person.get_absolute_url(), - '{0}{1}/'.format(app_hook_url, slug) - ) - # Now test that it will work when there's no slug too. - person.slug = '' - self.assertEqual( - person.get_absolute_url(), - '{0}{1}/'.format(app_hook_url, person.pk), - ) - - def test_auto_slugify(self): - name = 'Melchior Hoffman' - slug = 'melchior-hoffman' - person = Person.objects.create(name=name) - person.save() - self.assertEquals(person.slug, slug) - - def test_auto_slugify_same_name(self): - name_1 = 'Melchior Hoffman' - slug_1 = 'melchior-hoffman' - person_1 = Person.objects.create(name=name_1) - person_1.save() - - name_2 = 'Melchior Hoffman' - slug_2 = 'melchior-hoffman-1' - person_2 = Person.objects.create(name=name_2) - person_2.save() - - self.assertEquals(person_1.slug, slug_1) - self.assertEquals(person_2.slug, slug_2) - - -class TestBasicGroupModel(TransactionTestCase): - - def test_create_group(self): - """We can create a group with a name.""" - group = Group.objects.create(name='group_b') - self.assertTrue(group.name, 'group_b') - - def test_delete_group(self): - """We can delete a group.""" - name = 'Group Delete' - Group.objects.create(name=name) - group = Group.objects.translated(name=name) - if group: - group[0].delete() - self.assertFalse(Group.objects.translated(name=name)) - - def test_create_another_group(self): - """we create a group.""" - name = 'Gruppe Neu' - group = Group.objects.create(name=name) - self.assertEqual(group.name, name) - self.assertEqual(Group.objects.all()[0], group) - - def test_add_person_to_group(self): - """We create a person and add her to the created group.""" - personname = 'Daniel' - person = Person.objects.create(name=personname) - name = 'Group One' - group = Group.objects.create(name=name) - person.groups.add(group) - person.save() - self.assertIn(person, group.people.all()) - - -class TestPersonModelTranslation(BasePeopleTest): - - def test_person_translatable(self): - person1 = self.reload(self.person1, 'en') - self.assertEqual( - person1.function, - self.data['person1']['en']['function'] - ) - person1 = self.reload(self.person1, 'de') - self.assertEqual( - person1.safe_translation_getter('function'), - self.data['person1']['de']['function'] - ) - - def test_comment(self): - person1 = self.reload(self.person1, 'en') - self.assertEqual( - person1.comment, - self.data['person1']['en']['description'] - ) - person1 = self.reload(self.person1, 'de') - self.assertEqual( - person1.comment, - self.data['person1']['de']['description'] - ) - - def test_get_vcard(self): - person1 = self.reload(self.person1, 'en') - # Test with no group - vcard_en = ('BEGIN:VCARD\r\n' - 'VERSION:3.0\r\n' - 'FN:person1\r\n' - 'N:;person1;;;\r\n' - 'TITLE:function1\r\n' - 'END:VCARD\r\n') - self.assertEqual( - person1.get_vcard().decode('utf-8'), - vcard_en - ) - # Test with a group and other fields populated - group1 = self.reload(self.group1, 'en') - group1.address = '123 Main Street' - group1.city = 'Anytown' - group1.postal_code = '12345' - group1.phone = '+1 (234) 567-8903' - group1.fax = '+1 (234) 567-8904' - group1.website = 'www.groupwebsite.com' - group1.save() - person1.groups.add(group1) - person1.email = 'person@org.org' - person1.phone = '+1 (234) 567-8900' - person1.mobile = '+1 (234) 567-8901' - person1.fax = '+1 (234) 567-8902' - person1.website = 'www.website.com' - person1.save() - vcard_en = ('BEGIN:VCARD\r\n' - 'VERSION:3.0\r\n' - 'FN:person1\r\n' - 'N:;person1;;;\r\n' - 'EMAIL:person@org.org\r\n' - 'TITLE:function1\r\n' - 'TEL;TYPE=WORK:+1 (234) 567-8900\r\n' - 'TEL;TYPE=CELL:+1 (234) 567-8901\r\n' - 'TEL;TYPE=FAX:+1 (234) 567-8902\r\n' - 'URL:www.website.com\r\n' - 'ORG:group1\r\n' - 'ADR;TYPE=WORK:;;123 Main Street;Anytown;;12345;\r\n' - 'TEL;TYPE=WORK:+1 (234) 567-8903\r\n' - 'TEL;TYPE=FAX:+1 (234) 567-8904\r\n' - 'URL:www.groupwebsite.com\r\n' - 'END:VCARD\r\n') - self.assertEqual( - person1.get_vcard().decode('utf-8'), - vcard_en - ) - # Ensure this works for other langs too - person1 = self.reload(self.person1, 'de') - vcard_de = ('BEGIN:VCARD\r\n' - 'VERSION:3.0\r\n' - 'FN:mensch1\r\n' - 'N:;mensch1;;;\r\n' - 'EMAIL:person@org.org\r\n' - 'TITLE:Funktion1\r\n' - 'TEL;TYPE=WORK:+1 (234) 567-8900\r\n' - 'TEL;TYPE=CELL:+1 (234) 567-8901\r\n' - 'TEL;TYPE=FAX:+1 (234) 567-8902\r\n' - 'URL:www.website.com\r\n' - 'ORG:Gruppe1\r\n' - 'ADR;TYPE=WORK:;;123 Main Street;Anytown;;12345;\r\n' - 'TEL;TYPE=WORK:+1 (234) 567-8903\r\n' - 'TEL;TYPE=FAX:+1 (234) 567-8904\r\n' - 'URL:www.groupwebsite.com\r\n' - 'END:VCARD\r\n') - with override('de'): - self.assertEqual( - person1.get_vcard().decode('utf-8'), - vcard_de - ) - - -class TestGroupModelTranslation(BasePeopleTest): - - def test_group_translation(self): - group1 = self.reload(self.group1, 'en') - self.assertEqual(group1.name, self.data['group1']['en']['name']) - group1 = self.reload(self.group1, 'de') - self.assertEqual(group1.name, self.data['group1']['de']['name']) - - def test_company_name(self): - group1 = self.reload(self.group1, 'en') - self.assertEqual( - group1.company_name, - self.data['group1']['en']['name'], - ) - group1 = self.reload(self.group1, 'de') - self.assertEqual( - group1.company_name, - self.data['group1']['de']['name'], - ) - - def test_company_description(self): - group1 = self.reload(self.group1, 'en') - self.assertEqual( - group1.company_description, - self.data['group1']['en']['description'], - ) - group1 = self.reload(self.group1, 'de') - self.assertEqual( - group1.company_description, - self.data['group1']['de']['description'], - ) - - def test_str(self): - group1 = self.reload(self.group1, 'en') - self.assertEqual( - force_text(group1), - self.data['group1']['en']['name'], - ) - group1 = self.reload(self.group1, 'de') - self.assertEqual( - force_text(group1), - self.data['group1']['de']['name'], - ) diff --git a/aldryn_people/tests/test_plugins.py b/aldryn_people/tests/test_plugins.py deleted file mode 100644 index 1017db9b..00000000 --- a/aldryn_people/tests/test_plugins.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.urls import reverse -from django.utils.encoding import force_text - -from cms import api -from cms.utils.i18n import force_language - -from aldryn_people import DEFAULT_APP_NAMESPACE - -from . import BasePeopleTest, DefaultApphookMixin -from ..cms_plugins import PeoplePlugin -from ..models import Group, Person - - -class TestPersonPlugins(DefaultApphookMixin, BasePeopleTest): - - def test_add_people_list_plugin_api(self): - """ - We add a person to the People Plugin and look her up - """ - name = 'Donald' - Person.objects.create(name=name) - plugin = api.add_plugin(self.placeholder, PeoplePlugin, self.language) - plugin.people.set(Person.objects.all()) - self.assertEqual(force_text(plugin), force_text(plugin.pk)) - self.page.publish(self.language) - - url = self.page.get_absolute_url() - response = self.client.get(url, follow=True) - self.assertContains(response, name) - - # This fails because of Sane Add Plugin (I suspect). This will be refactored - # and re-enabled in a future commit. - # def test_add_people_list_plugin_client(self): - # """ - # We log into the PeoplePlugin - # """ - # self.client.login( - # username=self.su_username, password=self.su_password) - # - # plugin_data = { - # 'plugin_type': 'PeoplePlugin', - # 'plugin_language': self.language, - # 'placeholder_id': self.placeholder.pk, - # } - # response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data) - # self.assertEqual(response.status_code, 200) - # self.assertTrue(CMSPlugin.objects.exists()) - - def test_hide_ungrouped(self): - """ - """ - the_bradys = Group.objects.create(name="The Bradys") - alice = Person.objects.create(name="Alice") - bobby = Person.objects.create(name="Bobby") - cindy = Person.objects.create(name="Cindy") - # Alice is the housekeeper, not a real Brady. - bobby.groups.add(the_bradys) - cindy.groups.add(the_bradys) - - # Add a plugin where ungrouped people are not shown - plugin = api.add_plugin(self.placeholder, PeoplePlugin, self.language) - plugin.people.set(Person.objects.all()) - plugin.group_by_group = True - plugin.show_ungrouped = False - plugin.save() - - self.page.publish(self.language) - url = self.page.get_absolute_url() - response = self.client.get(url, follow=True) - self.assertContains(response, bobby.name) - self.assertContains(response, cindy.name) - self.assertNotContains(response, alice.name) - - def test_show_ungrouped(self): - """ - """ - the_bradys = Group.objects.create(name="The Bradys") - alice = Person.objects.create(name="Alice") - bobby = Person.objects.create(name="Bobby") - cindy = Person.objects.create(name="Cindy") - # Alice is the housekeeper, not a real Brady. - bobby.groups.add(the_bradys) - cindy.groups.add(the_bradys) - - # Now, add a new plugin where ungrouped people are shown - plugin = api.add_plugin(self.placeholder, PeoplePlugin, self.language) - plugin.people.set(Person.objects.all()) - plugin.group_by_group = True - plugin.show_ungrouped = True - plugin.save() - - self.page.publish(self.language) - url = self.page.get_absolute_url() - response = self.client.get(url, follow=True) - self.assertContains(response, bobby.name) - self.assertContains(response, cindy.name) - self.assertContains(response, alice.name) - - -class TestPeopleListPluginNoApphook(BasePeopleTest): - - def setUp(self): - super(TestPeopleListPluginNoApphook, self).setUp() - # we are testing only en - self.person1.set_current_language('en') - self.namespace = DEFAULT_APP_NAMESPACE - - def create_plugin(self, plugin_params=None): - if plugin_params is None: - plugin_params = {} - with force_language('en'): - plugin = api.add_plugin( - self.placeholder, PeoplePlugin, 'en', **plugin_params) - self.page.publish('en') - return plugin - - def test_plugin_with_no_apphook_doesnot_breaks_page(self): - self.create_plugin() - url = self.page.get_absolute_url() - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.person1.name) - from ..cms_plugins import NAMESPACE_ERROR - self.assertNotContains(response, NAMESPACE_ERROR[:20]) - - def test_plugin_with_no_apphook_shows_error_message(self): - self.create_plugin() - url = self.page.get_absolute_url() - self.client.login(username=self.su_username, - password=self.su_password) - response = self.client.get(url, user=self.superuser) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.person1.name) - from ..cms_plugins import NAMESPACE_ERROR - self.assertContains(response, NAMESPACE_ERROR[:20]) - - def test_plugin_with_vcard_enabled_no_apphook(self): - self.create_plugin(plugin_params={'show_vcard': True}) - url = self.page.get_absolute_url() - response = self.client.get(url) - self.assertContains(response, self.person1.name) - - def test_plugin_with_vcard_disabled_no_apphook(self): - self.create_plugin(plugin_params={'show_vcard': False}) - url = self.page.get_absolute_url() - response = self.client.get(url) - self.assertContains(response, self.person1.name) - - def test_plugin_show_links_are_shown_if_enabled_and_apphook_page(self): - with force_language('en'): - app_page = self.create_apphook_page() - list_plugin = api.add_plugin( - placeholder=self.placeholder, - plugin_type=PeoplePlugin, - language='en', - ) - list_plugin.show_links = True - list_plugin.save() - self.page.publish('en') - url = self.page.get_absolute_url() - person_url = self.person1.get_absolute_url() - # ensure that url is not the link to the home page and not app page - app_page_len = len(app_page.get_absolute_url()) - self.assertGreater(len(person_url), app_page_len) - response = self.client.get(url, follow=True) - self.assertContains(response, person_url) - # ensure that url is not shown if not enabled for plugin. - list_plugin.show_links = False - list_plugin.save() - self.page.publish('en') - response = self.client.get(url, follow=True) - self.assertNotContains(response, person_url) - - def test_plugin_with_vcard_enabled_with_apphook(self): - vcard_kwargs = { - 'slug': self.person1.slug - } - with force_language('en'): - self.create_apphook_page() - person_vcard_url = reverse( - '{0}:download_vcard'.format(self.namespace), - kwargs=vcard_kwargs) - plugin = self.create_plugin(plugin_params={'show_vcard': True}) - url = self.page.get_absolute_url() - response = self.client.get(url, follow=True) - self.assertContains(response, self.person1.name) - self.assertContains(response, person_vcard_url) - # test that vcard download link is not shown if disabled - plugin.show_vcard = False - plugin.save() - self.page.publish('en') - response = self.client.get(url, follow=True) - self.assertContains(response, self.person1.name) - self.assertNotContains(response, person_vcard_url) diff --git a/aldryn_people/tests/test_search_indexes.py b/aldryn_people/tests/test_search_indexes.py deleted file mode 100644 index 9d52ace4..00000000 --- a/aldryn_people/tests/test_search_indexes.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from aldryn_people.search_indexes import PeopleIndex - -from . import BasePeopleTest -from ..models import Person - - -class TestPeopleIndex(BasePeopleTest): - def test_get_title(self): - idx_obj = PeopleIndex() - person1 = self.reload(self.person1, "en") - self.assertEqual(idx_obj.get_title(person1), person1.name) - person1 = self.reload(self.person1, "de") - self.assertEqual(idx_obj.get_title(person1), person1.name) - - def test_get_index_kwargs(self): - # This is a silly test, but is here for completeness. - idx_obj = PeopleIndex() - self.assertEqual(idx_obj.get_index_kwargs("en"), { - 'translations__language_code': 'en' - }) - - def test_get_index_queryset(self): - idx_obj = PeopleIndex() - # Person2 does NOT have an EN translation, so should appear here. - self.assertEqualItems( - [q.id for q in idx_obj.get_index_queryset("en")], - [self.person1.id], - ) - # Both person objects have DE translations - self.assertEqualItems( - [q.id for q in idx_obj.get_index_queryset("de")], - [self.person1.id, self.person2.id], - ) - - def test_get_model(self): - # This is a silly test, but is here for completeness. - idx_obj = PeopleIndex() - self.assertEqual(idx_obj.get_model(), Person) - - def test_get_search_data(self): - idx_obj = PeopleIndex() - person1 = self.reload(self.person1, "en") - search_data = idx_obj.get_search_data(person1, "en", None) - self.assertEqual(search_data, self.data['person1']['en']['description']) diff --git a/aldryn_people/tests/test_toolbar.py b/aldryn_people/tests/test_toolbar.py deleted file mode 100644 index 4047b079..00000000 --- a/aldryn_people/tests/test_toolbar.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.test import TransactionTestCase - -from ..cms_toolbars import get_admin_url - - -class TestToolbarUtils(TransactionTestCase): - - def test_get_admin_url(self): - # With pattern args, but no URL parameters - change_action = 'auth_user_change' - args = [1, ] - kwargs = {} - change_url = get_admin_url(change_action, args, **kwargs) - - # With pattern args and a single URL parameter - kwargs = {'alpha': 'beta', } - url = get_admin_url(change_action, args, **kwargs) - self.assertIn(change_url + '?alpha=beta', url) - - # With pattern args and two URL parameters - kwargs = {'alpha': 'beta', 'gamma': 'delta', } - url = get_admin_url(change_action, args, **kwargs) - self.assertIn(change_url + '?alpha=beta&gamma=delta', url) - - # With pattern args and 3 URL parameters - kwargs = {'a': 'b', 'g': 'd', 'e': 'z', } - url = get_admin_url(change_action, args, **kwargs) - self.assertIn(change_url + '?a=b&e=z&g=d', url) - - # With pattern args and numerical URL params - kwargs = {'a': 1, 'g': 2, 'e': 3, } - url = get_admin_url(change_action, args, **kwargs) - self.assertIn(change_url + '?a=1&e=3&g=2', url) - - # With pattern args and odd-typed URL params - kwargs = {'a': [], 'g': {}, 'e': None} - url = get_admin_url(change_action, args, **kwargs) - self.assertIn(change_url + '?a=[]&e=None&g={}', url) - - # No pattern args and no kwargs - add_action = 'auth_user_add' - add_url = get_admin_url(add_action) - - # No pattern args... - add_action = 'auth_user_add' - kwargs = {'groups': 1, } - url = get_admin_url(add_action, **kwargs) - self.assertIn(add_url + '?groups=1', url) diff --git a/aldryn_people/tests/test_views.py b/aldryn_people/tests/test_views.py deleted file mode 100644 index 98fe3f92..00000000 --- a/aldryn_people/tests/test_views.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.http import Http404 -from django.test.client import RequestFactory -from django.urls import reverse - -from cms.utils.i18n import force_language - -from . import BasePeopleTest, CMSRequestBasedTest, DefaultApphookMixin -from ..views import DownloadVcardView - - -class TestDownloadVcardView(DefaultApphookMixin, - BasePeopleTest, - CMSRequestBasedTest): - - def test_as_view(self): - """Tests that DownloadVcardView produces the correct headers.""" - person1 = self.reload(self.person1, "en") - person1.slug = 'person1-slug' - kwargs = {"slug": person1.slug} - person1_url = reverse('aldryn_people:person-detail', kwargs=kwargs) - factory = RequestFactory() - request = factory.get(person1_url) - response = DownloadVcardView.as_view()(request, **kwargs) - filename = '{0}.vcf'.format(person1.name) - self.assertEqual( - response["Content-Disposition"], - 'attachment; filename="{0}"'.format(filename) - ) - # Now, disable vcards for this person, and re-test - person1.vcard_enabled = False - person1.save() - with self.assertRaises(Http404): - request = factory.get(person1_url) - response = DownloadVcardView.as_view()(request, **kwargs) - - -class TestMainListView(BasePeopleTest, CMSRequestBasedTest): - - def test_list_view_with_only_en_apphook(self): - page = self.create_apphook_page(multilang=False) - # give some time for apphook reload middleware - self.client.get(page.get_absolute_url()) - - self.set_defalut_person_objects_current_language('en') - with force_language('en'): - url = page.get_absolute_url() - person1_url = self.person1.get_absolute_url() - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.person1.name) - self.assertContains(response, person1_url) - # should not contain person 2 since page for 'de' language is - # not published - self.assertNotContains(response, self.person2.name) - self.assertNotContains(response, self.person2.slug) - - def test_list_view_with_en_and_de_apphook(self): - page = self.create_apphook_page(multilang=True) - # give some time for apphook reload middleware - self.client.get(page.get_absolute_url()) - self.set_defalut_person_objects_current_language('en') - with force_language('en'): - url = page.get_absolute_url() - person1_url = self.person1.get_absolute_url() - person2_url = self.person2.get_absolute_url() - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.person1.name) - self.assertContains(response, self.person2.name) - self.assertContains(response, person1_url) - self.assertContains(response, person2_url) From d9edcb52980e7d72e89c353b2c0324743c576344 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 23 Nov 2020 02:19:03 -0700 Subject: [PATCH 0649/1137] Revert "remove broken tests" This reverts commit 5f0b43f7c09b85c266265c3dc49395179fbce07c. --- aldryn_categories/tests/__init__.py | 3 + aldryn_categories/tests/base.py | 50 ++ aldryn_categories/tests/test_admin.py | 37 + aldryn_categories/tests/test_fields.py | 120 +++ aldryn_categories/tests/test_migrations.py | 31 + aldryn_categories/tests/test_models.py | 184 ++++ aldryn_newsblog/tests/__init__.py | 335 +++++++ .../tests/browser/test_aldryn_newsblog.py | 30 + aldryn_newsblog/tests/frontend/.eslintrc.js | 30 + .../tests/frontend/casperjs.conf.js | 27 + .../tests/frontend/fixtures/search.html | 24 + .../tests/frontend/integration/crud.js | 309 +++++++ .../integration/handlers/externalMissing.js | 12 + .../integration/handlers/loadFailures.js | 12 + .../integration/handlers/missingPages.js | 16 + .../integration/handlers/pageErrors.js | 12 + .../integration/handlers/suiteFailures.js | 22 + .../frontend/integration/related-articles.js | 93 ++ .../tests/frontend/integration/setup.js | 13 + aldryn_newsblog/tests/frontend/karma.conf.js | 95 ++ .../tests/frontend/unit/test.cl.newsblog.js | 137 +++ .../tests/static/featured_image.jpg | Bin 0 -> 22058 bytes .../aldryn_newsblog/dummy/article_detail.html | 1 + .../aldryn_newsblog/dummy/article_list.html | 1 + .../dummy/plugins/latest_articles.html | 1 + aldryn_newsblog/tests/test_admin.py | 43 + aldryn_newsblog/tests/test_cms_wizards.py | 48 + aldryn_newsblog/tests/test_commands.py | 36 + aldryn_newsblog/tests/test_feeds.py | 71 ++ aldryn_newsblog/tests/test_i18n.py | 45 + aldryn_newsblog/tests/test_managers.py | 26 + aldryn_newsblog/tests/test_models.py | 244 ++++++ aldryn_newsblog/tests/test_plugins.py | 413 +++++++++ aldryn_newsblog/tests/test_search.py | 70 ++ aldryn_newsblog/tests/test_sitemaps.py | 94 ++ aldryn_newsblog/tests/test_utils.py | 74 ++ aldryn_newsblog/tests/test_views.py | 823 ++++++++++++++++++ aldryn_people/tests/__init__.py | 301 +++++++ aldryn_people/tests/frontend/.eslintrc.js | 30 + aldryn_people/tests/frontend/casperjs.conf.js | 27 + .../tests/frontend/fixtures/people.html | 1 + .../tests/frontend/integration/crud.js | 189 ++++ .../integration/handlers/externalMissing.js | 12 + .../integration/handlers/loadFailures.js | 12 + .../integration/handlers/missingPages.js | 16 + .../integration/handlers/pageErrors.js | 12 + .../integration/handlers/suiteFailures.js | 22 + .../tests/frontend/integration/setup.js | 13 + aldryn_people/tests/frontend/karma.conf.js | 95 ++ .../tests/frontend/unit/test.cl.people.js | 26 + aldryn_people/tests/test_admin.py | 25 + aldryn_people/tests/test_app_hook.py | 34 + aldryn_people/tests/test_migrations.py | 31 + aldryn_people/tests/test_models.py | 249 ++++++ aldryn_people/tests/test_plugins.py | 197 +++++ aldryn_people/tests/test_search_indexes.py | 48 + aldryn_people/tests/test_toolbar.py | 52 ++ aldryn_people/tests/test_views.py | 75 ++ 58 files changed, 5049 insertions(+) create mode 100644 aldryn_categories/tests/__init__.py create mode 100644 aldryn_categories/tests/base.py create mode 100644 aldryn_categories/tests/test_admin.py create mode 100644 aldryn_categories/tests/test_fields.py create mode 100644 aldryn_categories/tests/test_migrations.py create mode 100644 aldryn_categories/tests/test_models.py create mode 100644 aldryn_newsblog/tests/__init__.py create mode 100644 aldryn_newsblog/tests/browser/test_aldryn_newsblog.py create mode 100644 aldryn_newsblog/tests/frontend/.eslintrc.js create mode 100644 aldryn_newsblog/tests/frontend/casperjs.conf.js create mode 100644 aldryn_newsblog/tests/frontend/fixtures/search.html create mode 100644 aldryn_newsblog/tests/frontend/integration/crud.js create mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js create mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js create mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js create mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js create mode 100644 aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js create mode 100644 aldryn_newsblog/tests/frontend/integration/related-articles.js create mode 100644 aldryn_newsblog/tests/frontend/integration/setup.js create mode 100644 aldryn_newsblog/tests/frontend/karma.conf.js create mode 100644 aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js create mode 100644 aldryn_newsblog/tests/static/featured_image.jpg create mode 100644 aldryn_newsblog/tests/templates/aldryn_newsblog/dummy/article_detail.html create mode 100644 aldryn_newsblog/tests/templates/aldryn_newsblog/dummy/article_list.html create mode 100644 aldryn_newsblog/tests/templates/aldryn_newsblog/dummy/plugins/latest_articles.html create mode 100644 aldryn_newsblog/tests/test_admin.py create mode 100644 aldryn_newsblog/tests/test_cms_wizards.py create mode 100644 aldryn_newsblog/tests/test_commands.py create mode 100644 aldryn_newsblog/tests/test_feeds.py create mode 100644 aldryn_newsblog/tests/test_i18n.py create mode 100644 aldryn_newsblog/tests/test_managers.py create mode 100644 aldryn_newsblog/tests/test_models.py create mode 100644 aldryn_newsblog/tests/test_plugins.py create mode 100644 aldryn_newsblog/tests/test_search.py create mode 100644 aldryn_newsblog/tests/test_sitemaps.py create mode 100644 aldryn_newsblog/tests/test_utils.py create mode 100644 aldryn_newsblog/tests/test_views.py create mode 100644 aldryn_people/tests/__init__.py create mode 100644 aldryn_people/tests/frontend/.eslintrc.js create mode 100644 aldryn_people/tests/frontend/casperjs.conf.js create mode 100644 aldryn_people/tests/frontend/fixtures/people.html create mode 100644 aldryn_people/tests/frontend/integration/crud.js create mode 100644 aldryn_people/tests/frontend/integration/handlers/externalMissing.js create mode 100644 aldryn_people/tests/frontend/integration/handlers/loadFailures.js create mode 100644 aldryn_people/tests/frontend/integration/handlers/missingPages.js create mode 100644 aldryn_people/tests/frontend/integration/handlers/pageErrors.js create mode 100644 aldryn_people/tests/frontend/integration/handlers/suiteFailures.js create mode 100644 aldryn_people/tests/frontend/integration/setup.js create mode 100644 aldryn_people/tests/frontend/karma.conf.js create mode 100644 aldryn_people/tests/frontend/unit/test.cl.people.js create mode 100644 aldryn_people/tests/test_admin.py create mode 100644 aldryn_people/tests/test_app_hook.py create mode 100644 aldryn_people/tests/test_migrations.py create mode 100644 aldryn_people/tests/test_models.py create mode 100644 aldryn_people/tests/test_plugins.py create mode 100644 aldryn_people/tests/test_search_indexes.py create mode 100644 aldryn_people/tests/test_toolbar.py create mode 100644 aldryn_people/tests/test_views.py diff --git a/aldryn_categories/tests/__init__.py b/aldryn_categories/tests/__init__.py new file mode 100644 index 00000000..d8accbf9 --- /dev/null +++ b/aldryn_categories/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from .test_models import CategoryTestCaseMixin # noqa diff --git a/aldryn_categories/tests/base.py b/aldryn_categories/tests/base.py new file mode 100644 index 00000000..2f2098fa --- /dev/null +++ b/aldryn_categories/tests/base.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import random +import string + +from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from django.test import RequestFactory + +from django.contrib.auth.models import User + + +class CategoryTestCaseMixin(object): + """Mixin class for testing Categories""" + + @staticmethod + def reload(node): + """NOTE: django-treebeard requires nodes to be reloaded via the Django + ORM once its sub-tree is modified for the API to work properly. + + See:: https://tabo.pe/projects/django-treebeard/docs/2.0/caveats.html + + This is a simple helper-method to do that.""" + return node.__class__.objects.get(id=node.id) + + @classmethod + def rand_str(cls, prefix=u'', length=23, chars=string.ascii_letters): + return prefix + u''.join(random.choice(chars) for _ in range(length)) + + @classmethod + def create_user(cls): + return User.objects.create( + username=cls.rand_str(), first_name=cls.rand_str(), + last_name=cls.rand_str()) + + @staticmethod + def get_request(language=None): + """ + Returns a Request instance populated with cms specific attributes. + """ + request_factory = RequestFactory(HTTP_HOST=settings.ALLOWED_HOSTS[0]) + request = request_factory.get("/") + request.session = {} + request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE + # Needed for plugin rendering. + request.current_page = None + request.user = AnonymousUser() + return request diff --git a/aldryn_categories/tests/test_admin.py b/aldryn_categories/tests/test_admin.py new file mode 100644 index 00000000..2c65bb64 --- /dev/null +++ b/aldryn_categories/tests/test_admin.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.test import TransactionTestCase + +from .base import CategoryTestCaseMixin +from ..models import Category + + +class AdminTest(CategoryTestCaseMixin, TransactionTestCase): + + def test_admin_owner_default(self): + """ + Test that the ChangeForm contains Treebeard's MoveNodeForm + """ + from django.contrib import admin + admin.autodiscover() + + user = self.create_user() + user.is_superuser = True + user.save() + + root = Category.add_root(name="test root") + root.save() + root = self.reload(root) + root.add_child(name="test child 1") + root.add_child(name="test child 2") + + admin_inst = admin.site._registry[Category] + + request = self.get_request('en') + request.user = user + request.META['HTTP_HOST'] = 'example.com' + response = admin_inst.add_view(request) + option = '' + self.assertContains(response, option) diff --git a/aldryn_categories/tests/test_fields.py b/aldryn_categories/tests/test_fields.py new file mode 100644 index 00000000..750104f9 --- /dev/null +++ b/aldryn_categories/tests/test_fields.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.core.exceptions import ImproperlyConfigured +from django.test import TestCase + +from parler.utils.context import switch_language + +from aldryn_categories.models import Category +from aldryn_categories.fields import ( + CategoryForeignKey, + CategoryManyToManyField, + CategoryModelChoiceField, + CategoryMultipleChoiceField, + CategoryOneToOneField, +) + +from .base import CategoryTestCaseMixin + + +class TestCategoryField(CategoryTestCaseMixin, TestCase): + + def test_category_model_choice_field(self): + root = Category.add_root(name="root") + root.save() + child1 = root.add_child(name="child1") + child2 = root.add_child(name="child2") + grandchild1 = child1.add_child(name="grandchild1") + bad_grandchild = child1.add_child( + name='bad grandchild') + field = CategoryModelChoiceField(None) + + self.assertEqual( + field.label_from_instance(child2), + "  child2", + ) + self.assertEqual( + field.label_from_instance(grandchild1), + "    grandchild1", + ) + self.assertEqual( + field.label_from_instance(bad_grandchild), + '    bad grandchild<script>alert' + '("bad stuff");</script>', + ) + + # Tests that the field correctly throws an ImproperlyConfigured + # exception if the given object is not a Category (or something that + # acts like one) + with self.assertRaises(ImproperlyConfigured): + field.label_from_instance(object) + + # Check that using an untranslated language does not raise exceptions + with switch_language(child1, 'it'): + try: + field.label_from_instance(child1) + except ImproperlyConfigured: + self.fail("Translating to an unavailable language should not " + "result in an exception.") + + def test_category_multiple_choice_field(self): + root = Category.add_root(name="root") + root.save() + child1 = root.add_child(name="child1") + child2 = root.add_child(name="child2") + grandchild1 = child1.add_child(name="grandchild1") + bad_grandchild = child1.add_child( + name='bad grandchild') + root = self.reload(root) + child1 = self.reload(child1) + field = CategoryMultipleChoiceField(None) + self.assertEqual( + field.label_from_instance(child2), + "  child2", + ) + self.assertEqual( + field.label_from_instance(grandchild1), + "    grandchild1", + ) + self.assertEqual( + field.label_from_instance(bad_grandchild), + '    bad grandchild<script>alert' + '("bad stuff");</script>', + ) + + # Tests that the field correctly throws an ImproperlyConfigured + # exception if the given object is not a Category (or something that + # acts like one) + with self.assertRaises(ImproperlyConfigured): + field.label_from_instance(object) + + # Check that using an untranslated language does not raise exceptions + with switch_language(child1, 'it'): + try: + field.label_from_instance(child1) + except ImproperlyConfigured: + self.fail("Translating to an unavailable language should not " + "result in an exception.") + + def test_category_fk_field(self): + field = CategoryForeignKey(Category) + form_field = field.formfield() + self.assertTrue(isinstance(form_field, CategoryModelChoiceField)) + field_type = field.get_internal_type() + self.assertEquals(field_type, 'ForeignKey') + + def test_category_one_to_one_field(self): + field = CategoryOneToOneField(Category) + form_field = field.formfield() + self.assertTrue(isinstance(form_field, CategoryModelChoiceField)) + field_type = field.get_internal_type() + self.assertEquals(field_type, 'ForeignKey') + + def test_category_many_to_many_field(self): + field = CategoryManyToManyField(Category) + form_field = field.formfield() + self.assertTrue(isinstance(form_field, CategoryMultipleChoiceField)) + field_type = field.get_internal_type() + self.assertEquals(field_type, 'ManyToManyField') diff --git a/aldryn_categories/tests/test_migrations.py b/aldryn_categories/tests/test_migrations.py new file mode 100644 index 00000000..2b69e62f --- /dev/null +++ b/aldryn_categories/tests/test_migrations.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# original from +# http://tech.octopus.energy/news/2016/01/21/testing-for-missing-migrations-in-django.html +from django.core.management import call_command +from django.test import TestCase, override_settings +from six import text_type +from six.moves import StringIO + + +class MigrationTestCase(TestCase): + + @override_settings(MIGRATION_MODULES={}) + def test_for_missing_migrations(self): + output = StringIO() + options = { + 'interactive': False, + 'dry_run': True, + 'stdout': output, + 'check_changes': True, + } + + try: + call_command('makemigrations', **options) + except SystemExit as e: + status_code = text_type(e) + else: + # the "no changes" exit code is 0 + status_code = '0' + + if status_code == '1': + self.fail('There are missing migrations:\n {}'.format(output.getvalue())) diff --git a/aldryn_categories/tests/test_models.py b/aldryn_categories/tests/test_models.py new file mode 100644 index 00000000..40006ea7 --- /dev/null +++ b/aldryn_categories/tests/test_models.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +import six + +from django.test import TestCase, TransactionTestCase +from django.utils import translation + +from parler.utils.context import switch_language + +from aldryn_categories.models import Category + +from .base import CategoryTestCaseMixin + + +class TestCategories(CategoryTestCaseMixin, TransactionTestCase): + """Implementation-specific tests""" + + def test_category_slug_creation(self): + name = "Root Node" + root = Category.add_root(name=name) + root.set_current_language("en") + root.save() + self.assertEquals(root.slug, "root-node") + + def test_slug_collision(self): + root = Category.add_root(name="test") + root.save() + root = self.reload(root) + self.assertEquals(root.slug, "test") + child1 = root.add_child(name="test") + self.assertEquals(child1.slug, "test-1") + child2 = root.add_child(name="test") + self.assertEquals(child2.slug, "test-2") + + def test_str(self): + root = Category.add_root(name="test") + root.save() + self.assertEqual(root.name, str(root)) + + def test_str_malicious(self): + malicious = "" + escaped = "<script>alert('hi');</script>" + root = Category.add_root(name=malicious) + root.save() + self.assertEqual(six.u(str(root)), escaped) + + def test_delete(self): + root = Category.add_root(name="test") + root.save() + child1 = root.add_child(name="Child 1") + self.assertIn(child1, root.get_children()) + try: + root.delete() + except TypeError: + self.fail('Deleting a node throws a TypeError.') + except Exception: + self.fail('Deleting a node throws an exception.') + self.assertNotIn(child1, Category.objects.all()) + + def test_non_ascii_slug_generation(self): + """Test slug generation for common non-ASCII types of characters""" + root = Category.add_root(name="Root Node") + root.save() + child1 = root.add_child(name="Germanic umlauts: ä ö ü ß Ä Ö Ü") + self.assertEquals(child1.slug, "germanic-umlauts-a-o-u-ss-a-o-u") + child2 = root.add_child(name="Slavic Cyrillic: смачні пляцки") + self.assertEquals(child2.slug, "slavic-cyrillic-smachni-pliatski") + child3 = root.add_child(name="Simplified Chinese: 美味蛋糕") + self.assertEquals(child3.slug, "simplified-chinese-mei-wei-dan-gao") + # non-ascii only slug + child4 = root.add_child(name="ß ў 美") + self.assertEquals(child4.slug, "ss-u-mei") + + +class TestCategoryTrees(CategoryTestCaseMixin, TestCase): + """django-treebeard related tests""" + + def test_create_in_mem_category(self): + name = "Root Node" + root = Category.add_root(name=name) + root.set_current_language("en") + self.assertEquals(root.name, "Root Node") + + def test_create_in_orm_category(self): + name = "Root Node" + root = Category.add_root(name=name) + root.set_current_language("en") + root.save() + root = self.reload(root) + self.assertEquals(root.name, name) + + def test_tree_depth(self): + a = Category.add_root(name="A") + b = a.add_child(name="B") + c = b.add_child(name="C") + self.assertEqual(c.depth, 3) + + def test_get_children_count(self): + a = Category.add_root(name="A") + a.add_child(name="B") + self.assertEquals(a.get_children_count(), 1) + a.add_child(name="C") + a = self.reload(a) + self.assertEquals(a.get_children_count(), 2) + + def test_get_children(self): + a = Category.add_root(name="A") + b = a.add_child(name="B") + self.assertIn(b, a.get_children()) + c = a.add_child(name="C") + a = self.reload(a) + self.assertIn(c, a.get_children()) + + def test_get_descendants(self): + a = Category.add_root(name="A") + b = a.add_child(name="B") + c = b.add_child(name="C") + self.assertIn(c, a.get_descendants()) + d = b.add_child(name='D') + b = self.reload(b) + self.assertIn(d, b.get_descendants()) + + def test_get_ancestors(self): + a = Category.add_root(name="A") + b = a.add_child(name="B") + c = b.add_child(name="C") + self.assertIn(a, b.get_ancestors()) + self.assertIn(a, c.get_ancestors()) + d = b.add_child(name='D') + self.assertIn(a, d.get_ancestors()) + + def test_move_category(self): + a = Category.add_root(name="A") + b = a.add_child(name="B") + c = a.add_child(name="C") + a = self.reload(a) + b = self.reload(b) + self.assertEqual(a, c.get_parent()) + self.assertNotEqual(b, c.get_parent()) + c.move(b, "first-child") + b = self.reload(b) + c = self.reload(c) + self.assertEqual(b, c.get_parent()) + + +class TestCategoryParler(CategoryTestCaseMixin, TestCase): + """django-parler related tests""" + + def test_add_translations(self): + values = [ + # language code, name, slug + ('en', "Cheese Omelette", "cheese-omelette"), + ('de', "Käseomelett", "kaseomelett"), + ('fr', "Omelette au Fromage", "omelette-au-fromage"), + ] + + node = None + + # Create the translations + for lang, name, slug in values: + if node: + with switch_language(node, lang): + node.name = name + node.save() + else: + with translation.override(lang): + node = Category.add_root(name=name) + node.save() + + # Now test that they exist (and didn't obliterate one another) + for lang, name, slug in values: + with switch_language(node, lang): + self.assertEqual(node.name, name) + self.assertEqual(node.slug, slug) + + # Now test that we gracefully handle languages where there is no + # translation. + with switch_language(node, 'it'): + try: + node.name + except Exception: + self.fail("Translating to an unavailable language should not " + "result in an exception.") diff --git a/aldryn_newsblog/tests/__init__.py b/aldryn_newsblog/tests/__init__.py new file mode 100644 index 00000000..c8871a38 --- /dev/null +++ b/aldryn_newsblog/tests/__init__.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import os +import random +import string +import sys + +from django.conf import settings +from django.contrib.auth.models import AnonymousUser, User +from django.core.cache import cache +from django.test import RequestFactory +from django.urls import clear_url_caches +from django.utils.timezone import now +from django.utils.translation import override + +from cms import api +from cms.apphook_pool import apphook_pool +from cms.appresolver import clear_app_resolvers +from cms.exceptions import AppAlreadyRegistered +from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase +from cms.toolbar.toolbar import CMSToolbar +from cms.utils.conf import get_cms_setting + +from aldryn_categories.models import Category +from aldryn_people.models import Person +from parler.utils.context import switch_language + +from aldryn_newsblog.cms_apps import NewsBlogApp +from aldryn_newsblog.models import Article, NewsBlogConfig + + +TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) +TESTS_STATIC_ROOT = os.path.abspath(os.path.join(TESTS_ROOT, 'static')) + + +class NewsBlogTestsMixin(object): + + NO_REDIRECT_CMS_SETTINGS = { + 1: [ + { + 'code': 'de', + 'name': 'Deutsche', + 'fallbacks': ['en', ] # FOR TESTING DO NOT ADD 'fr' HERE + }, + { + 'code': 'fr', + 'name': 'Française', + 'fallbacks': ['en', ] # FOR TESTING DO NOT ADD 'de' HERE + }, + { + 'code': 'en', + 'name': 'English', + 'fallbacks': ['de', 'fr', ] + }, + { + 'code': 'it', + 'name': 'Italiano', + 'fallbacks': ['fr', ] # FOR TESTING, LEAVE AS ONLY 'fr' + }, + ], + 'default': { + 'redirect_on_fallback': False, + } + } + + @staticmethod + def reload(node): + """NOTE: django-treebeard requires nodes to be reloaded via the Django + ORM once its sub-tree is modified for the API to work properly. + + See:: https://tabo.pe/projects/django-treebeard/docs/2.0/caveats.html + + This is a simple helper-method to do that.""" + return node.__class__.objects.get(id=node.id) + + @classmethod + def rand_str(cls, prefix=u'', length=23, chars=string.ascii_letters): + return prefix + u''.join(random.choice(chars) for _ in range(length)) + + @classmethod + def create_user(cls, **kwargs): + kwargs.setdefault('username', cls.rand_str()) + kwargs.setdefault('first_name', cls.rand_str()) + kwargs.setdefault('last_name', cls.rand_str()) + return User.objects.create(**kwargs) + + def create_person(self): + return Person.objects.create( + user=self.create_user(), slug=self.rand_str()) + + def create_article(self, content=None, **kwargs): + try: + author = kwargs['author'] + except KeyError: + author = self.create_person() + try: + owner = kwargs['owner'] + except KeyError: + owner = author.user + + fields = { + 'title': self.rand_str(), + 'slug': self.rand_str(), + 'author': author, + 'owner': owner, + 'app_config': self.app_config, + 'publishing_date': now(), + 'is_published': True, + } + + fields.update(kwargs) + + article = Article.objects.create(**fields) + # save again to calculate article search_data. + article.save() + + if content: + api.add_plugin(article.content, 'TextPlugin', + self.language, body=content) + return article + + def create_tagged_articles(self, num_articles=3, tags=('tag1', 'tag2'), + **kwargs): + """Create num_articles Articles for each tag""" + articles = {} + for tag_name in tags: + tagged_articles = [] + for _ in range(num_articles): + article = self.create_article(**kwargs) + article.save() + article.tags.add(tag_name) + tagged_articles.append(article) + tag_slug = tagged_articles[0].tags.slugs()[0] + articles[tag_slug] = tagged_articles + return articles + + def setup_categories(self): + """ + Sets-up i18n categories (self.category_root, self.category1 and + self.category2) for use in tests + """ + self.language = settings.LANGUAGES[0][0] + + categories = [] + # Set the default language, create the objects + with override(self.language): + code = "{0}-".format(self.language) + self.category_root = Category.add_root( + name=self.rand_str(prefix=code, length=8)) + categories.append(self.category_root) + self.category1 = self.category_root.add_child( + name=self.rand_str(prefix=code, length=8)) + categories.append(self.category1) + self.category2 = self.category_root.add_child( + name=self.rand_str(prefix=code, length=8)) + categories.append(self.category2) + + # We should reload category_root, since we modified its children. + self.category_root = self.reload(self.category_root) + + # Setup the other language(s) translations for the categories + for language, _ in settings.LANGUAGES[1:]: + for category in categories: + with switch_language(category, language): + code = "{0}-".format(language) + category.name = self.rand_str(prefix=code, length=8) + category.save() + + @staticmethod + def get_request(language=None, url="/"): + """ + Returns a Request instance populated with cms specific attributes. + """ + request_factory = RequestFactory(HTTP_HOST=settings.ALLOWED_HOSTS[0]) + request = request_factory.get(url) + request.session = {} + request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE + # Needed for plugin rendering. + request.current_page = None + request.user = AnonymousUser() + request.toolbar = CMSToolbar(request) + return request + + def setUp(self): + self.template = get_cms_setting('TEMPLATES')[0][0] + self.language = settings.LANGUAGES[0][0] + self.root_page = api.create_page( + 'root page', + self.template, + self.language, + published=True, + ) + + try: + # Django-cms 3.5 doesn't set is_home when create_page is called + self.root_page.set_as_homepage() + except AttributeError: + pass + + self.app_config = NewsBlogConfig.objects.language(self.language).create( + app_title='news_blog', + namespace='NBNS', + paginate_by=15, + ) + self.page = api.create_page( + 'page', self.template, self.language, published=True, + parent=self.root_page, + apphook='NewsBlogApp', + apphook_namespace=self.app_config.namespace) + self.plugin_page = api.create_page( + title="plugin_page", template=self.template, language=self.language, + parent=self.root_page, published=True) + self.placeholder = self.page.placeholders.all()[0] + + self.setup_categories() + + for page in self.root_page, self.page: + for language, _ in settings.LANGUAGES[1:]: + api.create_title(language, page.get_slug(), page) + page.publish(language) + + +class CleanUpMixin(object): + apphook_object = None + + def setUp(self): + super(CleanUpMixin, self).setUp() + apphook_object = self.get_apphook_object() + self.reload_urls(apphook_object) + + def tearDown(self): + """ + Do a proper cleanup, delete everything what is preventing us from + clean environment for tests. + :return: None + """ + self.app_config.delete() + self.reset_all() + cache.clear() + super(CleanUpMixin, self).tearDown() + + def get_apphook_object(self): + return self.apphook_object + + def reset_apphook_cmsapp(self, apphook_object=None): + """ + For tests that should not be polluted by previous setup we need to + ensure that app hooks are reloaded properly. One of the steps is to + reset the relation between EventListAppHook and EventsConfig + """ + if apphook_object is None: + apphook_object = self.get_apphook_object() + app_config = getattr(apphook_object, 'app_config', None) + if app_config and getattr(app_config, 'cmsapp', None): + delattr(apphook_object.app_config, 'cmsapp') + if getattr(app_config, 'cmsapp', None): + delattr(app_config, 'cmsapp') + + def reset_all(self): + """ + Reset all that could leak from previous test to current/next test. + :return: None + """ + apphook_object = self.get_apphook_object() + self.delete_app_module(apphook_object.__module__) + self.reload_urls(apphook_object) + self.apphook_clear() + + def delete_app_module(self, app_module=None): + """ + Remove APP_MODULE from sys.modules. Taken from cms. + :return: None + """ + if app_module is None: + apphook_object = self.get_apphook_object() + app_module = apphook_object.__module__ + if app_module in sys.modules: + del sys.modules[app_module] + + def apphook_clear(self): + """ + Clean up apphook_pool and sys.modules. Taken from cms with slight + adjustments and fixes. + :return: None + """ + try: + apphooks = apphook_pool.get_apphooks() + except AppAlreadyRegistered: + # there is an issue with discover apps, or i'm using it wrong. + # setting discovered to True solves it. Maybe that is due to import + # from aldryn_events.cms_apps which registers EventListAppHook + apphook_pool.discovered = True + apphooks = apphook_pool.get_apphooks() + + for name, label in list(apphooks): + if apphook_pool.apps[name].__class__.__module__ in sys.modules: + del sys.modules[apphook_pool.apps[name].__class__.__module__] + apphook_pool.clear() + self.reset_apphook_cmsapp() + + def reload_urls(self, apphook_object=None): + """ + Clean up url related things (caches, app resolvers, modules). + Taken from cms. + :return: None + """ + if apphook_object is None: + apphook_object = self.get_apphook_object() + app_module = apphook_object.__module__ + package = app_module.split('.')[0] + clear_app_resolvers() + clear_url_caches() + url_modules = [ + 'cms.urls', + '{0}.urls'.format(package), + settings.ROOT_URLCONF + ] + + for module in url_modules: + if module in sys.modules: + del sys.modules[module] + + +class NewsBlogTestCase(CleanUpMixin, NewsBlogTestsMixin, CMSTestCase): + apphook_object = NewsBlogApp + pass + + +class NewsBlogTransactionTestCase(CleanUpMixin, + NewsBlogTestsMixin, + TransactionCMSTestCase): + apphook_object = NewsBlogApp + pass diff --git a/aldryn_newsblog/tests/browser/test_aldryn_newsblog.py b/aldryn_newsblog/tests/browser/test_aldryn_newsblog.py new file mode 100644 index 00000000..2dce4f69 --- /dev/null +++ b/aldryn_newsblog/tests/browser/test_aldryn_newsblog.py @@ -0,0 +1,30 @@ +from django.test import LiveServerTestCase + +from selenium import webdriver + + +class NewVisitorTest(LiveServerTestCase): + + def setUp(self): + self.browser = webdriver.Firefox() + self.browser.implicitly_wait(2) + + def tearDown(self): + self.browser.quit() + + def test_sees_an_empty_page_message(self): + # visit the home page of the facts appplication + self.browser.get(self.live_server_url) + + # the default title of the home page is "News" + self.assertIn("News", self.browser.title) + + # the default h1 is also "News" + h1_text = self.browser.find_element_by_tag_name("h1").text + self.assertIn("News", h1_text) + + def test_admin_user_can_login(self): + # go to the admin + self.browser.get("http://localhost:8000/admin") + + self.fail("finish the test") diff --git a/aldryn_newsblog/tests/frontend/.eslintrc.js b/aldryn_newsblog/tests/frontend/.eslintrc.js new file mode 100644 index 00000000..ab82b252 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/.eslintrc.js @@ -0,0 +1,30 @@ +module.exports = { + "env": { + "node": true + }, + "globals": { + "$": true, + "module": true, + "process": true, + "it": true, + "CMS": true, + "expect": true, + "jasmine": true, + "describe": true, + "casper": true, + "beforeEach": true, + "afterEach": true, + "beforeAll": true, + "afterAll": true, + "spyOn": true, + "spyOnEvent": true, + "fixture": true, + "pending": true + }, + "rules": { + "no-magic-numbers": 0, + "max-nested-callbacks": [2, 8], + "newline-after-var": 0, + "strict": [2, "global"] + } +}; diff --git a/aldryn_newsblog/tests/frontend/casperjs.conf.js b/aldryn_newsblog/tests/frontend/casperjs.conf.js new file mode 100644 index 00000000..2e7bdd9b --- /dev/null +++ b/aldryn_newsblog/tests/frontend/casperjs.conf.js @@ -0,0 +1,27 @@ +'use strict'; + +// ############################################################################# +// CasperJS options + +module.exports = { + init: function () { + this.viewportSize(); + this.timeout(20000); + }, + + viewportSize: function (width, height) { + var viewportWidth = width || 1280; + var viewportHeight = height || 1024; + + casper.echo('Current viewport size is ' + viewportWidth + 'x' + viewportHeight + '.', 'INFO'); + + casper.options.viewportSize = { + width: viewportWidth, + height: viewportHeight + }; + }, + + timeout: function (timeout) { + casper.options.waitTimeout = timeout || 10000; + } +}; diff --git a/aldryn_newsblog/tests/frontend/fixtures/search.html b/aldryn_newsblog/tests/frontend/fixtures/search.html new file mode 100644 index 00000000..9331d315 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/fixtures/search.html @@ -0,0 +1,24 @@ +
    +
    + +
    +
    + + +
    + + + + + +
    +
    +
    +
      +

      Most recent articles containing "test"

      +
    +
    +
    diff --git a/aldryn_newsblog/tests/frontend/integration/crud.js b/aldryn_newsblog/tests/frontend/integration/crud.js new file mode 100644 index 00000000..52f839c8 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/crud.js @@ -0,0 +1,309 @@ +'use strict'; + +var helpers = require('djangocms-casper-helpers'); +var globals = helpers.settings; +var casperjs = require('casper'); +var cms = helpers(casperjs); +var xPath = casperjs.selectXPath; + +casper.test.setUp(function (done) { + casper.start() + .then(cms.login()) + .run(done); +}); + +casper.test.tearDown(function (done) { + casper.start() + .then(cms.logout()) + .run(done); +}); + +casper.test.begin('Creation / deletion of the apphook', function (test) { + casper + .start(globals.adminUrl) + .waitUntilVisible('#content', function () { + test.assertVisible('#content', 'Admin loaded'); + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn News & Blog', + row: 'Sections', + link: 'Add' + })) + ); + }) + .waitForUrl(/add/) + .waitUntilVisible('#newsblogconfig_form') + .then(function () { + test.assertVisible('#newsblogconfig_form', 'Apphook creation form loaded'); + + this.fill('#newsblogconfig_form', { + namespace: 'Test namespace', + app_title: 'Test Blog' + }, true); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The Section "Test Blog" was added successfully.', + 'Apphook config was created' + ); + + test.assertElementCount( + '#result_list tbody tr', + 2, + 'There are 2 apphooks now' + ); + + this.clickLabel('Test Blog', 'a'); + }) + .waitUntilVisible('.deletelink', function () { + this.click('.deletelink'); + }) + .waitForUrl(/delete/, function () { + this.click('input[value="Yes, I\'m sure"]'); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The Section "Test Blog" was deleted successfully.', + 'Apphook config was deleted' + ); + }) + .run(function () { + test.done(); + }); +}); + +cms._modifyPageAdvancedSettings = function _modifyPageAdvancedSettings(opts) { + var that = this; + + return function () { + return this.wait(1000).thenOpen(globals.adminPagesUrl) + .waitUntilVisible('.cms-pagetree-jstree') + .then(that.waitUntilAllAjaxCallsFinish()) + .then(that.expandPageTree()) + .then(function () { + var pageId = that.getPageId(opts.page); + + this.thenOpen(globals.adminPagesUrl + pageId + '/advanced-settings/'); + }) + .waitForSelector('#page_form', function () { + this.fill('#page_form', opts.fields); + }) + .wait(100, function () { + this.click('input.default'); + }) + .waitForUrl(/page/) + .waitUntilVisible('.success') + .then(that.waitUntilAllAjaxCallsFinish()) + .wait(1000); + }; +}; + +casper.test.begin('Creation / deletion of the article', function (test) { + casper + .start() + .then(cms.addPage({ title: 'Blog' })) + .then(cms._modifyPageAdvancedSettings({ + page: 'Blog', + fields: { + application_configs: 1, + application_urls: 'NewsBlogApp' + } + })) + .then(cms.publishPage({ + page: 'Blog' + })) + .thenOpen(globals.editUrl, function () { + test.assertSelectorHasText('p', 'No items available', 'No articles yet'); + }) + .thenOpen(globals.adminUrl) + .waitUntilVisible('#content', function () { + test.assertVisible('#content', 'Admin loaded'); + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn News & Blog', + row: 'Articles', + link: 'Add' + })) + ); + }) + .waitForUrl(/add/) + .waitUntilVisible('#article_form') + .then(function () { + test.assertVisible('#article_form', 'Article creation form loaded'); + + this.fill('#article_form', { + title: 'Test article' + }, true); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The article "Test article" was added successfully.', + 'Article was created' + ); + + test.assertElementCount( + '#result_list tbody tr', + 1, + 'There is 1 article available' + ); + }) + .thenOpen(globals.editUrl, function () { + test.assertSelectorHasText( + '.article.unpublished h2 a', + 'Test article', + 'Article is available on the page' + ); + }) + .thenOpen(globals.adminUrl) + .waitUntilVisible('#content', function () { + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn News & Blog', + row: 'Articles' + })) + ); + }) + .waitForUrl(/article/, function () { + this.clickLabel('Test article', 'a'); + }) + .waitUntilVisible('.deletelink', function () { + this.click('.deletelink'); + }) + .waitForUrl(/delete/, function () { + this.click('input[value="Yes, I\'m sure"]'); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The article "Test article" was deleted successfully.', + 'Article was deleted' + ); + }) + .then(cms.removePage()) + .run(function () { + test.done(); + }); +}); + +casper.test.begin('Latest articles plugin', function (test) { + casper + .start() + .then(cms.addPage({ title: 'Home' })) + .then(cms.addPlugin({ + type: 'NewsBlogLatestArticlesPlugin', + content: { + id_latest_articles: 1 + } + })) + .thenOpen(globals.editUrl, function () { + test.assertSelectorHasText( + 'p.cms-plugin', + 'No items available', + 'No articles yet' + ); + }) + .then(cms.openSideframe()) + // add articles + .withFrame(0, function () { + this.waitForSelector('.cms-pagetree-breadcrumbs') + .then(function () { + this.click('.cms-pagetree-breadcrumbs a:first-child'); + }) + .waitForUrl(/admin/) + .waitForSelector('.dashboard', function () { + this.click(xPath(cms.getXPathForAdminSection({ + section: 'Aldryn News & Blog', + row: 'Articles', + link: 'Add' + }))); + }) + .waitForSelector('#article_form', function () { + this.fill('#article_form', { + title: 'First article' + }, false); + + }) + // wait 3 seconds so the second article is definitely + // created after the first one :) + .wait(3000, function () { + this.click('input[value="Save and add another"]'); + }) + .waitForSelector('.success', function () { + test.assertSelectorHasText( + '.success', + 'The article "First article" was added successfully. You may add another article below.', + 'First article added' + ); + + this.fill('#article_form', { + title: 'Second article' + }, true); + }) + .waitForSelector('.success'); + }) + .thenOpen(globals.editUrl, function () { + test.assertSelectorHasText( + 'p.cms-plugin', + 'No items available', + 'Still no articles yet (no apphooked page yet)' + ); + }) + .then(cms.addPage({ title: 'Blog' })) + .then(cms.addApphookToPage({ + page: 'Blog', + apphook: 'NewsBlogApp' + })) + .then(cms.publishPage({ page: 'Blog' })) + .thenOpen(globals.editUrl, function () { + test.assertSelectorHasText( + '.article h2 a cms-plugin', + 'Second article', + 'Latest article is visible on the page' + ); + test.assertElementCount( + '.article cms-plugin', + 1, + 'Only one latest article is visible on the page' + ); + }) + // remove articles + .then(cms.openSideframe()) + .withFrame(0, function () { + this.waitForSelector('.cms-pagetree-breadcrumbs') + .then(function () { + this.click('.cms-pagetree-breadcrumbs a:first-child'); + }) + .waitForUrl(/admin/) + .waitForSelector('.dashboard', function () { + this.click(xPath(cms.getXPathForAdminSection({ + section: 'Aldryn News & Blog', + row: 'Articles' + }))); + }) + .waitForSelector('#changelist-form', function () { + this.click('th input[type="checkbox"]'); + this.fill('#changelist-form', { + action: 'delete_selected' + }, true); + + }) + .waitForSelector('.delete-confirmation', function () { + this.click('input[value="Yes, I\'m sure"]'); + }) + .waitForSelector('.success', function () { + test.assertSelectorHasText( + '.success', + 'Successfully deleted 2 articles.', + 'Articles deleted' + ); + }); + }) + .then(cms.removePage()) + .then(cms.removePage()) + .run(function () { + test.done(); + }); +}); diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js b/aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js new file mode 100644 index 00000000..4962b9e6 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/handlers/externalMissing.js @@ -0,0 +1,12 @@ +'use strict'; + +// ############################################################################# +// Handles external resources load failures + +module.exports = { + bind: function () { + casper.on('resource.error', function (resource) { + casper.echo('Resource failed to load: ' + resource.url, 'ERROR'); + }); + } +}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js b/aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js new file mode 100644 index 00000000..9a66119c --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/handlers/loadFailures.js @@ -0,0 +1,12 @@ +'use strict'; + +// ############################################################################# +// Handles load failure errors + +module.exports = { + bind: function () { + casper.on('load.failed', function (error) { + casper.echo(JSON.stringify(error), 'ERROR'); + }); + } +}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js b/aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js new file mode 100644 index 00000000..ff6d5351 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/handlers/missingPages.js @@ -0,0 +1,16 @@ +'use strict'; + +// ############################################################################# +// Handles 404 and 500 pages + +module.exports = { + bind: function () { + casper.on('http.status.404', function (resource) { + casper.echo('404 page found: ' + resource.url, 'ERROR'); + }); + + casper.on('http.status.500', function (resource) { + casper.echo('500 page found: ' + resource.url, 'ERROR'); + }); + } +}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js b/aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js new file mode 100644 index 00000000..77ab8860 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/handlers/pageErrors.js @@ -0,0 +1,12 @@ +'use strict'; + +// ############################################################################# +// Handles JavaScript page errors + +module.exports = { + bind: function () { + casper.on('page.error', function (msg) { + casper.echo('Error on page: ' + JSON.stringify(msg), 'ERROR'); + }); + } +}; diff --git a/aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js b/aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js new file mode 100644 index 00000000..98f019e9 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/handlers/suiteFailures.js @@ -0,0 +1,22 @@ +'use strict'; + +// ############################################################################# +// Handles test suite errors (assert and waitFor) + +module.exports = { + bind: function () { + casper.on('step.error', function (error) { + casper.die('assert failed: ' + error.message); + }); + + casper.on('waitFor.timeout', function (timeout, error) { + if (error.selector) { + casper.die('waitFor failed, couldn\'t find ' + error.selector + ' within ' + timeout + 'ms'); + } else if (error.visible) { + casper.die('waitFor failed, couldn\'t find ' + error.visible + ' within ' + timeout + 'ms'); + } else { + casper.die('waitFor failed with error', JSON.stringify(error, null, 4)); + } + }); + } +}; diff --git a/aldryn_newsblog/tests/frontend/integration/related-articles.js b/aldryn_newsblog/tests/frontend/integration/related-articles.js new file mode 100644 index 00000000..889f8bdb --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/related-articles.js @@ -0,0 +1,93 @@ +'use strict'; + +var helpers = require('djangocms-casper-helpers'); +var globals = helpers.settings; +var casperjs = require('casper'); +var cms = helpers(casperjs); +var xPath = casperjs.selectXPath; + +casper.test.setUp(function (done) { + casper.start() + .then(cms.login()) + .run(done); +}); + +casper.test.tearDown(function (done) { + casper.start() + .then(cms.logout()) + .run(done); +}); + +casper.test.begin('Related articles', function (test) { + casper + .then(cms.addPage({ title: 'Home' })) + .thenOpen(globals.editUrl) + .then(cms.openSideframe()) + // add articles + .withFrame(0, function () { + this.waitForSelector('.cms-pagetree-breadcrumbs') + .then(function () { + this.click('.cms-pagetree-breadcrumbs a:first-child'); + }) + .waitForUrl(/admin/) + .waitForSelector('.dashboard', function () { + this.click(xPath(cms.getXPathForAdminSection({ + section: 'Aldryn News & Blog', + row: 'Articles', + link: 'Add' + }))); + }) + .waitForSelector('#article_form', function () { + test.assertDoesntExist('.field-related .add-related', 'Related articles "Add" are not shown'); + + this.fill('#article_form', { + title: 'First article' + }, false); + + }) + // wait 3 seconds so the second article is definitely + // created after the first one :) + .wait(3000, function () { + this.click('input[value="Save and add another"]'); + }) + .waitForSelector('.success', function () { + test.assertSelectorHasText( + '.success', + 'The article "First article" was added successfully. You may add another article below.', + 'First article added' + ); + + test.assertDoesntExist('.field-related .add-related', 'Related articles "Add" are not shown'); + test.assertSelectorHasText( + '.sortedm2m', + 'First article', + 'Correct related articles possibilities are shown' + ); + + this.fill('#article_form', { + title: 'Second article' + }, true); + }) + .waitForSelector('#changelist-form', function () { + this.click('th input[type="checkbox"]'); + this.fill('#changelist-form', { + action: 'delete_selected' + }, true); + + }) + .waitForSelector('.delete-confirmation', function () { + this.click('input[value="Yes, I\'m sure"]'); + }) + .waitForSelector('.success', function () { + test.assertSelectorHasText( + '.success', + 'Successfully deleted 2 articles.', + 'Articles deleted' + ); + }); + }) + .then(cms.removePage()) + .run(function () { + test.done(); + }); +}); diff --git a/aldryn_newsblog/tests/frontend/integration/setup.js b/aldryn_newsblog/tests/frontend/integration/setup.js new file mode 100644 index 00000000..93f356b1 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/integration/setup.js @@ -0,0 +1,13 @@ +// ############################################################################# +// Init all settings and event handlers on suite start +'use strict'; + +require('./../casperjs.conf').init(); + +require('./handlers/pageErrors').bind(); +require('./handlers/loadFailures').bind(); +require('./handlers/missingPages').bind(); +require('./handlers/externalMissing').bind(); +require('./handlers/suiteFailures').bind(); + +casper.test.done(); diff --git a/aldryn_newsblog/tests/frontend/karma.conf.js b/aldryn_newsblog/tests/frontend/karma.conf.js new file mode 100644 index 00000000..b2e23f71 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/karma.conf.js @@ -0,0 +1,95 @@ +/*! + * @author: Divio AG + * @copyright: http://www.divio.ch + */ + +'use strict'; + +// ############################################################################# +// CONFIGURATION +module.exports = function (config) { + var browsers = { + PhantomJS: 'used for local testing' + }; + + var settings = { + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '..', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine', 'fixture'], + + // list of files / patterns to load in the browser + // tests/${path} + files: [ + // these have to be specified in order since + // dependency loading is not handled yet + '../../aldryn_newsblog/boilerplates/bootstrap3/static/js/libs/*.js', + '../../aldryn_newsblog/boilerplates/bootstrap3/static/js/addons/*.js', + + // tests themselves + 'frontend/unit/*.js', + + // fixture patterns + { + pattern: 'frontend/fixtures/**/*' + } + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + '../../aldryn_newsblog/boilerplates/bootstrap3/static/js/addons/*.js': ['coverage'], + // for fixtures + '**/*.html': ['html2js'], + '**/*.json': ['json_fixtures'] + }, + + // optionally, configure the reporter + coverageReporter: { + reporters: [ + { type: 'html', dir: 'frontend/coverage/' }, + { type: 'lcov', dir: 'frontend/coverage/' } + ] + }, + + // fixtures dependency + // https://github.com/billtrik/karma-fixture + jsonFixturesPreprocessor: { + variableName: '__json__' + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress', 'coverage'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: + // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: Object.keys(browsers), + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false + }; + + config.set(settings); +}; diff --git a/aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js b/aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js new file mode 100644 index 00000000..3566b049 --- /dev/null +++ b/aldryn_newsblog/tests/frontend/unit/test.cl.newsblog.js @@ -0,0 +1,137 @@ +/*! + * @author: Divio AG + * @copyright: http://www.divio.ch + */ + +'use strict'; +/* global Cl, $, describe, window, it, expect, beforeEach, afterEach, fixture, spyOn */ + +// ############################################################################# +// UNIT TEST +describe('cl.newsblog.js:', function () { + beforeEach(function () { + fixture.setBase('frontend/fixtures'); + this.markup = fixture.load('search.html'); + this.preventEvent = { preventDefault: function () {} }; + }); + + afterEach(function () { + fixture.cleanup(); + }); + + it('has available Cl namespace', function () { + expect(Cl).toBeDefined(); + }); + + it('has a public method _search', function () { + expect(Cl.newsBlog._search).toBeDefined(); + }); + + describe('Cl.newsBlog.init(): ', function () { + it('returns undefined', function () { + expect(Cl.newsBlog.init()).toEqual(undefined); + }); + + it('runs _search()', function () { + spyOn(Cl.newsBlog, '_search'); + Cl.newsBlog.init(); + + // validate that _search was called inside Cl.newsBlog.init() + expect(Cl.newsBlog._search).toHaveBeenCalled(); + // validate 2 call as 2 js-aldryn-newsblog-article-search is + // specified in search.html + expect(Cl.newsBlog._search.calls.count()).toEqual(2); + }); + }); + + describe('Cl.newsBlog._search: ', function () { + it('returns undefined', function () { + // validate the return of undefined + expect(Cl.newsBlog._search( + $('.js-aldryn-newsblog-article-search').eq(1))) + .toEqual(undefined); + }); + + it('has correct url parameter in ajax request', function () { + spyOn($, 'ajax').and.returnValue({ + always: function () { + return { fail: function () {} }; + } + }); + + Cl.newsBlog._handler.call( + $('.js-aldryn-newsblog-article-search .form-inline')[0], + this.preventEvent); + + var callArgs = $.ajax.calls.allArgs()[0][0]; + + // validate ajax request url + expect(callArgs.url).toEqual( + '/en/blog/search/' + ); + }); + + it('has correct data parameter in ajax request', function () { + spyOn($, 'ajax').and.returnValue({ + always: function () { + return { fail: function () {} }; + } + }); + Cl.newsBlog._handler.call( + $('.js-aldryn-newsblog-article-search .form-inline')[0], + this.preventEvent); + + var callArgs = $.ajax.calls.allArgs()[0][0]; + + // validate ajax request data + expect(callArgs.data).toEqual( + 'csrfmiddlewaretoken=Ui0tGBmn0Thq5LUeUS2m4zAF20H0M8up&' + + 'max_articles=10&q=' + ); + }); + + it('has ajax request with "always" function adding results data ' + + 'correctly', function () { + // emulate always after ajax call + spyOn($, 'ajax').and.returnValue({ + always: function (callback) { + callback('

    Test results

    '); + + return { fail: function () {} }; + } + }); + + Cl.newsBlog._handler.call( + $('.js-aldryn-newsblog-article-search .form-inline')[0], + this.preventEvent); + + // validate search results got updated with new info + expect($('.js-search-results')[0].innerHTML).toEqual( + '

    Test results

    '); + }); + + it('has ajax request with "fail" function alert working ' + + 'correctly', function () { + // emulate fail after always after ajax call + spyOn($, 'ajax').and.returnValue({ + always: function () { + return { + fail: function (callback) { + callback(); + } + }; + } + }); + + spyOn(window, 'alert'); + + Cl.newsBlog._handler.call( + $('.js-aldryn-newsblog-article-search .form-inline')[0], + this.preventEvent); + + // validate alert text + expect(window.alert).toHaveBeenCalledWith('REQUEST TIMEOUT'); + }); + }); + +}); diff --git a/aldryn_newsblog/tests/static/featured_image.jpg b/aldryn_newsblog/tests/static/featured_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..60b16899f2cb7e0bb11f61bdf8cd178646de6ab7 GIT binary patch literal 22058 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

    E9<($=W@kd2`Q;ZY5Z>-f)!3ABWtI27{b(EOj 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.7) + # we need to delete one of configs to be sure that it is pre selected + # in the admin view. + if NewsBlogConfig.objects.count() > 1: + # delete the app config that was created during test set up. + NewsBlogConfig.objects.filter(namespace='NBNS').delete() + user = self.create_user() + user.is_superuser = True + user.save() + + Person.objects.create(user=user, name=u' '.join( + (user.first_name, user.last_name))) + + admin_inst = admin.site._registry[Article] + self.request = self.get_request('en') + self.request.user = user + self.request.META['HTTP_HOST'] = 'example.com' + response = admin_inst.add_view(self.request) + option = r'

    diff --git a/aldryn_people/tests/frontend/integration/crud.js b/aldryn_people/tests/frontend/integration/crud.js new file mode 100644 index 00000000..2eae53c5 --- /dev/null +++ b/aldryn_people/tests/frontend/integration/crud.js @@ -0,0 +1,189 @@ +'use strict'; + +var helpers = require('djangocms-casper-helpers'); +var globals = helpers.settings; +var casperjs = require('casper'); +var cms = helpers(casperjs); +var xPath = casperjs.selectXPath; + +casper.test.setUp(function (done) { + casper.start() + .then(cms.login()) + .run(done); +}); + +casper.test.tearDown(function (done) { + casper.start() + .then(cms.logout()) + .run(done); +}); + +casper.test.begin('Creation of a new group', function (test) { + casper + .start(globals.adminUrl) + .waitUntilVisible('#content', function () { + test.assertVisible('#content', 'Admin loaded'); + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn_People', + row: 'Groups', + link: 'Add' + })) + ); + }) + .waitForUrl(/add/) + .waitUntilVisible('#group_form') + .then(function () { + test.assertVisible('#group_form', 'Group creation form has been loaded'); + + this.fill('#group_form', { + name: 'Test group' + }, true); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The Group "Test group" was added successfully.', + 'New group has been created' + ); + }) + .run(function () { + test.done(); + }); +}); + +casper.test.begin('Creation of a new people entry', function (test) { + casper + .start(globals.adminUrl) + .waitUntilVisible('#content', function () { + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn_People', + row: 'People', + link: 'Add' + })) + ); + }) + .waitForUrl(/add/) + .waitUntilVisible('#person_form') + .then(function () { + test.assertVisible('#person_form', 'People creation form has been loaded'); + + this.fill('#person_form', { + name: 'Test person' + }, true); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The Person "Test person" was added successfully.', + 'New person has been created' + ); + }) + .run(function () { + test.done(); + }); +}); + +casper.test.begin('Adding a new people block to the page', function (test) { + casper + .start() + .then(cms.addPage({ title: 'People' })) + .then(cms.addPlugin({ + type: 'PeoplePlugin' + })) + .thenOpen(globals.editUrl, function () { + test.assertTitleMatch(/People/, 'The People page has been created'); + }) + .then(cms.publishPage({ page: 'People' })) + .thenOpen(globals.editUrl, function () { + test.assertSelectorHasText( + '.cms-plugin h2', + 'Test person', + 'The newly created "Test person" is displayed on the "People" page' + ); + }) + .run(function () { + test.done(); + }); +}); + +casper.test.begin('Deletion of a group', function (test) { + casper + .start(globals.adminUrl) + .waitUntilVisible('#content', function () { + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn_People', + row: 'Groups', + link: 'Change' + })) + ); + }) + .waitForUrl(/group/) + .waitUntilVisible('#result_list', function () { + test.assertElementCount( + '#result_list tbody tr', + 1, + 'The group is available' + ); + + this.clickLabel('Test group', 'a'); + }) + .waitUntilVisible('.deletelink', function () { + this.click('.deletelink'); + }) + .waitForUrl(/delete/, function () { + this.click('input[value="Yes, I\'m sure"]'); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The Group "Test group" was deleted successfully.', + 'The group has been deleted' + ); + }) + .run(function () { + test.done(); + }); +}); + +casper.test.begin('Deletion of a people entry', function (test) { + casper + .start(globals.adminUrl) + .waitUntilVisible('#content', function () { + this.click( + xPath(cms.getXPathForAdminSection({ + section: 'Aldryn_People', + row: 'People', + link: 'Change' + })) + ); + }) + .waitForUrl(/person/) + .waitUntilVisible('#result_list', function () { + test.assertElementCount( + '#result_list tbody tr', + 1, + 'The people entry is available' + ); + + this.clickLabel('Test person', 'a'); + }) + .waitUntilVisible('.deletelink', function () { + this.click('.deletelink'); + }) + .waitForUrl(/delete/, function () { + this.click('input[value="Yes, I\'m sure"]'); + }) + .waitUntilVisible('.success', function () { + test.assertSelectorHasText( + '.success', + 'The Person "Test person" was deleted successfully.', + 'The person has been deleted' + ); + }) + .run(function () { + test.done(); + }); +}); diff --git a/aldryn_people/tests/frontend/integration/handlers/externalMissing.js b/aldryn_people/tests/frontend/integration/handlers/externalMissing.js new file mode 100644 index 00000000..4962b9e6 --- /dev/null +++ b/aldryn_people/tests/frontend/integration/handlers/externalMissing.js @@ -0,0 +1,12 @@ +'use strict'; + +// ############################################################################# +// Handles external resources load failures + +module.exports = { + bind: function () { + casper.on('resource.error', function (resource) { + casper.echo('Resource failed to load: ' + resource.url, 'ERROR'); + }); + } +}; diff --git a/aldryn_people/tests/frontend/integration/handlers/loadFailures.js b/aldryn_people/tests/frontend/integration/handlers/loadFailures.js new file mode 100644 index 00000000..9a66119c --- /dev/null +++ b/aldryn_people/tests/frontend/integration/handlers/loadFailures.js @@ -0,0 +1,12 @@ +'use strict'; + +// ############################################################################# +// Handles load failure errors + +module.exports = { + bind: function () { + casper.on('load.failed', function (error) { + casper.echo(JSON.stringify(error), 'ERROR'); + }); + } +}; diff --git a/aldryn_people/tests/frontend/integration/handlers/missingPages.js b/aldryn_people/tests/frontend/integration/handlers/missingPages.js new file mode 100644 index 00000000..ff6d5351 --- /dev/null +++ b/aldryn_people/tests/frontend/integration/handlers/missingPages.js @@ -0,0 +1,16 @@ +'use strict'; + +// ############################################################################# +// Handles 404 and 500 pages + +module.exports = { + bind: function () { + casper.on('http.status.404', function (resource) { + casper.echo('404 page found: ' + resource.url, 'ERROR'); + }); + + casper.on('http.status.500', function (resource) { + casper.echo('500 page found: ' + resource.url, 'ERROR'); + }); + } +}; diff --git a/aldryn_people/tests/frontend/integration/handlers/pageErrors.js b/aldryn_people/tests/frontend/integration/handlers/pageErrors.js new file mode 100644 index 00000000..77ab8860 --- /dev/null +++ b/aldryn_people/tests/frontend/integration/handlers/pageErrors.js @@ -0,0 +1,12 @@ +'use strict'; + +// ############################################################################# +// Handles JavaScript page errors + +module.exports = { + bind: function () { + casper.on('page.error', function (msg) { + casper.echo('Error on page: ' + JSON.stringify(msg), 'ERROR'); + }); + } +}; diff --git a/aldryn_people/tests/frontend/integration/handlers/suiteFailures.js b/aldryn_people/tests/frontend/integration/handlers/suiteFailures.js new file mode 100644 index 00000000..98f019e9 --- /dev/null +++ b/aldryn_people/tests/frontend/integration/handlers/suiteFailures.js @@ -0,0 +1,22 @@ +'use strict'; + +// ############################################################################# +// Handles test suite errors (assert and waitFor) + +module.exports = { + bind: function () { + casper.on('step.error', function (error) { + casper.die('assert failed: ' + error.message); + }); + + casper.on('waitFor.timeout', function (timeout, error) { + if (error.selector) { + casper.die('waitFor failed, couldn\'t find ' + error.selector + ' within ' + timeout + 'ms'); + } else if (error.visible) { + casper.die('waitFor failed, couldn\'t find ' + error.visible + ' within ' + timeout + 'ms'); + } else { + casper.die('waitFor failed with error', JSON.stringify(error, null, 4)); + } + }); + } +}; diff --git a/aldryn_people/tests/frontend/integration/setup.js b/aldryn_people/tests/frontend/integration/setup.js new file mode 100644 index 00000000..93f356b1 --- /dev/null +++ b/aldryn_people/tests/frontend/integration/setup.js @@ -0,0 +1,13 @@ +// ############################################################################# +// Init all settings and event handlers on suite start +'use strict'; + +require('./../casperjs.conf').init(); + +require('./handlers/pageErrors').bind(); +require('./handlers/loadFailures').bind(); +require('./handlers/missingPages').bind(); +require('./handlers/externalMissing').bind(); +require('./handlers/suiteFailures').bind(); + +casper.test.done(); diff --git a/aldryn_people/tests/frontend/karma.conf.js b/aldryn_people/tests/frontend/karma.conf.js new file mode 100644 index 00000000..348cc51b --- /dev/null +++ b/aldryn_people/tests/frontend/karma.conf.js @@ -0,0 +1,95 @@ +/*! + * @author: Divio AG + * @copyright: http://www.divio.ch + */ + +'use strict'; + +// ############################################################################# +// CONFIGURATION +module.exports = function (config) { + var browsers = { + PhantomJS: 'used for local testing' + }; + + var settings = { + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '..', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine', 'fixture'], + + // list of files / patterns to load in the browser + // tests/${path} + files: [ + // these have to be specified in order since + // dependency loading is not handled yet + '../../aldryn_people/boilerplates/bootstrap3/static/js/libs/*.js', + '../../aldryn_people/boilerplates/bootstrap3/static/js/addons/*.js', + + // tests themselves + 'frontend/unit/*.js', + + // fixture patterns + { + pattern: 'frontend/fixtures/**/*' + } + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + '../../aldryn_people/boilerplates/bootstrap3/static/js/addons/*.js': ['coverage'], + // for fixtures + '**/*.html': ['html2js'], + '**/*.json': ['json_fixtures'] + }, + + // optionally, configure the reporter + coverageReporter: { + reporters: [ + { type: 'html', dir: 'frontend/coverage/' }, + { type: 'lcov', dir: 'frontend/coverage/' } + ] + }, + + // fixtures dependency + // https://github.com/billtrik/karma-fixture + jsonFixturesPreprocessor: { + variableName: '__json__' + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress', 'coverage'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: + // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: Object.keys(browsers), + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false + }; + + config.set(settings); +}; diff --git a/aldryn_people/tests/frontend/unit/test.cl.people.js b/aldryn_people/tests/frontend/unit/test.cl.people.js new file mode 100644 index 00000000..8977d9e7 --- /dev/null +++ b/aldryn_people/tests/frontend/unit/test.cl.people.js @@ -0,0 +1,26 @@ +/*! + * @author: Divio AG + * @copyright: http://www.divio.ch + */ + +'use strict'; +/* global describe, it, expect, beforeEach, afterEach, fixture */ + +// ############################################################################# +// UNIT TEST +describe('cl.people.js:', function () { + beforeEach(function () { + fixture.setBase('frontend/fixtures'); + this.markup = fixture.load('people.html'); + this.preventEvent = { preventDefault: function () {} }; + }); + + afterEach(function () { + fixture.cleanup(); + }); + + it('runs dummy test', function () { + expect('dummy test').toEqual('dummy test'); + }); + +}); diff --git a/aldryn_people/tests/test_admin.py b/aldryn_people/tests/test_admin.py new file mode 100644 index 00000000..b0a5b105 --- /dev/null +++ b/aldryn_people/tests/test_admin.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.contrib import admin + +from cms.utils.urlutils import admin_reverse + +from . import BasePeopleTest +from ..admin import PersonAdmin + + +class TestPersonAdmin(BasePeopleTest): + + def test_all_translations(self): + # Check that all the available languages appear in `all_translations` + model_admin = PersonAdmin(self.person1, admin.site) + all_translations = model_admin.all_translations(self.person1) + obj_id = self.person1.id + + change_url = admin_reverse('aldryn_people_person_change', args=[obj_id]) + + self.assertTrue(change_url + '?language=en' in all_translations) + self.assertTrue(change_url + '?language=de' in all_translations) + self.assertTrue(change_url + '?language=fr' in all_translations) diff --git a/aldryn_people/tests/test_app_hook.py b/aldryn_people/tests/test_app_hook.py new file mode 100644 index 00000000..9446c55e --- /dev/null +++ b/aldryn_people/tests/test_app_hook.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.urls import reverse + +from . import BasePeopleTest +from ..models import Person + + +class TestPersonAppHook(BasePeopleTest): + + def test_add_people_app(self): + """ + We add a person to the app + """ + self.page.application_urls = 'PeopleApp' + self.page.application_namespace = 'aldryn_people' + self.page.save() + self.page.publish(self.language) + + person = Person.objects.create( + name='Michael', phone='0785214521', email='michael@mit.ch', + slug='michael' + ) + # By slug + url = reverse('aldryn_people:person-detail', kwargs={'slug': person.slug}) + response = self.client.get(url) + self.assertContains(response, 'Michael') + + # By pk + url = reverse('aldryn_people:person-detail', kwargs={'pk': person.pk}) + response = self.client.get(url) + self.assertContains(response, 'Michael') diff --git a/aldryn_people/tests/test_migrations.py b/aldryn_people/tests/test_migrations.py new file mode 100644 index 00000000..2b69e62f --- /dev/null +++ b/aldryn_people/tests/test_migrations.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# original from +# http://tech.octopus.energy/news/2016/01/21/testing-for-missing-migrations-in-django.html +from django.core.management import call_command +from django.test import TestCase, override_settings +from six import text_type +from six.moves import StringIO + + +class MigrationTestCase(TestCase): + + @override_settings(MIGRATION_MODULES={}) + def test_for_missing_migrations(self): + output = StringIO() + options = { + 'interactive': False, + 'dry_run': True, + 'stdout': output, + 'check_changes': True, + } + + try: + call_command('makemigrations', **options) + except SystemExit as e: + status_code = text_type(e) + else: + # the "no changes" exit code is 0 + status_code = '0' + + if status_code == '1': + self.fail('There are missing migrations:\n {}'.format(output.getvalue())) diff --git a/aldryn_people/tests/test_models.py b/aldryn_people/tests/test_models.py new file mode 100644 index 00000000..060b1353 --- /dev/null +++ b/aldryn_people/tests/test_models.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.test import TransactionTestCase +from django.utils.encoding import force_text +from django.utils.translation import override + +from . import BasePeopleTest, DefaultApphookMixin +from ..models import Group, Person + + +class TestBasicPeopleModels(DefaultApphookMixin, BasePeopleTest): + + def test_create_person(self): + """We can create a person with a name.""" + name = 'Tom Test' + person = Person.objects.create(name=name) + person.refresh_from_db() + self.assertEqual(person.name, name) + + def test_delete_person(self): + """We can delete a person.""" + name = 'Person Delete' + person = Person.objects.create(name=name) + Person.objects.get(pk=person.pk).delete() + self.assertFalse(Person.objects.filter(pk=person.pk)) + + def test_str(self): + name = 'Person Str' + person = Person.objects.create(name=name) + self.assertEqual(force_text(person), name) + + def test_absolute_url(self): + slug = 'person-slug' + person = Person.objects.create(slug=slug) + # This isn't a translation test, per se, but let's make sure that we + # have a predictable language prefix, regardless of the tester's locale. + with override('en'): + app_hook_url = self.app_hook_page.get_absolute_url() + self.assertEqual( + person.get_absolute_url(), + '{0}{1}/'.format(app_hook_url, slug) + ) + # Now test that it will work when there's no slug too. + person.slug = '' + self.assertEqual( + person.get_absolute_url(), + '{0}{1}/'.format(app_hook_url, person.pk), + ) + + def test_auto_slugify(self): + name = 'Melchior Hoffman' + slug = 'melchior-hoffman' + person = Person.objects.create(name=name) + person.save() + self.assertEquals(person.slug, slug) + + def test_auto_slugify_same_name(self): + name_1 = 'Melchior Hoffman' + slug_1 = 'melchior-hoffman' + person_1 = Person.objects.create(name=name_1) + person_1.save() + + name_2 = 'Melchior Hoffman' + slug_2 = 'melchior-hoffman-1' + person_2 = Person.objects.create(name=name_2) + person_2.save() + + self.assertEquals(person_1.slug, slug_1) + self.assertEquals(person_2.slug, slug_2) + + +class TestBasicGroupModel(TransactionTestCase): + + def test_create_group(self): + """We can create a group with a name.""" + group = Group.objects.create(name='group_b') + self.assertTrue(group.name, 'group_b') + + def test_delete_group(self): + """We can delete a group.""" + name = 'Group Delete' + Group.objects.create(name=name) + group = Group.objects.translated(name=name) + if group: + group[0].delete() + self.assertFalse(Group.objects.translated(name=name)) + + def test_create_another_group(self): + """we create a group.""" + name = 'Gruppe Neu' + group = Group.objects.create(name=name) + self.assertEqual(group.name, name) + self.assertEqual(Group.objects.all()[0], group) + + def test_add_person_to_group(self): + """We create a person and add her to the created group.""" + personname = 'Daniel' + person = Person.objects.create(name=personname) + name = 'Group One' + group = Group.objects.create(name=name) + person.groups.add(group) + person.save() + self.assertIn(person, group.people.all()) + + +class TestPersonModelTranslation(BasePeopleTest): + + def test_person_translatable(self): + person1 = self.reload(self.person1, 'en') + self.assertEqual( + person1.function, + self.data['person1']['en']['function'] + ) + person1 = self.reload(self.person1, 'de') + self.assertEqual( + person1.safe_translation_getter('function'), + self.data['person1']['de']['function'] + ) + + def test_comment(self): + person1 = self.reload(self.person1, 'en') + self.assertEqual( + person1.comment, + self.data['person1']['en']['description'] + ) + person1 = self.reload(self.person1, 'de') + self.assertEqual( + person1.comment, + self.data['person1']['de']['description'] + ) + + def test_get_vcard(self): + person1 = self.reload(self.person1, 'en') + # Test with no group + vcard_en = ('BEGIN:VCARD\r\n' + 'VERSION:3.0\r\n' + 'FN:person1\r\n' + 'N:;person1;;;\r\n' + 'TITLE:function1\r\n' + 'END:VCARD\r\n') + self.assertEqual( + person1.get_vcard().decode('utf-8'), + vcard_en + ) + # Test with a group and other fields populated + group1 = self.reload(self.group1, 'en') + group1.address = '123 Main Street' + group1.city = 'Anytown' + group1.postal_code = '12345' + group1.phone = '+1 (234) 567-8903' + group1.fax = '+1 (234) 567-8904' + group1.website = 'www.groupwebsite.com' + group1.save() + person1.groups.add(group1) + person1.email = 'person@org.org' + person1.phone = '+1 (234) 567-8900' + person1.mobile = '+1 (234) 567-8901' + person1.fax = '+1 (234) 567-8902' + person1.website = 'www.website.com' + person1.save() + vcard_en = ('BEGIN:VCARD\r\n' + 'VERSION:3.0\r\n' + 'FN:person1\r\n' + 'N:;person1;;;\r\n' + 'EMAIL:person@org.org\r\n' + 'TITLE:function1\r\n' + 'TEL;TYPE=WORK:+1 (234) 567-8900\r\n' + 'TEL;TYPE=CELL:+1 (234) 567-8901\r\n' + 'TEL;TYPE=FAX:+1 (234) 567-8902\r\n' + 'URL:www.website.com\r\n' + 'ORG:group1\r\n' + 'ADR;TYPE=WORK:;;123 Main Street;Anytown;;12345;\r\n' + 'TEL;TYPE=WORK:+1 (234) 567-8903\r\n' + 'TEL;TYPE=FAX:+1 (234) 567-8904\r\n' + 'URL:www.groupwebsite.com\r\n' + 'END:VCARD\r\n') + self.assertEqual( + person1.get_vcard().decode('utf-8'), + vcard_en + ) + # Ensure this works for other langs too + person1 = self.reload(self.person1, 'de') + vcard_de = ('BEGIN:VCARD\r\n' + 'VERSION:3.0\r\n' + 'FN:mensch1\r\n' + 'N:;mensch1;;;\r\n' + 'EMAIL:person@org.org\r\n' + 'TITLE:Funktion1\r\n' + 'TEL;TYPE=WORK:+1 (234) 567-8900\r\n' + 'TEL;TYPE=CELL:+1 (234) 567-8901\r\n' + 'TEL;TYPE=FAX:+1 (234) 567-8902\r\n' + 'URL:www.website.com\r\n' + 'ORG:Gruppe1\r\n' + 'ADR;TYPE=WORK:;;123 Main Street;Anytown;;12345;\r\n' + 'TEL;TYPE=WORK:+1 (234) 567-8903\r\n' + 'TEL;TYPE=FAX:+1 (234) 567-8904\r\n' + 'URL:www.groupwebsite.com\r\n' + 'END:VCARD\r\n') + with override('de'): + self.assertEqual( + person1.get_vcard().decode('utf-8'), + vcard_de + ) + + +class TestGroupModelTranslation(BasePeopleTest): + + def test_group_translation(self): + group1 = self.reload(self.group1, 'en') + self.assertEqual(group1.name, self.data['group1']['en']['name']) + group1 = self.reload(self.group1, 'de') + self.assertEqual(group1.name, self.data['group1']['de']['name']) + + def test_company_name(self): + group1 = self.reload(self.group1, 'en') + self.assertEqual( + group1.company_name, + self.data['group1']['en']['name'], + ) + group1 = self.reload(self.group1, 'de') + self.assertEqual( + group1.company_name, + self.data['group1']['de']['name'], + ) + + def test_company_description(self): + group1 = self.reload(self.group1, 'en') + self.assertEqual( + group1.company_description, + self.data['group1']['en']['description'], + ) + group1 = self.reload(self.group1, 'de') + self.assertEqual( + group1.company_description, + self.data['group1']['de']['description'], + ) + + def test_str(self): + group1 = self.reload(self.group1, 'en') + self.assertEqual( + force_text(group1), + self.data['group1']['en']['name'], + ) + group1 = self.reload(self.group1, 'de') + self.assertEqual( + force_text(group1), + self.data['group1']['de']['name'], + ) diff --git a/aldryn_people/tests/test_plugins.py b/aldryn_people/tests/test_plugins.py new file mode 100644 index 00000000..1017db9b --- /dev/null +++ b/aldryn_people/tests/test_plugins.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.urls import reverse +from django.utils.encoding import force_text + +from cms import api +from cms.utils.i18n import force_language + +from aldryn_people import DEFAULT_APP_NAMESPACE + +from . import BasePeopleTest, DefaultApphookMixin +from ..cms_plugins import PeoplePlugin +from ..models import Group, Person + + +class TestPersonPlugins(DefaultApphookMixin, BasePeopleTest): + + def test_add_people_list_plugin_api(self): + """ + We add a person to the People Plugin and look her up + """ + name = 'Donald' + Person.objects.create(name=name) + plugin = api.add_plugin(self.placeholder, PeoplePlugin, self.language) + plugin.people.set(Person.objects.all()) + self.assertEqual(force_text(plugin), force_text(plugin.pk)) + self.page.publish(self.language) + + url = self.page.get_absolute_url() + response = self.client.get(url, follow=True) + self.assertContains(response, name) + + # This fails because of Sane Add Plugin (I suspect). This will be refactored + # and re-enabled in a future commit. + # def test_add_people_list_plugin_client(self): + # """ + # We log into the PeoplePlugin + # """ + # self.client.login( + # username=self.su_username, password=self.su_password) + # + # plugin_data = { + # 'plugin_type': 'PeoplePlugin', + # 'plugin_language': self.language, + # 'placeholder_id': self.placeholder.pk, + # } + # response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data) + # self.assertEqual(response.status_code, 200) + # self.assertTrue(CMSPlugin.objects.exists()) + + def test_hide_ungrouped(self): + """ + """ + the_bradys = Group.objects.create(name="The Bradys") + alice = Person.objects.create(name="Alice") + bobby = Person.objects.create(name="Bobby") + cindy = Person.objects.create(name="Cindy") + # Alice is the housekeeper, not a real Brady. + bobby.groups.add(the_bradys) + cindy.groups.add(the_bradys) + + # Add a plugin where ungrouped people are not shown + plugin = api.add_plugin(self.placeholder, PeoplePlugin, self.language) + plugin.people.set(Person.objects.all()) + plugin.group_by_group = True + plugin.show_ungrouped = False + plugin.save() + + self.page.publish(self.language) + url = self.page.get_absolute_url() + response = self.client.get(url, follow=True) + self.assertContains(response, bobby.name) + self.assertContains(response, cindy.name) + self.assertNotContains(response, alice.name) + + def test_show_ungrouped(self): + """ + """ + the_bradys = Group.objects.create(name="The Bradys") + alice = Person.objects.create(name="Alice") + bobby = Person.objects.create(name="Bobby") + cindy = Person.objects.create(name="Cindy") + # Alice is the housekeeper, not a real Brady. + bobby.groups.add(the_bradys) + cindy.groups.add(the_bradys) + + # Now, add a new plugin where ungrouped people are shown + plugin = api.add_plugin(self.placeholder, PeoplePlugin, self.language) + plugin.people.set(Person.objects.all()) + plugin.group_by_group = True + plugin.show_ungrouped = True + plugin.save() + + self.page.publish(self.language) + url = self.page.get_absolute_url() + response = self.client.get(url, follow=True) + self.assertContains(response, bobby.name) + self.assertContains(response, cindy.name) + self.assertContains(response, alice.name) + + +class TestPeopleListPluginNoApphook(BasePeopleTest): + + def setUp(self): + super(TestPeopleListPluginNoApphook, self).setUp() + # we are testing only en + self.person1.set_current_language('en') + self.namespace = DEFAULT_APP_NAMESPACE + + def create_plugin(self, plugin_params=None): + if plugin_params is None: + plugin_params = {} + with force_language('en'): + plugin = api.add_plugin( + self.placeholder, PeoplePlugin, 'en', **plugin_params) + self.page.publish('en') + return plugin + + def test_plugin_with_no_apphook_doesnot_breaks_page(self): + self.create_plugin() + url = self.page.get_absolute_url() + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.person1.name) + from ..cms_plugins import NAMESPACE_ERROR + self.assertNotContains(response, NAMESPACE_ERROR[:20]) + + def test_plugin_with_no_apphook_shows_error_message(self): + self.create_plugin() + url = self.page.get_absolute_url() + self.client.login(username=self.su_username, + password=self.su_password) + response = self.client.get(url, user=self.superuser) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.person1.name) + from ..cms_plugins import NAMESPACE_ERROR + self.assertContains(response, NAMESPACE_ERROR[:20]) + + def test_plugin_with_vcard_enabled_no_apphook(self): + self.create_plugin(plugin_params={'show_vcard': True}) + url = self.page.get_absolute_url() + response = self.client.get(url) + self.assertContains(response, self.person1.name) + + def test_plugin_with_vcard_disabled_no_apphook(self): + self.create_plugin(plugin_params={'show_vcard': False}) + url = self.page.get_absolute_url() + response = self.client.get(url) + self.assertContains(response, self.person1.name) + + def test_plugin_show_links_are_shown_if_enabled_and_apphook_page(self): + with force_language('en'): + app_page = self.create_apphook_page() + list_plugin = api.add_plugin( + placeholder=self.placeholder, + plugin_type=PeoplePlugin, + language='en', + ) + list_plugin.show_links = True + list_plugin.save() + self.page.publish('en') + url = self.page.get_absolute_url() + person_url = self.person1.get_absolute_url() + # ensure that url is not the link to the home page and not app page + app_page_len = len(app_page.get_absolute_url()) + self.assertGreater(len(person_url), app_page_len) + response = self.client.get(url, follow=True) + self.assertContains(response, person_url) + # ensure that url is not shown if not enabled for plugin. + list_plugin.show_links = False + list_plugin.save() + self.page.publish('en') + response = self.client.get(url, follow=True) + self.assertNotContains(response, person_url) + + def test_plugin_with_vcard_enabled_with_apphook(self): + vcard_kwargs = { + 'slug': self.person1.slug + } + with force_language('en'): + self.create_apphook_page() + person_vcard_url = reverse( + '{0}:download_vcard'.format(self.namespace), + kwargs=vcard_kwargs) + plugin = self.create_plugin(plugin_params={'show_vcard': True}) + url = self.page.get_absolute_url() + response = self.client.get(url, follow=True) + self.assertContains(response, self.person1.name) + self.assertContains(response, person_vcard_url) + # test that vcard download link is not shown if disabled + plugin.show_vcard = False + plugin.save() + self.page.publish('en') + response = self.client.get(url, follow=True) + self.assertContains(response, self.person1.name) + self.assertNotContains(response, person_vcard_url) diff --git a/aldryn_people/tests/test_search_indexes.py b/aldryn_people/tests/test_search_indexes.py new file mode 100644 index 00000000..9d52ace4 --- /dev/null +++ b/aldryn_people/tests/test_search_indexes.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from aldryn_people.search_indexes import PeopleIndex + +from . import BasePeopleTest +from ..models import Person + + +class TestPeopleIndex(BasePeopleTest): + def test_get_title(self): + idx_obj = PeopleIndex() + person1 = self.reload(self.person1, "en") + self.assertEqual(idx_obj.get_title(person1), person1.name) + person1 = self.reload(self.person1, "de") + self.assertEqual(idx_obj.get_title(person1), person1.name) + + def test_get_index_kwargs(self): + # This is a silly test, but is here for completeness. + idx_obj = PeopleIndex() + self.assertEqual(idx_obj.get_index_kwargs("en"), { + 'translations__language_code': 'en' + }) + + def test_get_index_queryset(self): + idx_obj = PeopleIndex() + # Person2 does NOT have an EN translation, so should appear here. + self.assertEqualItems( + [q.id for q in idx_obj.get_index_queryset("en")], + [self.person1.id], + ) + # Both person objects have DE translations + self.assertEqualItems( + [q.id for q in idx_obj.get_index_queryset("de")], + [self.person1.id, self.person2.id], + ) + + def test_get_model(self): + # This is a silly test, but is here for completeness. + idx_obj = PeopleIndex() + self.assertEqual(idx_obj.get_model(), Person) + + def test_get_search_data(self): + idx_obj = PeopleIndex() + person1 = self.reload(self.person1, "en") + search_data = idx_obj.get_search_data(person1, "en", None) + self.assertEqual(search_data, self.data['person1']['en']['description']) diff --git a/aldryn_people/tests/test_toolbar.py b/aldryn_people/tests/test_toolbar.py new file mode 100644 index 00000000..4047b079 --- /dev/null +++ b/aldryn_people/tests/test_toolbar.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.test import TransactionTestCase + +from ..cms_toolbars import get_admin_url + + +class TestToolbarUtils(TransactionTestCase): + + def test_get_admin_url(self): + # With pattern args, but no URL parameters + change_action = 'auth_user_change' + args = [1, ] + kwargs = {} + change_url = get_admin_url(change_action, args, **kwargs) + + # With pattern args and a single URL parameter + kwargs = {'alpha': 'beta', } + url = get_admin_url(change_action, args, **kwargs) + self.assertIn(change_url + '?alpha=beta', url) + + # With pattern args and two URL parameters + kwargs = {'alpha': 'beta', 'gamma': 'delta', } + url = get_admin_url(change_action, args, **kwargs) + self.assertIn(change_url + '?alpha=beta&gamma=delta', url) + + # With pattern args and 3 URL parameters + kwargs = {'a': 'b', 'g': 'd', 'e': 'z', } + url = get_admin_url(change_action, args, **kwargs) + self.assertIn(change_url + '?a=b&e=z&g=d', url) + + # With pattern args and numerical URL params + kwargs = {'a': 1, 'g': 2, 'e': 3, } + url = get_admin_url(change_action, args, **kwargs) + self.assertIn(change_url + '?a=1&e=3&g=2', url) + + # With pattern args and odd-typed URL params + kwargs = {'a': [], 'g': {}, 'e': None} + url = get_admin_url(change_action, args, **kwargs) + self.assertIn(change_url + '?a=[]&e=None&g={}', url) + + # No pattern args and no kwargs + add_action = 'auth_user_add' + add_url = get_admin_url(add_action) + + # No pattern args... + add_action = 'auth_user_add' + kwargs = {'groups': 1, } + url = get_admin_url(add_action, **kwargs) + self.assertIn(add_url + '?groups=1', url) diff --git a/aldryn_people/tests/test_views.py b/aldryn_people/tests/test_views.py new file mode 100644 index 00000000..98fe3f92 --- /dev/null +++ b/aldryn_people/tests/test_views.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.http import Http404 +from django.test.client import RequestFactory +from django.urls import reverse + +from cms.utils.i18n import force_language + +from . import BasePeopleTest, CMSRequestBasedTest, DefaultApphookMixin +from ..views import DownloadVcardView + + +class TestDownloadVcardView(DefaultApphookMixin, + BasePeopleTest, + CMSRequestBasedTest): + + def test_as_view(self): + """Tests that DownloadVcardView produces the correct headers.""" + person1 = self.reload(self.person1, "en") + person1.slug = 'person1-slug' + kwargs = {"slug": person1.slug} + person1_url = reverse('aldryn_people:person-detail', kwargs=kwargs) + factory = RequestFactory() + request = factory.get(person1_url) + response = DownloadVcardView.as_view()(request, **kwargs) + filename = '{0}.vcf'.format(person1.name) + self.assertEqual( + response["Content-Disposition"], + 'attachment; filename="{0}"'.format(filename) + ) + # Now, disable vcards for this person, and re-test + person1.vcard_enabled = False + person1.save() + with self.assertRaises(Http404): + request = factory.get(person1_url) + response = DownloadVcardView.as_view()(request, **kwargs) + + +class TestMainListView(BasePeopleTest, CMSRequestBasedTest): + + def test_list_view_with_only_en_apphook(self): + page = self.create_apphook_page(multilang=False) + # give some time for apphook reload middleware + self.client.get(page.get_absolute_url()) + + self.set_defalut_person_objects_current_language('en') + with force_language('en'): + url = page.get_absolute_url() + person1_url = self.person1.get_absolute_url() + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.person1.name) + self.assertContains(response, person1_url) + # should not contain person 2 since page for 'de' language is + # not published + self.assertNotContains(response, self.person2.name) + self.assertNotContains(response, self.person2.slug) + + def test_list_view_with_en_and_de_apphook(self): + page = self.create_apphook_page(multilang=True) + # give some time for apphook reload middleware + self.client.get(page.get_absolute_url()) + self.set_defalut_person_objects_current_language('en') + with force_language('en'): + url = page.get_absolute_url() + person1_url = self.person1.get_absolute_url() + person2_url = self.person2.get_absolute_url() + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.person1.name) + self.assertContains(response, self.person2.name) + self.assertContains(response, person1_url) + self.assertContains(response, person2_url) From 30ea58d02e2af708324f78ec4c2da75e27a0311f Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:15:22 -0700 Subject: [PATCH 0650/1137] Update settings_local.py.template --- settings_local.py.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings_local.py.template b/settings_local.py.template index 388eea9c..89116afd 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -64,7 +64,7 @@ GOOGLE_API_CLIENT_CONFIG = { # GITHUB SETTINGS STATIC_SITE_REPO = "python-gsoc/python-gsoc.github.io" GITHUB_ACCESS_TOKEN = "" -GITHUB_FILE_PATH = {"deadlines.html": "deadlines.html", "index.html": "index.html"} +GITHUB_FILE_PATH = {"deadlines.html": "deadlines.html", "ideas.html": "ideas.html"} # memcached use django.core.cache.backends.memcached.PyLibMCCache CACHES = { From e283ec3177b896984676f60f0cbe8ed9a14c7ff3 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:16:00 -0700 Subject: [PATCH 0651/1137] Update deadlines.html --- gsoc/templates/site/deadlines.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index be8b8a6b..f29538d1 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -74,7 +74,7 @@ Started
  • Students
  • Mentors
  • -
  • Project Ideas
  • +
  • Project Ideas
  • Deadlines
  • FAQ
  • Blogs
  • From bbcb8b1b4266d2c4917d22db59c14102acd127d1 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:16:48 -0700 Subject: [PATCH 0652/1137] Update views.py --- suborg/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/suborg/views.py b/suborg/views.py index 4e6cc77f..0e34a100 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -83,14 +83,14 @@ def update_application(request, application_id): suborg_details.send_update_notification() s = Scheduler.objects.filter( command="update_site_template", - data=json.dumps({"template": "index.html"}), + data=json.dumps({"template": "ideas.html"}), success=None, ).all() if len(s) == 0: time = timezone.now() + timezone.timedelta(minutes=5) Scheduler.objects.create( command="update_site_template", - data=json.dumps({"template": "index.html"}), + data=json.dumps({"template": "ideas.html"}), activation_date=time, ) return redirect(reverse("suborg:post_register")) From bc823b6ebdc370122f6dcc96e7d8e311fd1a9eb1 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:17:52 -0700 Subject: [PATCH 0653/1137] Update commands.py --- gsoc/common/utils/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/common/utils/commands.py b/gsoc/common/utils/commands.py index bd1823f0..b593338c 100644 --- a/gsoc/common/utils/commands.py +++ b/gsoc/common/utils/commands.py @@ -136,7 +136,7 @@ def update_site_template(scheduler: Scheduler): timeline__gsoc_year=gsoc_year ).all(), } - elif template == "index.html": + elif template == "ideas.html": # change this if the number of contact fields increase contact_fields = ("chat", "mailing_list", "twitter_url", "blog_url", "homepage") suborgs = SubOrgDetails.objects.filter( From e58304396b21a17b4a340b592256b9cb4e95952b Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:18:46 -0700 Subject: [PATCH 0654/1137] Update models.py --- gsoc/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 2d7d5e44..f32cd3fc 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -361,14 +361,14 @@ def accept(self): s = Scheduler.objects.filter( command="update_site_template", - data=json.dumps({"template": "index.html"}), + data=json.dumps({"template": "ideas.html"}), success=None, ).all() if len(s) == 0: time = timezone.now() + timezone.timedelta(minutes=5) Scheduler.objects.create( command="update_site_template", - data=json.dumps({"template": "index.html"}), + data=json.dumps({"template": "ideas.html"}), activation_date=time, ) From 95ea58cc94fa4f61ff00a11cf3341deaf754f2c4 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:21:17 -0700 Subject: [PATCH 0655/1137] rename index to ideas --- gsoc/templates/site/{index.html => ideas.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gsoc/templates/site/{index.html => ideas.html} (100%) diff --git a/gsoc/templates/site/index.html b/gsoc/templates/site/ideas.html similarity index 100% rename from gsoc/templates/site/index.html rename to gsoc/templates/site/ideas.html From 9ed354a6ad0202b392a9c7813e816893c0afe408 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:31:42 -0700 Subject: [PATCH 0656/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index 0205c254..953ebbd5 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -41,7 +41,7 @@
  • Students
  • Mentors
  • -
  • Project Ideas
  • +
  • Project Ideas
  • Deadlines
  • FAQ
  • Blogs
  • From 0260d21e347b82a40c707c26fcc79ca5d777f2d1 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 09:34:35 -0700 Subject: [PATCH 0657/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 319 +-------------------------------- 1 file changed, 3 insertions(+), 316 deletions(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index 953ebbd5..e956a56e 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -51,287 +51,7 @@ -
    -
    -

    Python Summer of Code

    -

    - Students: get paid to work on open source projects! -

    -

    - Projects: find new contributors and mentor the next generation! -

    -
    -
    - -
    -
    -
    -

    What is it?

    -
    -
    -
    -

    - - Python -

    -

    - Python is a popular high-level programming language. It is a general-purpose language used - by - scientists, developers, and many others who want to work more quickly and integrate systems - more effectively. -

    -
    -
    -
    -

    - - Google Summer of Code -

    -

    - Google Summer of Code (GSoC) is a global program that offers post-secondary students an - opportunity to be paid for contributing to an open source project over a three month period. -

    -
    -
    -

    - The Python Software Foundation (PSF) is an organization devoted to advancing open source - technology related to the Python programming language. - Since 2005, the Python Software Foundation has participated in Google Summer of Code, serving - as an "umbrella organization" to a variety of Python-related projects, as well as sponsoring - projects related to the development of the Python language. Python provides mentors, Google - provides the program (and the money!), and students write code! -

    -
    -
    -
    -

    We're accepting new sub-orgs! Email gsoc-admins(at)python.org if interested. -

    -
    -
    -
    -
    - - -
    -

    How do I get started?

    -
    -
    -

    - - Choose an organization. -

    -

    - There's hundreds of thousands of projects that use Python, and you - need to narrow - down the list before you can get help or do much that's useful. - See How - do I choose a project or sub-org? for ideas - on how to do that. -

    Any open source experience will help you prepare for GSoC, - so don't worry too much about what project you try first and don't be afraid - to change your mind! When we know which sub-orgs will be participating, - they'll be listed with the project ideas. -

    -
    -
    -
    -

    - - Set up your own development environment. -

    -

    - Document what you do so you can remember it later, and so you can - help others if they get stuck! And if you get stuck, don't be afraid to ask - for help. -

    -
    -
    -
    -
    -

    - - Start communicating with the developers. -

    -

    - Join the mailing list, IRC channel, or any other communication - channels the developers use. Listen, get to know the people involved, and ask - questions.

    -
      -
    • Read first to see if your question has already been answered. - We get a lot of repeat questions! -
    • -
    • Communicate in public (not in private). Most open source work is done in the open, - so - demonstrate that you can do that! -
    • -
    - -
    -
    -
    -

    - - Try to fix a bug. -

    -

    - Many projects have these tagged as "easy" "bite-size" or - "beginner-friendly" -- do a search to see what comes up. Competition for the easiest - ones can be fierce, so don't be afraid to try something harder if you think - you might know what to do. -

    -

    - Can't find a bug? Other ideas: find typos and fix them. Improve test coverage by - writing new tests. Improve documentation. Use a tool like Pylint or Bandit to see - if you can find new issues. -

    -
    -
    -
    -

    - - Find bugs and report them. -

    -

    - Hopefully you won't encounter too many, but it's always a good idea to get familiar with - your - project's bug reporting process. -

    -
    -
    -
    -
    -

    - - Help with documentation. -

    -

    - As a beginner in your project, you're going to see things that are confusing that more - experienced developers may not notice. Take advantage of your beginner mindset and make - sure to - document anything you think is missing! -

    -
    -
    -
    -

    - - Help others. -

    -

    - Most projects are looking for not just coders, but good community members who people like to - work with. Show your community skills by helping others and make a great impression come - selection time! -

    -
    -
    -
    - - -
    -
    -
    -

    How to apply

    -

    Short application checklist:

    -
      -
    1. Read the links and instructions given on this site -- All of it! we've - tried - to give you all - the information you need to be an awesome student applicant. -
    2. Choose a sub-org (check the list here). Applications - not - associated with a sub-org typically get rejected. -
    3. Talk with your prospective mentors about what they expect of student - applicants and get help from them to refine your project ideas. Listening to - your mentors' recommendations is very important at this stage! -
    4. -
    5. Prepare a patch for that sub-org. Usually we expect students to fix a bug - and - have made a pull - request (or equivalent). Your code doesn't have to be - accepted and merged, but it does have to be visible to the public and it does have to be - your - own work - (mentor help is ok, code you didn't write is not). -
    6. -
    7. - Write your application (with help from your mentors!) - The 2019 application template is available here. - All applications must go through Google's application system; we can't - accept - any application - unless it is submitted there. -
        -
      • Use a descriptive title and include your sub-org name in Google's system. Good - example: - "Mailman: - Improve - archive search" Bad example: "My gsoc project" -
      • Make it easy for your mentors to give you feedback. If you're using Google docs, - enable comments and submit a "draft" (we can't see the "final" versions until - applications close). - If you're using a format that doesn't accept comments, make sure your email is on - the - document and don't forget to check for - feedback! -
      • -
      -
    8. -
    9. Submit your application to Google before the deadline. We actually - recommend you submit a few days early in case you have internet problems or - the system is down. Google does not extend this deadline, so it's best to be - prepared early! You can edit your application up until the system - closes. -
    10. -
    -
    -

    - - Tip -

    -

    Communication is probably the most - important part of the application process. Talk to the mentors and other - developers, listen when they give you advice, - and demonstrate that you've understood by incorporating their feedback into - what you're proposing. We reject a lot of students who haven't listened to mentor - feedback. If your mentors tell you that a project idea won't work for them, you're - probably not going to get accepted unless you change it. -

    -
    -
    -

    - - What goes in an application? -

    - An ideal application will contain 5 things: -
      -
    1. A descriptive title including the name of the sub-org - you - want to work with - (if this is missing, your application may be rejected!) -
    2. -
    3. Information about you, including contact information.
    4. -
    5. Link to a code contribution you have made to your organization. - (Usually this is a link to a pull request.) -
    6. -
    7. Information about your proposed project. This should be fairly - detailed - and include - a timeline. -
    8. -
    9. Information about other commitments that might affect your ability to - work - during the GSoC period. - (exams, classes, holidays, other jobs, weddings, etc.) We can work around a lot of - things, - but - it helps - to know in advance. -
    10. -
    -
    -
    -
    -
    +
    @@ -380,41 +100,8 @@

    -
    -
    -

    Friends of the PSF

    -

    Here's some more interesting organizations that use Python!

    -
      -
    • OpenAstronomy - an umbrella - organisation that includes open source projects used by researchers and engineers around the - world to better understand the universe
    • -
    • GNU Mailman - - mailing list management software
    • -
    -
    -
    - -
    -

    Other Stuff

    -
    -
    - -
    -
    -
    - + + From f060833a59c1fb89b4f95ce7a066902fb3b4a20b Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 24 Dec 2020 10:24:18 -0700 Subject: [PATCH 0658/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index e956a56e..a3626c14 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -90,7 +90,7 @@

    {{ c.0 }}

    {% endfor %} - From 6f2937b2a1a2b71ecd4c5ee77088317c2c1f9d81 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 24 Feb 2021 10:47:11 -0700 Subject: [PATCH 0659/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index a3626c14..cad8579a 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -100,8 +100,31 @@

    + + Friends of the PSF - +
    +
    + +
    +

    + TARDIS +

    +
    +
    +
    TARDIS is an open-source Monte Carlo radiative-transfer spectral synthesis code for 1D models of supernova ejecta. It is designed for rapid spectral modelling of supernovae. It is developed and maintained by a multi-disciplinary team iincluding software engineers, computer scientists, statisticians, and astrophysicists. +
    +
    +

    Contact Links

    +
    + +
    +
    +
    From f4f4fb81d1846295fd27fa806423ea5c02317f18 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 24 Feb 2021 10:53:14 -0700 Subject: [PATCH 0660/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index cad8579a..3d3dd3b8 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -99,7 +99,7 @@

    {% endfor %} - + Friends of the PSF @@ -125,6 +125,7 @@


    + From 95967750911f5f07812c1ec24af9fa6786cc9d73 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 24 Feb 2021 10:56:58 -0700 Subject: [PATCH 0661/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index 3d3dd3b8..fa61a0f0 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -100,31 +100,17 @@

    - - Friends of the PSF - -
    -
    - -
    -

    - TARDIS -

    -
    -
    -
    TARDIS is an open-source Monte Carlo radiative-transfer spectral synthesis code for 1D models of supernova ejecta. It is designed for rapid spectral modelling of supernovae. It is developed and maintained by a multi-disciplinary team iincluding software engineers, computer scientists, statisticians, and astrophysicists. -
    -
    -

    Contact Links

    -
    - + +
    +
    +

    Friends of the PSF

    +

    Here's some more interesting organizations that use Python!

    +
      +
    • TARDIS TARDIS is an open-source Monte Carlo radiative-transfer spectral synthesis code for 1D models of supernova ejecta. It is designed for rapid spectral modelling of supernovae. It is developed and maintained by a multi-disciplinary team iincluding software engineers, computer scientists, statisticians, and astrophysicists.
    • +
    -
    +
    From a50b8d11c3d3fb163d2e1d2a2ece3a46febdbda0 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 22 Mar 2021 11:26:33 -0600 Subject: [PATCH 0662/1137] show role and filters --- gsoc/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 9e733401..502f4f07 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -372,9 +372,10 @@ class HiddenUserProfileAdmin(admin.ModelAdmin): "hidden", "reminder_disabled", "current_blog_count", + "role", "gsoc_invited", ) - list_filter = ("hidden", "reminder_disabled") + list_filter = ("hidden", "reminder_disabled", "role", "gsoc_invited") readonly_fields = ( "user", "role", From 3c49fb44007abdca6d188bf8b15be0caf23d5996 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 22 Mar 2021 13:12:20 -0600 Subject: [PATCH 0663/1137] update django/cms and fix migrations --- gsoc/migrations/0001_initial.py | 312 +++++++++++++++--- gsoc/migrations/0002_scheduler.py | 30 -- gsoc/migrations/0003_auto_20190219_0320.py | 25 -- gsoc/migrations/0004_auto_20190219_1215.py | 21 -- gsoc/migrations/0005_auto_20190219_1253.py | 16 - gsoc/migrations/0006_auto_20190219_1301.py | 14 - gsoc/migrations/0007_auto_20190305_0853.py | 51 --- gsoc/migrations/0008_auto_20190322_0318.py | 61 ---- gsoc/migrations/0009_reglink.py | 34 -- gsoc/migrations/0010_auto_20190324_2216.py | 42 --- .../migrations/0011_userprofile_app_config.py | 28 -- gsoc/migrations/0012_auto_20190418_2214.py | 21 -- gsoc/migrations/0013_pagenotification.py | 55 --- gsoc/migrations/0014_auto_20190420_0140.py | 70 ---- gsoc/migrations/0015_auto_20190423_1031.py | 16 - gsoc/migrations/0016_comment.py | 58 ---- .../0017_userprofile_proposal_confirmed.py | 16 - gsoc/migrations/0018_auto_20190526_1519.py | 44 --- gsoc/migrations/0019_auto_20190529_1656.py | 115 ------- gsoc/migrations/0020_auto_20190529_1759.py | 22 -- gsoc/migrations/0021_auto_20190530_0622.py | 52 --- gsoc/migrations/0022_articlereview.py | 49 --- gsoc/migrations/0023_auto_20190604_1310.py | 14 - gsoc/migrations/0024_auto_20190607_0428.py | 41 --- gsoc/migrations/0025_auto_20190607_1004.py | 31 -- gsoc/migrations/0026_auto_20190608_0852.py | 41 --- gsoc/migrations/0027_auto_20190608_1421.py | 16 - gsoc/migrations/0028_blogpostduedate_title.py | 16 - gsoc/migrations/0029_suborgdetails.py | 150 --------- gsoc/migrations/0030_auto_20190614_0559.py | 41 --- gsoc/migrations/0031_auto_20190614_0630.py | 16 - gsoc/migrations/0032_auto_20190621_0347.py | 45 --- gsoc/migrations/0033_auto_20190623_0751.py | 16 - gsoc/migrations/0034_auto_20190623_1123.py | 21 -- gsoc/migrations/0035_auto_20190626_0437.py | 52 --- gsoc/migrations/0036_auto_20190628_0502.py | 21 -- gsoc/migrations/0037_auto_20190629_1627.py | 26 -- gsoc/migrations/0038_auto_20190702_0326.py | 26 -- gsoc/migrations/0039_gsocenddate.py | 33 -- gsoc/migrations/0040_auto_20190703_0617.py | 23 -- gsoc/migrations/0041_auto_20190707_0432.py | 21 -- gsoc/migrations/0042_auto_20190707_0505.py | 21 -- .../0043_blogpostduedate_category.py | 20 -- gsoc/migrations/0044_auto_20190716_0700.py | 27 -- gsoc/migrations/0045_auto_20190719_1851.py | 36 -- gsoc/migrations/0046_sendemail.py | 61 ---- .../0047_sendemail_activation_date.py | 16 - .../0048_reglink_send_notifications.py | 16 - gsoc/migrations/0049_blogposthistory.py | 38 --- gsoc/migrations/0050_auto_20190809_0529.py | 56 ---- gsoc/migrations/0051_auto_20190814_1454.py | 51 --- .../0052_userprofile_github_handle.py | 16 - gsoc/migrations/0053_auto_20190815_1130.py | 31 -- gsoc/migrations/0054_auto_20190817_1209.py | 33 -- gsoc/migrations/0055_auto_20200109_2053.py | 22 -- .../0056_userprofile_gsoc_invited.py | 18 - gsoc/migrations/0057_auto_20200427_0720.py | 17 - gsoc/migrations/0058_auto_20200701_0000.py | 90 ----- project.db | Bin 1859584 -> 1982464 bytes requirements.txt | 4 +- users.db | 0 61 files changed, 259 insertions(+), 2116 deletions(-) delete mode 100644 gsoc/migrations/0002_scheduler.py delete mode 100644 gsoc/migrations/0003_auto_20190219_0320.py delete mode 100644 gsoc/migrations/0004_auto_20190219_1215.py delete mode 100644 gsoc/migrations/0005_auto_20190219_1253.py delete mode 100644 gsoc/migrations/0006_auto_20190219_1301.py delete mode 100644 gsoc/migrations/0007_auto_20190305_0853.py delete mode 100644 gsoc/migrations/0008_auto_20190322_0318.py delete mode 100644 gsoc/migrations/0009_reglink.py delete mode 100644 gsoc/migrations/0010_auto_20190324_2216.py delete mode 100644 gsoc/migrations/0011_userprofile_app_config.py delete mode 100644 gsoc/migrations/0012_auto_20190418_2214.py delete mode 100644 gsoc/migrations/0013_pagenotification.py delete mode 100644 gsoc/migrations/0014_auto_20190420_0140.py delete mode 100644 gsoc/migrations/0015_auto_20190423_1031.py delete mode 100644 gsoc/migrations/0016_comment.py delete mode 100644 gsoc/migrations/0017_userprofile_proposal_confirmed.py delete mode 100644 gsoc/migrations/0018_auto_20190526_1519.py delete mode 100644 gsoc/migrations/0019_auto_20190529_1656.py delete mode 100644 gsoc/migrations/0020_auto_20190529_1759.py delete mode 100644 gsoc/migrations/0021_auto_20190530_0622.py delete mode 100644 gsoc/migrations/0022_articlereview.py delete mode 100644 gsoc/migrations/0023_auto_20190604_1310.py delete mode 100644 gsoc/migrations/0024_auto_20190607_0428.py delete mode 100644 gsoc/migrations/0025_auto_20190607_1004.py delete mode 100644 gsoc/migrations/0026_auto_20190608_0852.py delete mode 100644 gsoc/migrations/0027_auto_20190608_1421.py delete mode 100644 gsoc/migrations/0028_blogpostduedate_title.py delete mode 100644 gsoc/migrations/0029_suborgdetails.py delete mode 100644 gsoc/migrations/0030_auto_20190614_0559.py delete mode 100644 gsoc/migrations/0031_auto_20190614_0630.py delete mode 100644 gsoc/migrations/0032_auto_20190621_0347.py delete mode 100644 gsoc/migrations/0033_auto_20190623_0751.py delete mode 100644 gsoc/migrations/0034_auto_20190623_1123.py delete mode 100644 gsoc/migrations/0035_auto_20190626_0437.py delete mode 100644 gsoc/migrations/0036_auto_20190628_0502.py delete mode 100644 gsoc/migrations/0037_auto_20190629_1627.py delete mode 100644 gsoc/migrations/0038_auto_20190702_0326.py delete mode 100644 gsoc/migrations/0039_gsocenddate.py delete mode 100644 gsoc/migrations/0040_auto_20190703_0617.py delete mode 100644 gsoc/migrations/0041_auto_20190707_0432.py delete mode 100644 gsoc/migrations/0042_auto_20190707_0505.py delete mode 100644 gsoc/migrations/0043_blogpostduedate_category.py delete mode 100644 gsoc/migrations/0044_auto_20190716_0700.py delete mode 100644 gsoc/migrations/0045_auto_20190719_1851.py delete mode 100644 gsoc/migrations/0046_sendemail.py delete mode 100644 gsoc/migrations/0047_sendemail_activation_date.py delete mode 100644 gsoc/migrations/0048_reglink_send_notifications.py delete mode 100644 gsoc/migrations/0049_blogposthistory.py delete mode 100644 gsoc/migrations/0050_auto_20190809_0529.py delete mode 100644 gsoc/migrations/0051_auto_20190814_1454.py delete mode 100644 gsoc/migrations/0052_userprofile_github_handle.py delete mode 100644 gsoc/migrations/0053_auto_20190815_1130.py delete mode 100644 gsoc/migrations/0054_auto_20190817_1209.py delete mode 100644 gsoc/migrations/0055_auto_20200109_2053.py delete mode 100644 gsoc/migrations/0056_userprofile_gsoc_invited.py delete mode 100644 gsoc/migrations/0057_auto_20200427_0720.py delete mode 100644 gsoc/migrations/0058_auto_20200701_0000.py delete mode 100644 users.db diff --git a/gsoc/migrations/0001_initial.py b/gsoc/migrations/0001_initial.py index aca09ce7..68bf7601 100644 --- a/gsoc/migrations/0001_initial.py +++ b/gsoc/migrations/0001_initial.py @@ -1,71 +1,273 @@ -# Generated by Django 2.1.7 on 2019-02-19 21:48 +# Generated by Django 3.1.7 on 2021-03-22 13:09 +import aldryn_apphooks_config.fields from django.conf import settings +import django.core.validators from django.db import migrations, models import django.db.models.deletion +import gsoc.models class Migration(migrations.Migration): initial = True - dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] + dependencies = [ + ('cms', '0022_auto_20180620_1551'), + ('aldryn_newsblog', '0017_auto_20200624_0802'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] operations = [ migrations.CreateModel( - name="GsocYear", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("gsoc_year", models.IntegerField()), - ], - ), - migrations.CreateModel( - name="SubOrg", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("suborg_name", models.CharField(max_length=80)), - ], - ), - migrations.CreateModel( - name="UserProfile", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("gsoc_year", models.ManyToManyField(blank=True, to="gsoc.GsocYear")), - ( - "suborg_full_name", - models.ManyToManyField(blank=True, to="gsoc.SubOrg"), - ), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), + name='AddUserLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('log_id', models.CharField(default=gsoc.models.gen_uuid_str, max_length=36)), + ], + options={ + 'verbose_name': 'Add Users (The invites will be sent to the emails on save)', + 'verbose_name_plural': 'Add Users (The invites will be sent to the emails on save)', + }, + ), + migrations.CreateModel( + name='Builder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), ('build_remove_user_details', 'build_remove_user_details')], max_length=40)), + ('activation_date', models.DateTimeField(blank=True, null=True)), + ('built', models.BooleanField(default=None, null=True)), + ('data', models.TextField()), + ('last_error', models.TextField(blank=True, default=None, null=True)), + ], + ), + migrations.CreateModel( + name='GsocYear', + fields=[ + ('gsoc_year', models.IntegerField(primary_key=True, serialize=False)), + ], + options={ + 'ordering': ['-gsoc_year'], + }, + ), + migrations.CreateModel( + name='Scheduler', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('command', models.CharField(choices=[('send_email', 'send_email'), ('send_irc_msg', 'send_irc_msg'), ('revoke_student_permissions', 'revoke_student_permissions'), ('send_reg_reminder', 'send_reg_reminder'), ('add_blog_counter', 'add_blog_counter'), ('update_site_template', 'update_site_template'), ('archive_gsoc_pages', 'archive_gsoc_pages')], max_length=40)), + ('activation_date', models.DateTimeField(blank=True, null=True)), + ('data', models.TextField()), + ('success', models.BooleanField(null=True)), + ('last_error', models.TextField(blank=True, default=None, null=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='SubOrg', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('suborg_name', models.CharField(max_length=80)), + ], + options={ + 'ordering': ['suborg_name'], + }, + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role', models.IntegerField(choices=[(0, 'Others'), (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student')], default=0)), + ('accepted_proposal_pdf', models.FileField(blank=True, null=True, upload_to='proposals/')), + ('proposal_confirmed', models.BooleanField(default=False)), + ('hidden', models.BooleanField(default=False)), + ('reminder_disabled', models.BooleanField(default=False)), + ('current_blog_count', models.IntegerField(default=0)), + ('github_handle', models.TextField(blank=True, max_length=100, null=True)), + ('gsoc_invited', models.BooleanField(default=False)), + ('app_config', aldryn_apphooks_config.fields.AppHookConfigField(blank=True, help_text='When selecting a value, the form is reloaded to get the updated default', null=True, on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.newsblogconfig', verbose_name='Section')), + ('gsoc_year', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.gsocyear')), + ('suborg_full_name', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.suborg')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='UserDetails', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('deactivation_date', models.DateTimeField(blank=True, null=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'User details', + }, + ), + migrations.CreateModel( + name='Timeline', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('calendar_id', models.CharField(blank=True, max_length=255, null=True)), + ('gsoc_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gsoc.gsocyear')), + ], + ), + migrations.CreateModel( + name='SubOrgDetails', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reason_for_participation', models.TextField(blank=True, null=True, verbose_name='Why does your org want to participate in Google Summer of Code?')), + ('suborg_admin_email', models.EmailField(max_length=254, verbose_name='Suborg admin email')), + ('suborg_admin_2_email', models.EmailField(blank=True, help_text='Fill this if there are other suborg admins other than you', max_length=254, null=True, verbose_name='Suborg admin 2 email')), + ('suborg_admin_3_email', models.EmailField(blank=True, help_text='Fill this if there are other suborg admins other than you', max_length=254, null=True, verbose_name='Suborg admin 3 email')), + ('mentors_student_engagement', models.TextField(blank=True, null=True, verbose_name='How will you keep mentors engaged with their students?')), + ('students_on_schedule', models.TextField(blank=True, null=True, verbose_name='How will you help your students stay on schedule to complete their projects?')), + ('students_involvement_gsoc', models.TextField(blank=True, null=True, verbose_name='How will you get your students involved in your community during GSoC?')), + ('students_involvement_after', models.TextField(blank=True, null=True, verbose_name='How will you keep students involved with your community after GSoC?')), + ('past_gsoc_experience', models.BooleanField(help_text='Mark the checkbox for yes', verbose_name='Has your org been accepted as a mentor org in Google Summer of Code before?')), + ('suborg_in_past', models.BooleanField(help_text='Mark the checkbox for yes', verbose_name='Was this as a Suborg?')), + ('year_of_start', models.IntegerField(verbose_name='What year was your project started?')), + ('source_code', models.URLField(verbose_name='Where does your source code live?')), + ('docs', models.URLField(verbose_name='Please provide the URL that points to the repository, GitHub organization, or a web page that describes how to get your source code')), + ('anything_else', models.TextField(blank=True, null=True, verbose_name='Anything else we should know (optional)')), + ('suborg_name', models.CharField(blank=True, max_length=80, null=True, verbose_name='If applying for the first time enter the name of your suborg')), + ('description', models.TextField(verbose_name='A very short description of your organization')), + ('logo', models.ImageField(help_text='Must be a 24-bit PNG of 256 x 256 pixels.', upload_to='logos/', verbose_name='Your organization logo')), + ('primary_os_license', models.CharField(max_length=50, verbose_name='Primary Open Source License')), + ('ideas_list', models.URLField(verbose_name='Ideas List')), + ('chat', models.CharField(blank=True, max_length=80, null=True)), + ('mailing_list', models.CharField(blank=True, max_length=80, null=True)), + ('twitter_url', models.URLField(blank=True, null=True)), + ('blog_url', models.URLField(blank=True, null=True)), + ('homepage', models.URLField(blank=True, null=True, verbose_name='Homepage')), + ('last_message', models.TextField(blank=True, null=True)), + ('last_reviewed_at', models.DateTimeField(blank=True, null=True)), + ('created_at', models.DateTimeField(blank=True, null=True)), + ('updated_at', models.DateTimeField(blank=True, null=True)), + ('accepted', models.BooleanField(default=False)), + ('changed', models.BooleanField(default=None, null=True)), + ('applied_but_not_selected', models.ManyToManyField(blank=True, related_name='applied_not_selected', to='gsoc.GsocYear', verbose_name='If your org has applied for GSoC before but not been accepted, select the years')), + ('gsoc_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='suborg_details', to='gsoc.gsocyear')), + ('last_reviewed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('past_years', models.ManyToManyField(blank=True, to='gsoc.GsocYear', verbose_name='Which years did your org participate in GSoC?')), + ('suborg', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.suborg', verbose_name='Select your suborg, if you have applied before')), + ], + options={ + 'verbose_name_plural': 'Suborg Details', + }, + ), + migrations.CreateModel( + name='SendEmail', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('to', models.CharField(blank=True, help_text='Separate email with a comma', max_length=255, null=True)), + ('to_group', models.CharField(blank=True, choices=[('students', 'Students'), ('mentors', 'Mentors'), ('suborg_admins', 'Suborg Admins'), ('admins', 'Admins'), ('all', 'All')], max_length=80, null=True)), + ('subject', models.CharField(max_length=255)), + ('body', models.TextField()), + ('activation_date', models.DateTimeField(blank=True, null=True)), + ('scheduler', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.scheduler')), ], ), + migrations.CreateModel( + name='RegLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_used', models.BooleanField(default=False, editable=False)), + ('reglink_id', models.CharField(default=gsoc.models.gen_uuid_str, editable=False, max_length=36)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user_role', models.IntegerField(choices=[(0, 'Others'), (1, 'Suborg Admin'), (2, 'Mentor'), (3, 'Student')], default=0, null=True)), + ('email', models.CharField(default='', max_length=300, validators=[django.core.validators.EmailValidator()])), + ('send_notifications', models.BooleanField(default=True)), + ('adduserlog', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reglinks', to='gsoc.adduserlog')), + ('gsoc_year', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.gsocyear')), + ('reminder', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reglinks', to='gsoc.scheduler')), + ('scheduler', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.scheduler')), + ('user_suborg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.suborg')), + ], + ), + migrations.CreateModel( + name='ReaddUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.CharField(max_length=100)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='PageNotification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.TextField()), + ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='cms.page')), + ('published_page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications_for_published', to='cms.page')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='GsocEndDate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('timeline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='gsoc.timeline')), + ], + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100)), + ('start_date', models.DateField()), + ('end_date', models.DateField(blank=True, null=True)), + ('event_id', models.CharField(blank=True, max_length=255, null=True)), + ('timeline', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.timeline')), + ], + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('username', models.CharField(max_length=50)), + ('content', models.CharField(max_length=1100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.article')), + ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='gsoc.comment')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='BlogPostHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('content', models.TextField(blank=True, null=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.article')), + ], + ), + migrations.CreateModel( + name='BlogPostDueDate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(default='Weekly Blog Post Due', max_length=100)), + ('date', models.DateField()), + ('event_id', models.CharField(blank=True, max_length=255, null=True)), + ('category', models.IntegerField(blank=True, choices=[(0, 'Weekly Check-In'), (1, 'Blog Post')], null=True)), + ('add_counter_scheduler', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.scheduler')), + ('post_blog_reminder_builder', models.ManyToManyField(blank=True, to='gsoc.Builder')), + ('pre_blog_reminder_builder', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pre', to='gsoc.builder')), + ('timeline', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='gsoc.timeline')), + ], + options={ + 'ordering': ['date'], + }, + ), + migrations.CreateModel( + name='ArticleReview', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_reviewed', models.BooleanField(default=False)), + ('article', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.article')), + ('last_reviewed_by', models.ForeignKey(blank=True, limit_choices_to={'is_superuser': True}, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddConstraint( + model_name='userprofile', + constraint=models.UniqueConstraint(fields=('suborg_full_name', 'user', 'gsoc_year'), name='unique_draft_user'), + ), ] diff --git a/gsoc/migrations/0002_scheduler.py b/gsoc/migrations/0002_scheduler.py deleted file mode 100644 index 3799d03a..00000000 --- a/gsoc/migrations/0002_scheduler.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-19 07:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0001_initial")] - - operations = [ - migrations.CreateModel( - name="Scheduler", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ( - "command", - models.CharField( - choices=[ - ("send_emails", "send_emails"), - ("send_irc_msgs", "send_irc_msgs"), - ], - max_length=20, - ), - ), - ("data", models.CharField(max_length=1000)), - ("success", models.BooleanField(default=False)), - ("created", models.DateTimeField(auto_now_add=True)), - ], - ) - ] diff --git a/gsoc/migrations/0003_auto_20190219_0320.py b/gsoc/migrations/0003_auto_20190219_0320.py deleted file mode 100644 index 6a7151dd..00000000 --- a/gsoc/migrations/0003_auto_20190219_0320.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-19 11:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0002_scheduler")] - - operations = [ - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ], - max_length=20, - ), - ), - migrations.AlterField( - model_name="scheduler", name="data", field=models.TextField() - ), - ] diff --git a/gsoc/migrations/0004_auto_20190219_1215.py b/gsoc/migrations/0004_auto_20190219_1215.py deleted file mode 100644 index 33159211..00000000 --- a/gsoc/migrations/0004_auto_20190219_1215.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-19 20:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0003_auto_20190219_0320")] - - operations = [ - migrations.AddField( - model_name="scheduler", - name="last_error", - field=models.CharField(default=None, max_length=100, null=True), - ), - migrations.AlterField( - model_name="scheduler", - name="success", - field=models.BooleanField(default=None, null=True), - ), - ] diff --git a/gsoc/migrations/0005_auto_20190219_1253.py b/gsoc/migrations/0005_auto_20190219_1253.py deleted file mode 100644 index 4af7f68f..00000000 --- a/gsoc/migrations/0005_auto_20190219_1253.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-19 20:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0004_auto_20190219_1215")] - - operations = [ - migrations.AlterField( - model_name="scheduler", - name="last_error", - field=models.TextField(default=None, null=True), - ) - ] diff --git a/gsoc/migrations/0006_auto_20190219_1301.py b/gsoc/migrations/0006_auto_20190219_1301.py deleted file mode 100644 index fa1597a5..00000000 --- a/gsoc/migrations/0006_auto_20190219_1301.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-19 21:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0005_auto_20190219_1253")] - - operations = [ - migrations.AlterField( - model_name="scheduler", name="success", field=models.BooleanField(null=True) - ) - ] diff --git a/gsoc/migrations/0007_auto_20190305_0853.py b/gsoc/migrations/0007_auto_20190305_0853.py deleted file mode 100644 index 42d81a58..00000000 --- a/gsoc/migrations/0007_auto_20190305_0853.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-05 08:53 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0006_auto_20190219_1301")] - - operations = [ - migrations.AddField( - model_name="userprofile", - name="role", - field=models.IntegerField( - choices=[ - (0, "Others"), - (1, "Suborg Admin"), - (2, "Mentor"), - (3, "Student"), - ], - default=0, - ), - ), - migrations.RemoveField(model_name="userprofile", name="gsoc_year"), - migrations.AddField( - model_name="userprofile", - name="gsoc_year", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.GsocYear", - ), - ), - migrations.RemoveField(model_name="userprofile", name="suborg_full_name"), - migrations.AddField( - model_name="userprofile", - name="suborg_full_name", - field=models.ForeignKey( - null=True, on_delete=django.db.models.deletion.CASCADE, to="gsoc.SubOrg" - ), - ), - migrations.AlterField( - model_name="userprofile", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL - ), - ), - ] diff --git a/gsoc/migrations/0008_auto_20190322_0318.py b/gsoc/migrations/0008_auto_20190322_0318.py deleted file mode 100644 index 429d1a4a..00000000 --- a/gsoc/migrations/0008_auto_20190322_0318.py +++ /dev/null @@ -1,61 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-22 03:18 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("gsoc", "0007_auto_20190305_0853"), - ] - - operations = [ - migrations.CreateModel( - name="UserDetails", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("deactivation_date", models.DateTimeField(blank=True, null=True)), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={"verbose_name_plural": "User details"}, - ), - migrations.AddField( - model_name="scheduler", - name="activation_date", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="userprofile", - name="accepted_proposal_pdf", - field=models.FileField(blank=True, null=True, upload_to=""), - ), - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("deactivate_user", "deactivate_user"), - ], - max_length=20, - ), - ), - ] diff --git a/gsoc/migrations/0009_reglink.py b/gsoc/migrations/0009_reglink.py deleted file mode 100644 index 9fa18cc1..00000000 --- a/gsoc/migrations/0009_reglink.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-22 04:08 - -from django.db import migrations, models -import gsoc.models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0008_auto_20190322_0318")] - - operations = [ - migrations.CreateModel( - name="RegLink", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("is_used", models.BooleanField(default=False, editable=False)), - ( - "reglink_id", - models.CharField( - default=gsoc.models.gen_uuid_str, editable=False, max_length=36 - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ], - ) - ] diff --git a/gsoc/migrations/0010_auto_20190324_2216.py b/gsoc/migrations/0010_auto_20190324_2216.py deleted file mode 100644 index 59fe212c..00000000 --- a/gsoc/migrations/0010_auto_20190324_2216.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-24 22:16 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0009_reglink")] - - operations = [ - migrations.AddField( - model_name="reglink", - name="user_gsoc_year", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.GsocYear", - ), - ), - migrations.AddField( - model_name="reglink", - name="user_role", - field=models.IntegerField( - choices=[ - (0, "Others"), - (1, "Suborg Admin"), - (2, "Mentor"), - (3, "Student"), - ], - default=0, - null=True, - ), - ), - migrations.AddField( - model_name="reglink", - name="user_suborg", - field=models.ForeignKey( - null=True, on_delete=django.db.models.deletion.CASCADE, to="gsoc.SubOrg" - ), - ), - ] diff --git a/gsoc/migrations/0011_userprofile_app_config.py b/gsoc/migrations/0011_userprofile_app_config.py deleted file mode 100644 index 5751c418..00000000 --- a/gsoc/migrations/0011_userprofile_app_config.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-30 19:09 - -import aldryn_apphooks_config.fields -from django.db import migrations -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("aldryn_newsblog", "0016_auto_20180329_1417"), - ("gsoc", "0010_auto_20190324_2216"), - ] - - operations = [ - migrations.AddField( - model_name="userprofile", - name="app_config", - field=aldryn_apphooks_config.fields.AppHookConfigField( - blank=True, - help_text="When selecting a value, the form is reloaded to get the updated default", - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="aldryn_newsblog.NewsBlogConfig", - verbose_name="Section", - ), - ) - ] diff --git a/gsoc/migrations/0012_auto_20190418_2214.py b/gsoc/migrations/0012_auto_20190418_2214.py deleted file mode 100644 index 6bd71c6e..00000000 --- a/gsoc/migrations/0012_auto_20190418_2214.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.8 on 2019-04-18 22:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0011_userprofile_app_config")] - - operations = [ - migrations.AddField( - model_name="userprofile", - name="hidden", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="scheduler", - name="last_error", - field=models.TextField(blank=True, default=None, null=True), - ), - ] diff --git a/gsoc/migrations/0013_pagenotification.py b/gsoc/migrations/0013_pagenotification.py deleted file mode 100644 index 04b2eba6..00000000 --- a/gsoc/migrations/0013_pagenotification.py +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by Django 2.1.8 on 2019-04-18 22:40 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("cms", "0022_auto_20180620_1551"), - ("gsoc", "0012_auto_20190418_2214"), - ] - - operations = [ - migrations.CreateModel( - name="PageNotification", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("message", models.TextField()), - ( - "page", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="notifications", - to="cms.Page", - ), - ), - ( - "published_page", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="notifications_for_published", - to="cms.Page", - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ) - ] diff --git a/gsoc/migrations/0014_auto_20190420_0140.py b/gsoc/migrations/0014_auto_20190420_0140.py deleted file mode 100644 index 715a027f..00000000 --- a/gsoc/migrations/0014_auto_20190420_0140.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 2.1.8 on 2019-04-20 01:40 - -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import gsoc.models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0013_pagenotification")] - - operations = [ - migrations.CreateModel( - name="AddUserLog", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "log_id", - models.CharField(default=gsoc.models.gen_uuid_str, max_length=36), - ), - ], - options={ - "verbose_name": "Add Users(The invites will be sent to the emails on save)", - "verbose_name_plural": "Add Users(The invites will be sent to the emails on save)", - }, - ), - migrations.AlterModelOptions( - name="gsocyear", options={"ordering": ["-gsoc_year"]} - ), - migrations.AddField( - model_name="reglink", - name="email", - field=models.CharField( - default="", - max_length=300, - validators=[django.core.validators.EmailValidator()], - ), - ), - migrations.AddField( - model_name="reglink", - name="scheduler", - field=models.ForeignKey( - blank=True, - editable=False, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.Scheduler", - ), - ), - migrations.AddField( - model_name="reglink", - name="adduserlog", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="reglinks", - to="gsoc.AddUserLog", - ), - ), - ] diff --git a/gsoc/migrations/0015_auto_20190423_1031.py b/gsoc/migrations/0015_auto_20190423_1031.py deleted file mode 100644 index a97787c4..00000000 --- a/gsoc/migrations/0015_auto_20190423_1031.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.8 on 2019-04-23 10:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0014_auto_20190420_0140")] - - operations = [ - migrations.AlterField( - model_name="userprofile", - name="accepted_proposal_pdf", - field=models.FileField(blank=True, null=True, upload_to="proposals/"), - ) - ] diff --git a/gsoc/migrations/0016_comment.py b/gsoc/migrations/0016_comment.py deleted file mode 100644 index e4638c28..00000000 --- a/gsoc/migrations/0016_comment.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-08 06:44 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("aldryn_newsblog", "0016_auto_20180329_1417"), - ("gsoc", "0015_auto_20190423_1031"), - ] - - operations = [ - migrations.CreateModel( - name="Comment", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("username", models.CharField(max_length=50)), - ("content", models.TextField()), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "article", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="aldryn_newsblog.Article", - ), - ), - ( - "parent", - models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="replies", - to="gsoc.Comment", - ), - ), - ( - "user", - models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ) - ] diff --git a/gsoc/migrations/0017_userprofile_proposal_confirmed.py b/gsoc/migrations/0017_userprofile_proposal_confirmed.py deleted file mode 100644 index 7ae7b47a..00000000 --- a/gsoc/migrations/0017_userprofile_proposal_confirmed.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-17 05:57 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0016_comment")] - - operations = [ - migrations.AddField( - model_name="userprofile", - name="proposal_confirmed", - field=models.BooleanField(default=False), - ) - ] diff --git a/gsoc/migrations/0018_auto_20190526_1519.py b/gsoc/migrations/0018_auto_20190526_1519.py deleted file mode 100644 index bec93d14..00000000 --- a/gsoc/migrations/0018_auto_20190526_1519.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-26 15:19 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0017_userprofile_proposal_confirmed")] - - operations = [ - migrations.AlterModelOptions( - name="adduserlog", - options={ - "verbose_name": "Add Users (The invites will be sent to the emails on save)", - "verbose_name_plural": "Add Users (The invites will be sent to the emails on save)", - }, - ), - migrations.AddField( - model_name="reglink", - name="reminder", - field=models.ForeignKey( - blank=True, - editable=False, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="reglinks", - to="gsoc.Scheduler", - ), - ), - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("deactivate_user", "deactivate_user"), - ("send_reg_reminder", "send_reg_reminder"), - ], - max_length=20, - ), - ), - ] diff --git a/gsoc/migrations/0019_auto_20190529_1656.py b/gsoc/migrations/0019_auto_20190529_1656.py deleted file mode 100644 index ec4588ba..00000000 --- a/gsoc/migrations/0019_auto_20190529_1656.py +++ /dev/null @@ -1,115 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-29 16:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0018_auto_20190526_1519")] - - operations = [ - migrations.CreateModel( - name="BlogPostDueDate", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("title", models.CharField(max_length=255)), - ("date", models.DateTimeField()), - ], - ), - migrations.CreateModel( - name="Builder", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "category", - models.CharField( - choices=[ - ("build_pre_blog_reminders", "build_pre_blog_reminders"), - ("build_post_blog_reminders", "build_post_blog_reminders"), - ], - max_length=40, - ), - ), - ("activation_date", models.DateTimeField(blank=True, null=True)), - ("built", models.BooleanField(default=False)), - ("data", models.TextField()), - ], - ), - migrations.AlterModelOptions( - name="suborg", options={"ordering": ["suborg_name"]} - ), - migrations.AddField( - model_name="userprofile", - name="current_blog_count", - field=models.IntegerField(default=0), - ), - migrations.AddField( - model_name="userprofile", - name="reminder_disabled", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("deactivate_user", "deactivate_user"), - ("send_reg_reminder", "send_reg_reminder"), - ("add_blog_counter", "add_blog_counter"), - ], - max_length=40, - ), - ), - migrations.AddField( - model_name="blogpostduedate", - name="add_counter_scheduler", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.Scheduler", - ), - ), - migrations.AddField( - model_name="blogpostduedate", - name="gsoc_year", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="gsoc.GsocYear" - ), - ), - migrations.AddField( - model_name="blogpostduedate", - name="post_blog_reminder_builder", - field=models.ManyToManyField(blank=True, null=True, to="gsoc.Builder"), - ), - migrations.AddField( - model_name="blogpostduedate", - name="pre_blog_reminder_builder", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="pre", - to="gsoc.Builder", - ), - ), - ] diff --git a/gsoc/migrations/0020_auto_20190529_1759.py b/gsoc/migrations/0020_auto_20190529_1759.py deleted file mode 100644 index 1fa4c05a..00000000 --- a/gsoc/migrations/0020_auto_20190529_1759.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-29 17:59 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0019_auto_20190529_1656")] - - operations = [ - migrations.AlterField( - model_name="blogpostduedate", - name="gsoc_year", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.GsocYear", - ), - ) - ] diff --git a/gsoc/migrations/0021_auto_20190530_0622.py b/gsoc/migrations/0021_auto_20190530_0622.py deleted file mode 100644 index b5836536..00000000 --- a/gsoc/migrations/0021_auto_20190530_0622.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-30 06:22 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0020_auto_20190529_1759")] - - operations = [ - migrations.CreateModel( - name="Timeline", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "gsoc_year", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="gsoc.GsocYear" - ), - ), - ], - ), - migrations.RemoveField(model_name="blogpostduedate", name="gsoc_year"), - migrations.RemoveField(model_name="blogpostduedate", name="title"), - migrations.AlterField( - model_name="blogpostduedate", name="date", field=models.DateField() - ), - migrations.AlterField( - model_name="blogpostduedate", - name="post_blog_reminder_builder", - field=models.ManyToManyField(blank=True, to="gsoc.Builder"), - ), - migrations.AddField( - model_name="blogpostduedate", - name="timeline", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.Timeline", - ), - ), - ] diff --git a/gsoc/migrations/0022_articlereview.py b/gsoc/migrations/0022_articlereview.py deleted file mode 100644 index e8d91e93..00000000 --- a/gsoc/migrations/0022_articlereview.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 2.1.8 on 2019-05-30 19:34 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("aldryn_newsblog", "0016_auto_20180329_1417"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("gsoc", "0021_auto_20190530_0622"), - ] - - operations = [ - migrations.CreateModel( - name="ArticleReview", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("is_reviewed", models.BooleanField(default=False)), - ( - "article", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to="aldryn_newsblog.Article", - ), - ), - ( - "last_reviewed_by", - models.ForeignKey( - blank=True, - limit_choices_to={"is_superuser": True}, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ) - ] diff --git a/gsoc/migrations/0023_auto_20190604_1310.py b/gsoc/migrations/0023_auto_20190604_1310.py deleted file mode 100644 index 0954f052..00000000 --- a/gsoc/migrations/0023_auto_20190604_1310.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.1.8 on 2019-06-04 13:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0022_articlereview")] - - operations = [ - migrations.AlterField( - model_name="comment", name="content", field=models.CharField(max_length=255) - ) - ] diff --git a/gsoc/migrations/0024_auto_20190607_0428.py b/gsoc/migrations/0024_auto_20190607_0428.py deleted file mode 100644 index bf6eb69e..00000000 --- a/gsoc/migrations/0024_auto_20190607_0428.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-07 04:28 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0023_auto_20190604_1310")] - - operations = [ - migrations.CreateModel( - name="Event", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("title", models.CharField(max_length=100)), - ("start_date", models.DateField()), - ("end_date", models.DateField(blank=True, null=True)), - ( - "timeline", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.Timeline", - ), - ), - ], - ), - migrations.AlterModelOptions( - name="blogpostduedate", options={"ordering": ["date"]} - ), - ] diff --git a/gsoc/migrations/0025_auto_20190607_1004.py b/gsoc/migrations/0025_auto_20190607_1004.py deleted file mode 100644 index 03033baf..00000000 --- a/gsoc/migrations/0025_auto_20190607_1004.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-07 10:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0024_auto_20190607_0428")] - - operations = [ - migrations.AddField( - model_name="event", - name="link", - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("deactivate_user", "deactivate_user"), - ("send_reg_reminder", "send_reg_reminder"), - ("add_blog_counter", "add_blog_counter"), - ("add_calendar_event", "add_calendar_event"), - ], - max_length=40, - ), - ), - ] diff --git a/gsoc/migrations/0026_auto_20190608_0852.py b/gsoc/migrations/0026_auto_20190608_0852.py deleted file mode 100644 index 7e9fc96a..00000000 --- a/gsoc/migrations/0026_auto_20190608_0852.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-08 08:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0025_auto_20190607_1004")] - - operations = [ - migrations.RemoveField(model_name="event", name="link"), - migrations.AddField( - model_name="blogpostduedate", - name="event_id", - field=models.CharField(blank=True, max_length=255, null=True), - ), - migrations.AddField( - model_name="event", - name="event_id", - field=models.CharField(blank=True, max_length=255, null=True), - ), - migrations.AddField( - model_name="timeline", - name="calendar_id", - field=models.CharField(blank=True, max_length=255, null=True), - ), - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("deactivate_user", "deactivate_user"), - ("send_reg_reminder", "send_reg_reminder"), - ("add_blog_counter", "add_blog_counter"), - ], - max_length=40, - ), - ), - ] diff --git a/gsoc/migrations/0027_auto_20190608_1421.py b/gsoc/migrations/0027_auto_20190608_1421.py deleted file mode 100644 index 6f777108..00000000 --- a/gsoc/migrations/0027_auto_20190608_1421.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-08 14:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0026_auto_20190608_0852")] - - operations = [ - migrations.AlterField( - model_name="comment", - name="content", - field=models.CharField(max_length=1100), - ) - ] diff --git a/gsoc/migrations/0028_blogpostduedate_title.py b/gsoc/migrations/0028_blogpostduedate_title.py deleted file mode 100644 index f051be62..00000000 --- a/gsoc/migrations/0028_blogpostduedate_title.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-09 04:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0027_auto_20190608_1421")] - - operations = [ - migrations.AddField( - model_name="blogpostduedate", - name="title", - field=models.CharField(default="Weekly Blog Post Due", max_length=100), - ) - ] diff --git a/gsoc/migrations/0029_suborgdetails.py b/gsoc/migrations/0029_suborgdetails.py deleted file mode 100644 index f519a19e..00000000 --- a/gsoc/migrations/0029_suborgdetails.py +++ /dev/null @@ -1,150 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-13 11:08 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0028_blogpostduedate_title")] - - operations = [ - migrations.CreateModel( - name="SubOrgDetails", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "reason_for_participation", - models.TextField( - verbose_name="Why does your org want to participate in Google Summer of Code?" - ), - ), - ( - "suborg_admin_email", - models.EmailField( - max_length=254, verbose_name="Suborg admin email" - ), - ), - ( - "mentors_student_engagement", - models.TextField( - verbose_name="How will you keep mentors engaged with their students?" - ), - ), - ( - "students_on_schedule", - models.TextField( - verbose_name="How will you help your students stay on schedule to complete their projects?" - ), - ), - ( - "students_involvement_gsoc", - models.TextField( - verbose_name="How will you get your students involved in your community during GSoC?" - ), - ), - ( - "students_involvement_after", - models.TextField( - verbose_name="How will you keep students involved with your community after GSoC?" - ), - ), - ( - "past_gsoc_experience", - models.BooleanField( - verbose_name="Has your org been accepted as a mentor org in Google Summer of Code before?" - ), - ), - ( - "suborg_in_past", - models.BooleanField(verbose_name="Was this as a Suborg?"), - ), - ( - "year_of_start", - models.IntegerField( - verbose_name="What year was your project started?" - ), - ), - ( - "source_code", - models.URLField(verbose_name="Where does your source code live?"), - ), - ( - "docs", - models.URLField( - verbose_name="Please provide the URL that points to the repository, GitHub organization, or a web page that describes how to get your source code" - ), - ), - ( - "anything_else", - models.TextField( - blank=True, - null=True, - verbose_name="Anything else we should know (optional)", - ), - ), - ("suborg_name", models.CharField(max_length=80, verbose_name="Name")), - ( - "description", - models.TextField( - verbose_name="A very short description of your organization" - ), - ), - ( - "logo", - models.ImageField( - help_text="Must be a 24-bit PNG, minimum height 256 pixels.", - upload_to="logos/", - verbose_name="Your organization logo", - ), - ), - ( - "primary_os_license", - models.CharField( - max_length=50, verbose_name="Primary Open Source License" - ), - ), - ( - "ideas_list", - models.TextField( - help_text="Write the ideas one by one, separated with newlines.", - verbose_name="Ideas List", - ), - ), - ( - "applied_but_not_selected", - models.ManyToManyField( - null=True, - related_name="applied_not_selected", - to="gsoc.GsocYear", - verbose_name="If your org has applied for GSoC before but not been accepted, select the years", - ), - ), - ( - "gsoc_year", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="suborg_details", - to="gsoc.GsocYear", - ), - ), - ( - "past_years", - models.ManyToManyField( - null=True, - to="gsoc.GsocYear", - verbose_name="Which years did your org participate in GSoC?", - ), - ), - ], - options={"verbose_name_plural": "Suborg Details"}, - ) - ] diff --git a/gsoc/migrations/0030_auto_20190614_0559.py b/gsoc/migrations/0030_auto_20190614_0559.py deleted file mode 100644 index a1d87a23..00000000 --- a/gsoc/migrations/0030_auto_20190614_0559.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-14 05:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0029_suborgdetails")] - - operations = [ - migrations.AddField( - model_name="suborgdetails", - name="accepted", - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name="suborgdetails", - name="blog_url", - field=models.URLField(blank=True, null=True), - ), - migrations.AddField( - model_name="suborgdetails", - name="chat", - field=models.URLField(blank=True, null=True), - ), - migrations.AddField( - model_name="suborgdetails", - name="link", - field=models.URLField(blank=True, null=True, verbose_name="Any other link"), - ), - migrations.AddField( - model_name="suborgdetails", - name="mailing_list", - field=models.EmailField(blank=True, max_length=254, null=True), - ), - migrations.AddField( - model_name="suborgdetails", - name="twitter_url", - field=models.URLField(blank=True, null=True), - ), - ] diff --git a/gsoc/migrations/0031_auto_20190614_0630.py b/gsoc/migrations/0031_auto_20190614_0630.py deleted file mode 100644 index 44395164..00000000 --- a/gsoc/migrations/0031_auto_20190614_0630.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-14 06:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0030_auto_20190614_0559")] - - operations = [ - migrations.AlterField( - model_name="suborgdetails", - name="accepted", - field=models.BooleanField(default=None, null=True), - ) - ] diff --git a/gsoc/migrations/0032_auto_20190621_0347.py b/gsoc/migrations/0032_auto_20190621_0347.py deleted file mode 100644 index 5d0c474d..00000000 --- a/gsoc/migrations/0032_auto_20190621_0347.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-21 03:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0031_auto_20190614_0630")] - - operations = [ - migrations.AddField( - model_name="suborgdetails", - name="changed", - field=models.BooleanField(default=None, null=True), - ), - migrations.AddField( - model_name="suborgdetails", - name="last_message", - field=models.TextField(blank=True, null=True), - ), - migrations.AlterField( - model_name="suborgdetails", - name="accepted", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="suborgdetails", - name="applied_but_not_selected", - field=models.ManyToManyField( - blank=True, - related_name="applied_not_selected", - to="gsoc.GsocYear", - verbose_name="If your org has applied for GSoC before but not been accepted, select the years", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="past_years", - field=models.ManyToManyField( - blank=True, - to="gsoc.GsocYear", - verbose_name="Which years did your org participate in GSoC?", - ), - ), - ] diff --git a/gsoc/migrations/0033_auto_20190623_0751.py b/gsoc/migrations/0033_auto_20190623_0751.py deleted file mode 100644 index 9d47a98d..00000000 --- a/gsoc/migrations/0033_auto_20190623_0751.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-23 07:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0032_auto_20190621_0347")] - - operations = [ - migrations.AlterField( - model_name="suborgdetails", - name="ideas_list", - field=models.URLField(verbose_name="Ideas List"), - ) - ] diff --git a/gsoc/migrations/0034_auto_20190623_1123.py b/gsoc/migrations/0034_auto_20190623_1123.py deleted file mode 100644 index 973e0982..00000000 --- a/gsoc/migrations/0034_auto_20190623_1123.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-23 11:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0033_auto_20190623_0751")] - - operations = [ - migrations.AlterField( - model_name="suborgdetails", - name="chat", - field=models.CharField(blank=True, max_length=80, null=True), - ), - migrations.AlterField( - model_name="suborgdetails", - name="mailing_list", - field=models.CharField(blank=True, max_length=80, null=True), - ), - ] diff --git a/gsoc/migrations/0035_auto_20190626_0437.py b/gsoc/migrations/0035_auto_20190626_0437.py deleted file mode 100644 index 0f9827b0..00000000 --- a/gsoc/migrations/0035_auto_20190626_0437.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-26 04:37 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("gsoc", "0034_auto_20190623_1123"), - ] - - operations = [ - migrations.AddField( - model_name="suborgdetails", - name="last_updated_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="suborgdetails", - name="last_updated_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="suborgdetails", - name="suborg", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.SubOrg", - verbose_name="Select your suborg, if you have applied before", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="suborg_name", - field=models.CharField( - blank=True, - max_length=80, - null=True, - verbose_name="If applying for the first time enter the name of your suborg", - ), - ), - ] diff --git a/gsoc/migrations/0036_auto_20190628_0502.py b/gsoc/migrations/0036_auto_20190628_0502.py deleted file mode 100644 index 1b753845..00000000 --- a/gsoc/migrations/0036_auto_20190628_0502.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-28 05:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0035_auto_20190626_0437")] - - operations = [ - migrations.AddField( - model_name="builder", - name="last_error", - field=models.TextField(blank=True, default=None, null=True), - ), - migrations.AlterField( - model_name="builder", - name="built", - field=models.BooleanField(default=None, null=True), - ), - ] diff --git a/gsoc/migrations/0037_auto_20190629_1627.py b/gsoc/migrations/0037_auto_20190629_1627.py deleted file mode 100644 index fb5e7f9e..00000000 --- a/gsoc/migrations/0037_auto_20190629_1627.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 2.1.9 on 2019-06-29 16:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0036_auto_20190628_0502")] - - operations = [ - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("deactivate_user", "deactivate_user"), - ("send_reg_reminder", "send_reg_reminder"), - ("add_blog_counter", "add_blog_counter"), - ("update_site_template", "update_site_template"), - ], - max_length=40, - ), - ) - ] diff --git a/gsoc/migrations/0038_auto_20190702_0326.py b/gsoc/migrations/0038_auto_20190702_0326.py deleted file mode 100644 index 2ab11d51..00000000 --- a/gsoc/migrations/0038_auto_20190702_0326.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 2.1.9 on 2019-07-02 03:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0037_auto_20190629_1627")] - - operations = [ - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("revoke_student_permissions", "revoke_student_permissions"), - ("send_reg_reminder", "send_reg_reminder"), - ("add_blog_counter", "add_blog_counter"), - ("update_site_template", "update_site_template"), - ], - max_length=40, - ), - ) - ] diff --git a/gsoc/migrations/0039_gsocenddate.py b/gsoc/migrations/0039_gsocenddate.py deleted file mode 100644 index 84e269d5..00000000 --- a/gsoc/migrations/0039_gsocenddate.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.1.9 on 2019-07-03 05:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0038_auto_20190702_0326")] - - operations = [ - migrations.CreateModel( - name="GsocEndDate", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("date", models.DateField()), - ( - "timeline", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, to="gsoc.Timeline" - ), - ), - ], - ) - ] diff --git a/gsoc/migrations/0040_auto_20190703_0617.py b/gsoc/migrations/0040_auto_20190703_0617.py deleted file mode 100644 index 3325b79b..00000000 --- a/gsoc/migrations/0040_auto_20190703_0617.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.1.9 on 2019-07-03 06:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0039_gsocenddate")] - - operations = [ - migrations.AlterField( - model_name="builder", - name="category", - field=models.CharField( - choices=[ - ("build_pre_blog_reminders", "build_pre_blog_reminders"), - ("build_post_blog_reminders", "build_post_blog_reminders"), - ("build_revoke_student_perms", "build_revoke_student_perms"), - ], - max_length=40, - ), - ) - ] diff --git a/gsoc/migrations/0041_auto_20190707_0432.py b/gsoc/migrations/0041_auto_20190707_0432.py deleted file mode 100644 index 8afa97b3..00000000 --- a/gsoc/migrations/0041_auto_20190707_0432.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.9 on 2019-07-07 04:32 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0040_auto_20190703_0617")] - - operations = [ - migrations.RenameField( - model_name="suborgdetails", - old_name="last_updated_at", - new_name="last_reviewed_at", - ), - migrations.RenameField( - model_name="suborgdetails", - old_name="last_updated_by", - new_name="last_reviewed_by", - ), - ] diff --git a/gsoc/migrations/0042_auto_20190707_0505.py b/gsoc/migrations/0042_auto_20190707_0505.py deleted file mode 100644 index d4a76aef..00000000 --- a/gsoc/migrations/0042_auto_20190707_0505.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.9 on 2019-07-07 05:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0041_auto_20190707_0432")] - - operations = [ - migrations.AddField( - model_name="suborgdetails", - name="created_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="suborgdetails", - name="updated_at", - field=models.DateTimeField(blank=True, null=True), - ), - ] diff --git a/gsoc/migrations/0043_blogpostduedate_category.py b/gsoc/migrations/0043_blogpostduedate_category.py deleted file mode 100644 index 40a8b990..00000000 --- a/gsoc/migrations/0043_blogpostduedate_category.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.9 on 2019-07-07 07:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0042_auto_20190707_0505")] - - operations = [ - migrations.AddField( - model_name="blogpostduedate", - name="category", - field=models.IntegerField( - blank=True, - choices=[(0, "Weekly Check-In"), (1, "Blog Post")], - null=True, - ), - ) - ] diff --git a/gsoc/migrations/0044_auto_20190716_0700.py b/gsoc/migrations/0044_auto_20190716_0700.py deleted file mode 100644 index b1f6e725..00000000 --- a/gsoc/migrations/0044_auto_20190716_0700.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.1.10 on 2019-07-16 07:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0043_blogpostduedate_category")] - - operations = [ - migrations.AlterField( - model_name="scheduler", - name="command", - field=models.CharField( - choices=[ - ("send_email", "send_email"), - ("send_irc_msg", "send_irc_msg"), - ("revoke_student_permissions", "revoke_student_permissions"), - ("send_reg_reminder", "send_reg_reminder"), - ("add_blog_counter", "add_blog_counter"), - ("update_site_template", "update_site_template"), - ("archive_gsoc_pages", "archive_gsoc_pages"), - ], - max_length=40, - ), - ) - ] diff --git a/gsoc/migrations/0045_auto_20190719_1851.py b/gsoc/migrations/0045_auto_20190719_1851.py deleted file mode 100644 index 1a40a97f..00000000 --- a/gsoc/migrations/0045_auto_20190719_1851.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 2.1.10 on 2019-07-19 18:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0044_auto_20190716_0700")] - - operations = [ - migrations.AlterField( - model_name="suborgdetails", - name="logo", - field=models.ImageField( - help_text="Must be a 24-bit PNG of 256 x 256 pixels.", - upload_to="logos/", - verbose_name="Your organization logo", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="past_gsoc_experience", - field=models.BooleanField( - help_text="Mark the checkbox for yes", - verbose_name="Has your org been accepted as a mentor org in Google Summer of Code before?", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="suborg_in_past", - field=models.BooleanField( - help_text="Mark the checkbox for yes", - verbose_name="Was this as a Suborg?", - ), - ), - ] diff --git a/gsoc/migrations/0046_sendemail.py b/gsoc/migrations/0046_sendemail.py deleted file mode 100644 index 27046783..00000000 --- a/gsoc/migrations/0046_sendemail.py +++ /dev/null @@ -1,61 +0,0 @@ -# Generated by Django 2.1.10 on 2019-07-20 02:15 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0045_auto_20190719_1851")] - - operations = [ - migrations.CreateModel( - name="SendEmail", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "to", - models.CharField( - blank=True, - help_text="Separate email with a comma", - max_length=255, - null=True, - ), - ), - ( - "to_group", - models.CharField( - blank=True, - choices=[ - ("students", "Students"), - ("mentors", "Mentors"), - ("suborg_admins", "Suborg Admins"), - ("admins", "Admins"), - ("all", "All"), - ], - max_length=80, - null=True, - ), - ), - ("subject", models.CharField(max_length=255)), - ("body", models.TextField()), - ( - "scheduler", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gsoc.Scheduler", - ), - ), - ], - ) - ] diff --git a/gsoc/migrations/0047_sendemail_activation_date.py b/gsoc/migrations/0047_sendemail_activation_date.py deleted file mode 100644 index 5f1658df..00000000 --- a/gsoc/migrations/0047_sendemail_activation_date.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.10 on 2019-07-21 02:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0046_sendemail")] - - operations = [ - migrations.AddField( - model_name="sendemail", - name="activation_date", - field=models.DateTimeField(blank=True, null=True), - ) - ] diff --git a/gsoc/migrations/0048_reglink_send_notifications.py b/gsoc/migrations/0048_reglink_send_notifications.py deleted file mode 100644 index af4ab14b..00000000 --- a/gsoc/migrations/0048_reglink_send_notifications.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.10 on 2019-07-21 06:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0047_sendemail_activation_date")] - - operations = [ - migrations.AddField( - model_name="reglink", - name="send_notifications", - field=models.BooleanField(default=True), - ) - ] diff --git a/gsoc/migrations/0049_blogposthistory.py b/gsoc/migrations/0049_blogposthistory.py deleted file mode 100644 index 3b357efe..00000000 --- a/gsoc/migrations/0049_blogposthistory.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.1.10 on 2019-07-28 09:57 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("aldryn_newsblog", "0016_auto_20180329_1417"), - ("gsoc", "0048_reglink_send_notifications"), - ] - - operations = [ - migrations.CreateModel( - name="BlogPostHistory", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("timestamp", models.DateTimeField(auto_now_add=True)), - ("content", models.TextField(blank=True, null=True)), - ( - "article", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="aldryn_newsblog.Article", - ), - ), - ], - ) - ] diff --git a/gsoc/migrations/0050_auto_20190809_0529.py b/gsoc/migrations/0050_auto_20190809_0529.py deleted file mode 100644 index 9c86f20a..00000000 --- a/gsoc/migrations/0050_auto_20190809_0529.py +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by Django 2.1.10 on 2019-08-09 05:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0049_blogposthistory")] - - operations = [ - migrations.AlterField( - model_name="suborgdetails", - name="mentors_student_engagement", - field=models.TextField( - blank=True, - null=True, - verbose_name="How will you keep mentors engaged with their students?", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="reason_for_participation", - field=models.TextField( - blank=True, - null=True, - verbose_name="Why does your org want to participate in Google Summer of Code?", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="students_involvement_after", - field=models.TextField( - blank=True, - null=True, - verbose_name="How will you keep students involved with your community after GSoC?", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="students_involvement_gsoc", - field=models.TextField( - blank=True, - null=True, - verbose_name="How will you get your students involved in your community during GSoC?", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="students_on_schedule", - field=models.TextField( - blank=True, - null=True, - verbose_name="How will you help your students stay on schedule to complete their projects?", - ), - ), - ] diff --git a/gsoc/migrations/0051_auto_20190814_1454.py b/gsoc/migrations/0051_auto_20190814_1454.py deleted file mode 100644 index 09c4081b..00000000 --- a/gsoc/migrations/0051_auto_20190814_1454.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 2.1.10 on 2019-08-14 14:54 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("gsoc", "0050_auto_20190809_0529"), - ] - - operations = [ - migrations.CreateModel( - name="ReaddUser", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("uuid", models.CharField(max_length=100)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - migrations.AlterField( - model_name="builder", - name="category", - field=models.CharField( - choices=[ - ("build_pre_blog_reminders", "build_pre_blog_reminders"), - ("build_post_blog_reminders", "build_post_blog_reminders"), - ("build_revoke_student_perms", "build_revoke_student_perms"), - ("build_remove_user_details", "build_remove_user_details"), - ], - max_length=40, - ), - ), - ] diff --git a/gsoc/migrations/0052_userprofile_github_handle.py b/gsoc/migrations/0052_userprofile_github_handle.py deleted file mode 100644 index a992b2af..00000000 --- a/gsoc/migrations/0052_userprofile_github_handle.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.1.10 on 2019-08-15 10:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0051_auto_20190814_1454")] - - operations = [ - migrations.AddField( - model_name="userprofile", - name="github_handle", - field=models.TextField(blank=True, max_length=100, null=True), - ) - ] diff --git a/gsoc/migrations/0053_auto_20190815_1130.py b/gsoc/migrations/0053_auto_20190815_1130.py deleted file mode 100644 index 9baa11b0..00000000 --- a/gsoc/migrations/0053_auto_20190815_1130.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.1.10 on 2019-08-15 11:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0052_userprofile_github_handle")] - - operations = [ - migrations.AddField( - model_name="suborgdetails", - name="suborg_admin_2_email", - field=models.EmailField( - blank=True, - max_length=254, - null=True, - verbose_name="Suborg admin 2 email", - ), - ), - migrations.AddField( - model_name="suborgdetails", - name="suborg_admin_3_email", - field=models.EmailField( - blank=True, - max_length=254, - null=True, - verbose_name="Suborg admin 3 email", - ), - ), - ] diff --git a/gsoc/migrations/0054_auto_20190817_1209.py b/gsoc/migrations/0054_auto_20190817_1209.py deleted file mode 100644 index c2212071..00000000 --- a/gsoc/migrations/0054_auto_20190817_1209.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.1.11 on 2019-08-17 12:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("gsoc", "0053_auto_20190815_1130")] - - operations = [ - migrations.AlterField( - model_name="suborgdetails", - name="suborg_admin_2_email", - field=models.EmailField( - blank=True, - help_text="Fill this if there are other suborg admins other than you", - max_length=254, - null=True, - verbose_name="Suborg admin 2 email", - ), - ), - migrations.AlterField( - model_name="suborgdetails", - name="suborg_admin_3_email", - field=models.EmailField( - blank=True, - help_text="Fill this if there are other suborg admins other than you", - max_length=254, - null=True, - verbose_name="Suborg admin 3 email", - ), - ), - ] diff --git a/gsoc/migrations/0055_auto_20200109_2053.py b/gsoc/migrations/0055_auto_20200109_2053.py deleted file mode 100644 index 930c1781..00000000 --- a/gsoc/migrations/0055_auto_20200109_2053.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-09 20:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gsoc', '0054_auto_20190817_1209'), - ] - - operations = [ - migrations.RemoveField( - model_name='suborgdetails', - name='link', - ), - migrations.AddField( - model_name='suborgdetails', - name='homepage', - field=models.URLField(blank=True, null=True, verbose_name='Homepage'), - ), - ] diff --git a/gsoc/migrations/0056_userprofile_gsoc_invited.py b/gsoc/migrations/0056_userprofile_gsoc_invited.py deleted file mode 100644 index 52188c14..00000000 --- a/gsoc/migrations/0056_userprofile_gsoc_invited.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-04-27 06:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gsoc', '0055_auto_20200109_2053'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='gsoc_invited', - field=models.BooleanField(default=False), - ), - ] diff --git a/gsoc/migrations/0057_auto_20200427_0720.py b/gsoc/migrations/0057_auto_20200427_0720.py deleted file mode 100644 index 15a0ad58..00000000 --- a/gsoc/migrations/0057_auto_20200427_0720.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.12 on 2020-04-27 07:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gsoc', '0056_userprofile_gsoc_invited'), - ] - - operations = [ - migrations.AddConstraint( - model_name='userprofile', - constraint=models.UniqueConstraint(fields=('suborg_full_name', 'user', 'gsoc_year'), name='unique_draft_user'), - ), - ] diff --git a/gsoc/migrations/0058_auto_20200701_0000.py b/gsoc/migrations/0058_auto_20200701_0000.py deleted file mode 100644 index 48e8af0a..00000000 --- a/gsoc/migrations/0058_auto_20200701_0000.py +++ /dev/null @@ -1,90 +0,0 @@ -# Generated by Django 3.0.7 on 2020-07-01 00:00 - -from django.db import migrations, models -import django.db.models.deletion - -def update_keys(apps, schema_editor): - Years = apps.get_model('gsoc', 'gsocyear') - mapping = {y.id:y.gsoc_year for y in Years.objects.all()} - - # First, create year:year keys - for key in mapping: - year = Years(id = mapping[key], gsoc_year = mapping[key]) - mapping[key] = year - - # Update forign keys - for model in ['reglink', 'suborgdetails', 'timeline', 'userprofile']: - for record in apps.get_model('gsoc', model).objects.all(): - record.gsoc_year = mapping[record.id] - - # TODO Need to update ManyToMany fields: - # SubOrgDetails.past_years - # SubOrgDetails.applied_but_not_selected - - # Delete old keys - for year_id in mapping: - Years.objects.filter(id=year_id).delete() - - -class Migration(migrations.Migration): - - dependencies = [ - ('gsoc', '0057_auto_20200427_0720'), - ] - - operations = [ - migrations.AlterField( - model_name='gsocyear', - name='gsoc_year', - field=models.IntegerField(unique=True), - ), - migrations.RenameField('reglink', 'user_gsoc_year', 'gsoc_year'), - - # Update forign keys - migrations.RunPython(update_keys), - - migrations.AlterField( - model_name='reglink', - name='gsoc_year', - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to='gsoc.GsocYear', - to_field='gsoc_year'), - ), - migrations.AlterField( - model_name='suborgdetails', - name='gsoc_year', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='suborg_details', - to='gsoc.GsocYear', - to_field='gsoc_year'), - ), - migrations.AlterField( - model_name='timeline', - name='gsoc_year', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='gsoc.GsocYear', - to_field='gsoc_year'), - ), - migrations.AlterField( - model_name='userprofile', - name='gsoc_year', - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to='gsoc.GsocYear', - to_field='gsoc_year'), - ), - migrations.RemoveField( - model_name='gsocyear', - name='id', - ), - migrations.AlterField( - model_name='gsocyear', - name='gsoc_year', - field=models.IntegerField(primary_key=True, serialize=False), - ), - ] diff --git a/project.db b/project.db index 9bd0523d22537e0dfc2e0c7c91e1f3d6d27f8e58..9d0f72347235c21fdc243537b43a8887f01bb812 100644 GIT binary patch delta 43475 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 delta 11114 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 diff --git a/requirements.txt b/requirements.txt index 726b93f2..939b1d1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ #django -django~=3.0.0 +django~=3.1.0 django-appdata>=0.3.0 django-filer>=1.4.2 django-haystack>=3.0b2 @@ -12,7 +12,7 @@ django-treebeard>=4.0,<5.0 django-debug-toolbar>=1.11 #django cms -django-cms~=3.7.3 +django-cms~=3.8.0 djangocms-admin-style>=1.5,<1.6 djangocms-text-ckeditor>=3.9.1,<4.0 djangocms-file>=2.3,<2.5 diff --git a/users.db b/users.db deleted file mode 100644 index e69de29b..00000000 From c9f0f47b83b9e0a65db943485f93dcf1934d4246 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 22 Mar 2021 13:35:46 -0600 Subject: [PATCH 0664/1137] add gsoc invites page --- gsoc/admin.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 502f4f07..aa094520 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -417,9 +417,64 @@ def email(self, obj): def get_queryset(self, request): return UserProfile.all_objects.all() - admin.site.register(UserProfile, HiddenUserProfileAdmin) +def mark_invited(self, request, queryset): + for scheduler in queryset: + Scheduler.objects.create(command=scheduler.command, data=scheduler.data) + +class HiddenGSOCInviteAdmin(admin.ModelAdmin): + actions = [mark_invited] + list_display = ( + "user", + "email", + "suborg_full_name", + "gsoc_invited", + ) + list_filter = ("gsoc_invited") + readonly_fields = ( + "user", + "role", + "gsoc_year", + "accepted_proposal_pdf", + "blog_link", + "proposal_confirmed", + "github_handle", + ) + fieldsets = ( + ("Unhide", {"fields": ("hidden", "reminder_disabled")}), + ( + "User Profile Details", + { + "fields": ( + "user", + "role", + "gsoc_year", + "accepted_proposal_pdf", + "proposal_confirmed", + "blog_link", + "current_blog_count", + "github_handle", + "gsoc_invited", + ) + }, + ), + ) + + def blog_link(self, obj): + ns = obj.app_config.namespace + page = Page.objects.get(application_namespace=ns, publisher_is_draft=False) + url = page.get_absolute_url() + return mark_safe(f'{ns}') + + def email(self, obj): + return obj.user.email + + def get_queryset(self, request): + return UserProfile.all_objects.all().filter(gsoc_year=gsoc_year).exclude(role=student_role) + +admin.site.register(UserProfile, HiddenGSOCInviteAdmin) + class PageNotificationAdmin(admin.ModelAdmin): list_display = ("message", "user", "page") From 03ce3136067ffab0bc8a469d7be96c7495559d85 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 22 Mar 2021 14:08:02 -0600 Subject: [PATCH 0665/1137] fix admin profile --- gsoc/admin.py | 29 ++++------------------- gsoc/migrations/0002_userprofileproxy.py | 24 +++++++++++++++++++ gsoc/models.py | 5 +++- project.db | Bin 1982464 -> 1982464 bytes 4 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 gsoc/migrations/0002_userprofileproxy.py diff --git a/gsoc/admin.py b/gsoc/admin.py index aa094520..a08d1d94 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,23 +1,4 @@ -from .models import ( - UserProfile, - RegLink, - UserDetails, - Scheduler, - PageNotification, - AddUserLog, - BlogPostDueDate, - Builder, - Timeline, - ArticleReview, - Event, - SubOrgDetails, - GsocEndDate, - Comment, - SendEmail, - BlogPostHistory, - GsocYear, - SubOrg, -) +from .models import * from .forms import ( UserProfileForm, UserDetailsForm, @@ -27,7 +8,7 @@ GsocEndDateForm, ) - +from datetime import datetime from django.contrib.auth.models import User from django.contrib import admin from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin @@ -431,7 +412,7 @@ class HiddenGSOCInviteAdmin(admin.ModelAdmin): "suborg_full_name", "gsoc_invited", ) - list_filter = ("gsoc_invited") + list_filter = ("gsoc_invited",) readonly_fields = ( "user", "role", @@ -471,9 +452,9 @@ def email(self, obj): return obj.user.email def get_queryset(self, request): - return UserProfile.all_objects.all().filter(gsoc_year=gsoc_year).exclude(role=student_role) + return UserProfile.all_objects.all().filter(gsoc_year=datetime.now().year).exclude(role="3") -admin.site.register(UserProfile, HiddenGSOCInviteAdmin) +admin.site.register(UserProfileProxy, HiddenGSOCInviteAdmin) class PageNotificationAdmin(admin.ModelAdmin): diff --git a/gsoc/migrations/0002_userprofileproxy.py b/gsoc/migrations/0002_userprofileproxy.py new file mode 100644 index 00000000..69d39f30 --- /dev/null +++ b/gsoc/migrations/0002_userprofileproxy.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1.7 on 2021-03-22 13:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfileProxy', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('gsoc.userprofile',), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index f32cd3fc..d4d5ca59 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -424,7 +424,6 @@ class UserProfileManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(hidden=False) - class UserProfile(models.Model): ROLES = ((0, "Others"), (1, "Suborg Admin"), (2, "Mentor"), (3, "Student")) @@ -467,6 +466,10 @@ def confirm_proposal(self): self.proposal_confirmed = True self.save() +class UserProfileProxy(UserProfile): + + class Meta: + proxy = True class UserDetails(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) diff --git a/project.db b/project.db index 9d0f72347235c21fdc243537b43a8887f01bb812..57d71ef12804250b0cf37703389bcbfb1f093782 100644 GIT binary patch delta 1792 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 From c433737c39b3bc09aef0f748d02814d140bc43d0 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 22 Mar 2021 14:28:28 -0600 Subject: [PATCH 0666/1137] update for mark invited to work --- gsoc/admin.py | 7 +++--- gsoc/migrations/0003_auto_20210322_1410.py | 27 +++++++++++++++++++++ gsoc/models.py | 2 +- project.db | Bin 1982464 -> 1982464 bytes 4 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 gsoc/migrations/0003_auto_20210322_1410.py diff --git a/gsoc/admin.py b/gsoc/admin.py index a08d1d94..cacf3cb7 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -401,9 +401,8 @@ def get_queryset(self, request): admin.site.register(UserProfile, HiddenUserProfileAdmin) def mark_invited(self, request, queryset): - for scheduler in queryset: - Scheduler.objects.create(command=scheduler.command, data=scheduler.data) - + queryset.update(gsoc_invited=True) + class HiddenGSOCInviteAdmin(admin.ModelAdmin): actions = [mark_invited] list_display = ( @@ -454,7 +453,7 @@ def email(self, obj): def get_queryset(self, request): return UserProfile.all_objects.all().filter(gsoc_year=datetime.now().year).exclude(role="3") -admin.site.register(UserProfileProxy, HiddenGSOCInviteAdmin) +admin.site.register(AdminGSOCInvites, HiddenGSOCInviteAdmin) class PageNotificationAdmin(admin.ModelAdmin): diff --git a/gsoc/migrations/0003_auto_20210322_1410.py b/gsoc/migrations/0003_auto_20210322_1410.py new file mode 100644 index 00000000..39e0be18 --- /dev/null +++ b/gsoc/migrations/0003_auto_20210322_1410.py @@ -0,0 +1,27 @@ +# Generated by Django 3.1.7 on 2021-03-22 14:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0002_userprofileproxy'), + ] + + operations = [ + migrations.DeleteModel( + name='UserProfileProxy', + ), + migrations.CreateModel( + name='AdminGSOCInvites', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('gsoc.userprofile',), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index d4d5ca59..ecd55fa0 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -466,7 +466,7 @@ def confirm_proposal(self): self.proposal_confirmed = True self.save() -class UserProfileProxy(UserProfile): +class AdminGSOCInvites(UserProfile): class Meta: proxy = True diff --git a/project.db b/project.db index 57d71ef12804250b0cf37703389bcbfb1f093782..4eee5fa00390010fbfe52db6c7acb8a960a0b1bb 100644 GIT binary patch delta 1845 zcmZ`)ZA?>F81A_rw1u8?i%<~|sdYvj7H|z%mgy$mMog=lrGz%_~SY6&3(^v z?)yCFJ?9P&M}~(Z$FiYKBm#FZ+C+FbzPSk4K4^>Oi36)gD6&AxU;dmKd4h$hBeglO!1u2eXmn_S6~sq`6MT$P+l zB|S%UM@yM&&-075lD?xa>hy%fW*!G_l<;6`2%@JRp@mwz9^^ zarvB1t25~0nKr5dBzTWse@}9h zFRx8sv(tDen-(dyo8pbZvTQ-Cy*KP{jTgrjXtk}HLJj+v4%W!A`P#Tc+}O$5bm3&J zV*d$xxo>#+l4A9kYDs|+j~N(iFfs+zQr3L#B%sLOq(8^RS5L=#OC(C-61`=W9ad%^ zR<6CE%%LukEz}|}zMT`3(B*UQE9m-`L%DDB)f6lIPv;h;hTc0K+PEDv> zkzAi34?RU`oSjImz%CrE!)(s9q7QK<_Gbl}ZnSn;Dz*skOmD~)3!ja6m&~O z$X*u7UWOO2EkT*&5SKyk9gUdyhp(k7hw@$J1N9z@rLr?(s;+UhntReg_-yT3c)3wa$){eHFEVhMJ1IGRce166evbyZH(NkMd<+g+k`aXl1jZ_EJRW|=Ff4|RDCfpQF@j z3RyK;F4EP|TU8SR@-7r>9#N(l=_Gx?0`U zMoo8Rb#JM0z}p`5IYM5uX}5cCyVl@wmvh0+<~s9kf0?h@qSN~ZtVPa>NKum|ahwX( zt61)TH_r086)wPa8v%vy*^R;v2tSf?W*A?tiuKsPMn)ng2|~Pp@4+Lv>jvcFWPIor zq!)Sdr4-E&Q_Yk!W@ZP&G5O3UM#*F{tC$pqLXXfNXc7H_enj7)Yv^lq1$}}9G=ol~ zQ|JVmM6aX6Xb26U9^^so=tWeAY{-h6+JLku4?Tl&&}zgW2?Fphcpu(_3veFZgjeAg z@Kg8^JO`)YyYM93^cFl0UxlME3VWdscEUE;1Z&_PSPFMS9oz;t!}Tx=rcF?@zhg1K Viq|H7Epv%*GZAk5_rKYc^f#9yMN)G)BT_1(qVmCvh;TDV$?~$Bg)Gr_ zf%PR#k1$9%qXcMT;~d2wZycA%i|?;KvNZf4*Vn%VY*JnSqB@ zViUulHIQH!29NBHpr%&|W1rAnc8A0D7Bc^VFV+1u<*3p)QgZ^Kv@znZW6_d6Uv~(n zQBoJw_xhqbsC#qOMp7GAfxTG?_=EEJO-Km_*BlJ-1=Y z7UmFO0R|l4fr%F8gpZ%GSy-jdhRu7uXq7I#G^+_tmZuqFOE}zcya~~(cFSRj7rt>K zo1OMp=(NXrTE(&{O@wBv2Qp_&*>tNFlmHtLfdpilZdE$Qq44Uua%vLa5YIwj9Wga5O zTO1V!w3r&zB5hhgjRjOK=2xSBE#lPzaV-)LMr&iys1^?MpO`<4bAMw;s^^lbhU(ic z_FwO53#i#AEe`sm#o76YXScdbl%lgN>nl3J)?5xMz;@~@R<3xadO0P_d8Yb=?g2bY7t|qI(zO?)u7k`bB~zmgPZcx^wHh S+hvmRxOw`(<4!3P3FRMK>Ea;( From 1f7d5d667d980298750cd7889a3e09a78c9367df Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Mon, 22 Mar 2021 15:11:58 -0600 Subject: [PATCH 0667/1137] update requirements --- gsoc/admin.py | 2 +- project.db | Bin 1982464 -> 1982464 bytes requirements.txt | 28 ++++++++++++++++------------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index cacf3cb7..7c47bdec 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -451,7 +451,7 @@ def email(self, obj): return obj.user.email def get_queryset(self, request): - return UserProfile.all_objects.all().filter(gsoc_year=datetime.now().year).exclude(role="3") + return UserProfile.all_objects.all().filter(gsoc_year=datetime.now().year).exclude(role="3").distinct() admin.site.register(AdminGSOCInvites, HiddenGSOCInviteAdmin) diff --git a/project.db b/project.db index 4eee5fa00390010fbfe52db6c7acb8a960a0b1bb..07f252ebb2091968e4eaa170d3b7d608a5e8e48c 100644 GIT binary patch delta 262 zcmZo@sBCDcoFL8UKT*b+(Z4aFHG#1;fvGitxix{MHG#D?fvq)xy)}WOHG#7=fvYuv zyETEQHG#J^fv+`zzcoQ%Yl5K0S{@T4D?=0.3.0 django-filer>=1.4.2 django-haystack>=3.0b2 @@ -12,21 +12,25 @@ django-treebeard>=4.0,<5.0 django-debug-toolbar>=1.11 #django cms -django-cms~=3.8.0 -djangocms-admin-style>=1.5,<1.6 -djangocms-text-ckeditor>=3.9.1,<4.0 -djangocms-file>=2.3,<2.5 -djangocms-link>=2.5,<2.7 -djangocms-icon>=1.4,<1.6 -djangocms-picture>=2.3,<2.5 -djangocms-bootstrap4>=1.5,<1.7 -djangocms-style>=2.2,<2.4 +django-cms>=3.8,<3.9 +djangocms-admin-style>=2.0,<3.0 +djangocms-text-ckeditor>=4.0,<5.0 +djangocms-file>=3.0,<4.0 +djangocms-link>=3.0,<4.0 +djangocms-icon>=2.0,<3.0 +djangocms-picture>=3.0,<4.0 +djangocms-bootstrap4>=2.0,<3.0 +djangocms-style>=3.0,<4.0 djangocms-snippet>=2.2,<2.4 -djangocms-googlemap>=1.3,<1.5 -djangocms-video>=2.1,<2.4 +djangocms-googlemap>=2.0,<3.0 +djangocms-video>=3.0,<4.0 djangocms-audio>=1.1.0 djangocms_history>=1.0.0 easy_thumbnails +django-classy-tags>=2.0 +django-sekizai>=2.0 +django-mptt>0.9 + #djangocms_column>=1.9 #aldryn dependencies From bd0ff4e8dccda73ed5f0637cd9000db009486df7 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Tue, 23 Mar 2021 10:03:44 -0600 Subject: [PATCH 0668/1137] Update README.md --- docs/README.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/docs/README.md b/docs/README.md index 6bb5150a..b80703d9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,14 +36,6 @@ Default student users are `student-1`, `student-2`, `student-3` and `student-4` ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. -## Git - -To see diff's on the database you will need to run the following command: -``` -$ git config --local include.path ../.gitconfig -``` -Also make sure sqlite3 is available. - ## Virtualenv A virtual environment is a tool that helps to keep dependencies required by different projects separate by creating isolated python virtual environments for them. This means that each project can have its own dependencies, regardless of what dependencies every other project has. We use a module named `virtualenv` which is a tool to create isolated Python environments. `virtualenv` creates a folder which contains all the necessary executables to use the packages that a Python project would need. @@ -91,13 +83,3 @@ Once you are done with the work, you can deactivate the virtual environment by t Now you will be back to system’s default Python installation. - -notes: - -for django 3 need to change in aldryn_newsblog - -from six import python_2_unicode_compatible - -instead of - -from django.utils.six import python_2_unicode_compatible From 19ebc5cf326c8ac35256999d5192b55d23702321 Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 18 Nov 2021 16:54:39 +0545 Subject: [PATCH 0669/1137] add path and form for profile info change --- gsoc/forms.py | 5 +++++ gsoc/templates/myprofile.html | 1 + gsoc/templates/registration/change_info.html | 18 ++++++++++++++++++ gsoc/urls.py | 2 ++ gsoc/views.py | 18 +++++++++++++++++- project.db | Bin 1982464 -> 1982464 bytes 6 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 gsoc/templates/registration/change_info.html diff --git a/gsoc/forms.py b/gsoc/forms.py index 31ca1a60..230630ef 100644 --- a/gsoc/forms.py +++ b/gsoc/forms.py @@ -9,6 +9,7 @@ SubOrgDetails, SubOrg, GsocEndDate, + User ) from django import forms @@ -64,6 +65,10 @@ class Meta: model = GsocEndDate fields = ("date",) +class ChangeInfoForm(forms.ModelForm): + class Meta: + model = User + fields = ("username", "email", "first_name", "last_name") class SubOrgApplicationForm(forms.ModelForm): class Meta: diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index 71c3bc7b..ceefd7f5 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -13,6 +13,7 @@

    Welcome{% if not user.is_anonymous %} {{ user.username }}{% endif %} to PSF! {% if user %} Change Password + Change Profile Info {% if user.is_current_year_student %} -{% endblock %} - - -{% block submit_buttons_bottom %} - {% if not original.is_reviewed %} - - {% endif %} -{% endblock %} From 7e1d1bacf60a1b402d8dae7f2fce3c6ce8fe00ad Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 14 Jul 2022 16:47:11 +0545 Subject: [PATCH 1009/1137] add view url for mark all reviewed --- gsoc/templates/aldryn_newsblog/article_list.html | 6 ++++++ gsoc/urls.py | 9 +++++++++ gsoc/views.py | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/gsoc/templates/aldryn_newsblog/article_list.html b/gsoc/templates/aldryn_newsblog/article_list.html index 3772f0a8..72cd6a66 100644 --- a/gsoc/templates/aldryn_newsblog/article_list.html +++ b/gsoc/templates/aldryn_newsblog/article_list.html @@ -7,6 +7,12 @@

    {% page_attribute "page_title" %} + + {% if article_list %} + {{article_list.0}} + + + {% endif %}

    diff --git a/gsoc/urls.py b/gsoc/urls.py index 57259fcd..4069def5 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -128,3 +128,12 @@ url("authorize", gsoc.views.authorize, name="auth"), url("oauth2callback", gsoc.views.oauth2callback, name="oauth2callback") ] + +# Review all articles at once +urlpatterns += [ + path( + "mark_all_reviewed/", + gsoc.views.mark_all_article_as_reviewed, + name="mark_all_reviewed" + ) +] diff --git a/gsoc/views.py b/gsoc/views.py index 6b4aa901..42833d67 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -646,3 +646,7 @@ def oauth2callback(request): token.write(credentials.to_json()) return HttpResponse("Token generated successfully!!") + +def mark_all_article_as_reviewed(request): + articles = Article.objects.filter(author=request.user) + print(articles) \ No newline at end of file From 5642d7d45e438ae511ba0bb2dd207932f5f26256 Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 14 Jul 2022 19:11:09 +0545 Subject: [PATCH 1010/1137] add filter to get author --- gsoc/templates/aldryn_newsblog/article_list.html | 7 ++++--- gsoc/templatetags/app_tag.py | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/aldryn_newsblog/article_list.html b/gsoc/templates/aldryn_newsblog/article_list.html index 72cd6a66..418361e7 100644 --- a/gsoc/templates/aldryn_newsblog/article_list.html +++ b/gsoc/templates/aldryn_newsblog/article_list.html @@ -1,5 +1,6 @@ {% extends "aldryn_newsblog/base.html" %} {% load i18n apphooks_config_tags cms_tags %} +{% load app_tag %} {% block meta %}{% page_attribute "page_title" %} - {% endblock %} @@ -8,11 +9,11 @@

    {% page_attribute "page_title" %} - {% if article_list %} - {{article_list.0}} + + - {% endif %} +

    diff --git a/gsoc/templatetags/app_tag.py b/gsoc/templatetags/app_tag.py index fc99800d..6595feb5 100644 --- a/gsoc/templatetags/app_tag.py +++ b/gsoc/templatetags/app_tag.py @@ -39,3 +39,8 @@ def time_zone(context, flag=0): return gmtTime else: return TIME_ZONE + + +@register.filter +def get_author(value): + return value[0].article From d889679019eb77e33a80866f653b8e448a5e5a3a Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 13:09:11 +0545 Subject: [PATCH 1011/1137] handle exception on token refresh --- gsoc/models.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 814c2c06..f52eb895 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -76,13 +76,16 @@ def getCreds(): ) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - with open(os.path.join(settings.BASE_DIR, 'token.json'), 'w') as token: - token.write(creds.to_json()) - creds = Credentials.from_authorized_user_file( - os.path.join(BASE_DIR, 'token.json'), - SCOPES - ) + try: + creds.refresh(Request()) + with open(os.path.join(settings.BASE_DIR, 'token.json'), 'w') as token: + token.write(creds.to_json()) + creds = Credentials.from_authorized_user_file( + os.path.join(BASE_DIR, 'token.json'), + SCOPES + ) + except Exception as e: + return str(e) return creds From d6a2db05d9214cc631398b60c03b5137c1cdd915 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 13:15:00 +0545 Subject: [PATCH 1012/1137] handle exception while refreshing token --- gsoc/common/utils/build_tasks.py | 188 ++++++++++++++++--------------- gsoc/models.py | 17 ++- 2 files changed, 107 insertions(+), 98 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index a49ee195..813edee3 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -162,109 +162,121 @@ def build_remove_user_details(builder): def build_add_timeline_to_calendar(builder): data = json.loads(builder.data) if not data["calendar_id"]: + try: + creds = getCreds() + if creds: + service = build("calendar", "v3", credentials=creds, cache_discovery=False) + calendar = {"summary": "GSoC @ PSF Calendar", "timezone": "UTC"} + calendar = service.calendars().insert(body=calendar).execute() + timeline = Timeline.objects.get(id=data["timeline_id"]) + timeline.calendar_id = calendar.get("id") + timeline.save() + else: + raise Exception( + f"Please get the Access Token: " + + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" + ) + except Exception as e: + return str(e) + + +def build_add_bpdd_to_calendar(builder): + data = json.loads(builder.data) + try: creds = getCreds() if creds: service = build("calendar", "v3", credentials=creds, cache_discovery=False) - calendar = {"summary": "GSoC @ PSF Calendar", "timezone": "UTC"} - calendar = service.calendars().insert(body=calendar).execute() - timeline = Timeline.objects.get(id=data["timeline_id"]) - timeline.calendar_id = calendar.get("id") - timeline.save() + event = { + "summary": data["title"], + "start": {"date": data["date"]}, + "end": {"date": data["date"]}, + } + cal_id = builder.timeline.calendar_id if builder.timeline else "primary" + if not data["event_id"]: + event = ( + service.events() + .insert(calendarId=cal_id, body=event) + .execute() + ) + item = BlogPostDueDate.objects.get(id=data["id"]) + item.event_id = event.get("id") + item.save() + else: + service.events().update( + calendarId=cal_id, eventId=data["event_id"], body=event + ).execute() else: raise Exception( f"Please get the Access Token: " + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" ) - - -def build_add_bpdd_to_calendar(builder): - data = json.loads(builder.data) - creds = getCreds() - if creds: - service = build("calendar", "v3", credentials=creds, cache_discovery=False) - event = { - "summary": data["title"], - "start": {"date": data["date"]}, - "end": {"date": data["date"]}, - } - cal_id = builder.timeline.calendar_id if builder.timeline else "primary" - if not data["event_id"]: - event = ( - service.events() - .insert(calendarId=cal_id, body=event) - .execute() - ) - item = BlogPostDueDate.objects.get(id=data["id"]) - item.event_id = event.get("id") - item.save() - else: - service.events().update( - calendarId=cal_id, eventId=data["event_id"], body=event - ).execute() - else: - raise Exception( - f"Please get the Access Token: " + - f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" - ) + except Exception as e: + return str(e) def build_add_event_to_calendar(builder): data = json.loads(builder.data) - creds = getCreds() - if creds: - service = build("calendar", "v3", credentials=creds, cache_discovery=False) - event = { - "summary": data["title"], - "start": {"date": data["date"]}, - "end": {"date": data["date"]}, - } - cal_id = builder.timeline.calendar_id if builder.timeline else "primary" - item = Event.objects.get(id=data["id"]) - if not data["event_id"]: - event = ( - service.events() - .insert(calendarId=cal_id, body=event) - .execute() - ) - item.event_id = event.get("id") - item.save() + try: + creds = getCreds() + if creds: + service = build("calendar", "v3", credentials=creds, cache_discovery=False) + event = { + "summary": data["title"], + "start": {"date": data["date"]}, + "end": {"date": data["date"]}, + } + cal_id = builder.timeline.calendar_id if builder.timeline else "primary" + item = Event.objects.get(id=data["id"]) + if not data["event_id"]: + event = ( + service.events() + .insert(calendarId=cal_id, body=event) + .execute() + ) + item.event_id = event.get("id") + item.save() + else: + service.events().update( + calendarId=cal_id, eventId=item.event_id, body=event + ).execute() else: - service.events().update( - calendarId=cal_id, eventId=item.event_id, body=event - ).execute() - else: - raise Exception( - f"Please get the Access Token: " + - f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" - ) + raise Exception( + f"Please get the Access Token: " + + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" + ) + except Exception as e: + return str(e) def build_add_end_to_calendar(builder): data = json.loads(builder.data) - creds = getCreds() - if creds: - service = build("calendar", "v3", credentials=creds, cache_discovery=False) - event = { - "summary": data["title"], - "start": {"date": data["date"]}, - "end": {"date": data["date"]}, - } - cal_id = builder.timeline.calendar_id if builder.timeline else "primary" - if not data["event_id"]: - event = ( - service.events() - .insert(calendarId=cal_id, body=event) - .execute() - ) - item = GsocEndDate.objects.get(id=data["id"]) - item.event_id = event.get("id") - item.save() + try: + creds = getCreds() + if creds: + service = build("calendar", "v3", credentials=creds, cache_discovery=False) + event = { + "summary": data["title"], + "start": {"date": data["date"]}, + "end": {"date": data["date"]}, + } + cal_id = builder.timeline.calendar_id if builder.timeline else "primary" + if not data["event_id"]: + event = ( + service.events() + .insert(calendarId=cal_id, body=event) + .execute() + ) + item = GsocEndDate.objects.get(id=data["id"]) + item.event_id = event.get("id") + item.save() + else: + service.events().update( + calendarId=cal_id, eventId=data["event_id"], body=event + ).execute() else: - service.events().update( - calendarId=cal_id, eventId=data["event_id"], body=event - ).execute() - else: - raise Exception( - f"Please get the Access Token: " + - f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" - ) + raise Exception( + f"Please get the Access Token: " + + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" + ) + except Exception as e: + return str(e) diff --git a/gsoc/models.py b/gsoc/models.py index f52eb895..814c2c06 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -76,16 +76,13 @@ def getCreds(): ) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: - try: - creds.refresh(Request()) - with open(os.path.join(settings.BASE_DIR, 'token.json'), 'w') as token: - token.write(creds.to_json()) - creds = Credentials.from_authorized_user_file( - os.path.join(BASE_DIR, 'token.json'), - SCOPES - ) - except Exception as e: - return str(e) + creds.refresh(Request()) + with open(os.path.join(settings.BASE_DIR, 'token.json'), 'w') as token: + token.write(creds.to_json()) + creds = Credentials.from_authorized_user_file( + os.path.join(BASE_DIR, 'token.json'), + SCOPES + ) return creds From b2814f0290a5713d5a286bdba26151d490cf1012 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 13:16:22 +0545 Subject: [PATCH 1013/1137] fix over indentation --- gsoc/common/utils/build_tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 813edee3..0a118a50 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -211,7 +211,7 @@ def build_add_bpdd_to_calendar(builder): f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" ) except Exception as e: - return str(e) + return str(e) def build_add_event_to_calendar(builder): @@ -245,7 +245,7 @@ def build_add_event_to_calendar(builder): f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" ) except Exception as e: - return str(e) + return str(e) def build_add_end_to_calendar(builder): @@ -279,4 +279,4 @@ def build_add_end_to_calendar(builder): f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" ) except Exception as e: - return str(e) + return str(e) From 8d1979a455adf86ed09f9c85ebccbf2b44058dd6 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 16:25:05 +0545 Subject: [PATCH 1014/1137] make mark all reviewed button work --- gsoc/templates/aldryn_newsblog/article_list.html | 4 ++-- gsoc/templatetags/app_tag.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/aldryn_newsblog/article_list.html b/gsoc/templates/aldryn_newsblog/article_list.html index 418361e7..6b42dbb3 100644 --- a/gsoc/templates/aldryn_newsblog/article_list.html +++ b/gsoc/templates/aldryn_newsblog/article_list.html @@ -9,11 +9,11 @@

    {% page_attribute "page_title" %} - + {% if request.user.is_superuser and article_list %} - + {% endif %}

    diff --git a/gsoc/templatetags/app_tag.py b/gsoc/templatetags/app_tag.py index 6595feb5..268f8cb2 100644 --- a/gsoc/templatetags/app_tag.py +++ b/gsoc/templatetags/app_tag.py @@ -43,4 +43,4 @@ def time_zone(context, flag=0): @register.filter def get_author(value): - return value[0].article + return value[0].owner.id From fb6e15eab815910fa2ec76558156f07d90d80153 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 16:52:36 +0545 Subject: [PATCH 1015/1137] make mark all work --- gsoc/views.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 42833d67..24c1629f 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,5 +1,5 @@ import csv -from datetime import datetime +from datetime import date, datetime from gsoc import settings @@ -647,6 +647,20 @@ def oauth2callback(request): return HttpResponse("Token generated successfully!!") -def mark_all_article_as_reviewed(request): - articles = Article.objects.filter(author=request.user) - print(articles) \ No newline at end of file + +@decorators.login_required +@decorators.user_passes_test(is_superuser) +def mark_all_article_as_reviewed(request, author_id): + user = User.objects.get(id=author_id) + current_year = datetime.now().year + articles = Article.objects.filter( + owner=user, + publishing_date__contains=current_year + ) + for article in articles: + review = ArticleReview.objects.get( + article=article + ) + review.is_reviewed = True + review.last_reviewed_by = request.user + review.save() From 9ffeb57d663d812359a83b18a7fc5dc534ddb795 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 20:03:04 +0545 Subject: [PATCH 1016/1137] return HttpResponse back to previous page --- gsoc/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gsoc/views.py b/gsoc/views.py index 24c1629f..35bb8290 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -664,3 +664,5 @@ def mark_all_article_as_reviewed(request, author_id): review.is_reviewed = True review.last_reviewed_by = request.user review.save() + + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) From 976ffd47f33e241804963465cff4ff89adc534a1 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 20:04:33 +0545 Subject: [PATCH 1017/1137] remove redundant imports --- gsoc/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/views.py b/gsoc/views.py index 35bb8290..467848d5 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,5 +1,5 @@ import csv -from datetime import date, datetime +from datetime import datetime from gsoc import settings From 0508d12ea5c4c0e2082dfe7261f0a4e84c589318 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 20:08:11 +0545 Subject: [PATCH 1018/1137] add message to confirm marking --- gsoc/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gsoc/views.py b/gsoc/views.py index 467848d5..1290622b 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -665,4 +665,5 @@ def mark_all_article_as_reviewed(request, author_id): review.last_reviewed_by = request.user review.save() + messages.success(request, "All articles marked as reviewed!") return HttpResponseRedirect(request.META.get('HTTP_REFERER')) From 6d76c941513fc63e6a7e5f54f6aeb3b35e3d3037 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 15 Jul 2022 20:55:56 +0545 Subject: [PATCH 1019/1137] handle no query exception --- gsoc/views.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gsoc/views.py b/gsoc/views.py index 1290622b..23ec6f6f 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -658,12 +658,16 @@ def mark_all_article_as_reviewed(request, author_id): publishing_date__contains=current_year ) for article in articles: - review = ArticleReview.objects.get( - article=article - ) - review.is_reviewed = True - review.last_reviewed_by = request.user - review.save() + try: + review = ArticleReview.objects.get( + article=article, + is_reviewed=False + ) + review.is_reviewed = True + review.last_reviewed_by = request.user + review.save() + except Exception: + pass messages.success(request, "All articles marked as reviewed!") return HttpResponseRedirect(request.META.get('HTTP_REFERER')) From 9b00c8c9ec98b23b24523f61e9596a4e1ed91cf7 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 16 Jul 2022 11:31:11 +0545 Subject: [PATCH 1020/1137] update data to fix clean install runcron error --- data.json | 296 ------------------------------------------------------ 1 file changed, 296 deletions(-) diff --git a/data.json b/data.json index b6e42d5c..a812a5dc 100644 --- a/data.json +++ b/data.json @@ -6217,234 +6217,6 @@ "gsoc_invited": true } }, - { - "model": "gsoc.scheduler", - "pk": 1, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=12b10014-5d11-4b3c-8dcc-d734a367b2b1\", \"role\": 0, \"gsoc_year\": 2019}, \"subject\": \"You have been invited to join for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-1@email.com\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:40:17.127" - } - }, - { - "model": "gsoc.scheduler", - "pk": 2, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=12b10014-5d11-4b3c-8dcc-d734a367b2b1\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 2, \"send_to\": \"test-1@email.com\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:40:17.134" - } - }, - { - "model": "gsoc.scheduler", - "pk": 3, - "fields": { - "command": "send_email", - "activation_date": null, - "data": "{\"template_data\": {\"suborg_name\": \"Python Software Foundation GSoC Team\"}, \"subject\": \"Review new/updated SubOrg Application\", \"template\": \"suborg_application_notification.html\", \"send_to\": []}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:45:25.275" - } - }, - { - "model": "gsoc.scheduler", - "pk": 4, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=a7955ef9-c604-4855-a6b6-e6b863c2bc51\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"Python Software Foundation GSoC Team\"}, \"subject\": \"You have been invited to join Python Software Foundation GSoC Team as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-1@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.224" - } - }, - { - "model": "gsoc.scheduler", - "pk": 5, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=a7955ef9-c604-4855-a6b6-e6b863c2bc51\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 3, \"send_to\": \"test-1@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.227" - } - }, - { - "model": "gsoc.scheduler", - "pk": 6, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=66bc4c9c-6d66-4765-9037-9f7387079bce\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"CVE Binary Tool\"}, \"subject\": \"You have been invited to join CVE Binary Tool as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-2@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.235" - } - }, - { - "model": "gsoc.scheduler", - "pk": 7, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=66bc4c9c-6d66-4765-9037-9f7387079bce\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 4, \"send_to\": \"test-2@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.238" - } - }, - { - "model": "gsoc.scheduler", - "pk": 8, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=59d608ef-5bea-4354-8994-3c8829f12d7d\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"Mercurial\"}, \"subject\": \"You have been invited to join Mercurial as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-3@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.243" - } - }, - { - "model": "gsoc.scheduler", - "pk": 9, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=59d608ef-5bea-4354-8994-3c8829f12d7d\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 5, \"send_to\": \"test-3@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.245" - } - }, - { - "model": "gsoc.scheduler", - "pk": 10, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=3a576b03-7a03-457b-8329-c7b778039e31\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"SciPy\"}, \"subject\": \"You have been invited to join SciPy as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-4@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.251" - } - }, - { - "model": "gsoc.scheduler", - "pk": 11, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=3a576b03-7a03-457b-8329-c7b778039e31\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 6, \"send_to\": \"test-4@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:49:15.252" - } - }, - { - "model": "gsoc.scheduler", - "pk": 12, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=4c0cf685-ebde-41de-a834-0422deedad69\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"CVE Binary Tool\"}, \"subject\": \"You have been invited to join CVE Binary Tool as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-1@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.306" - } - }, - { - "model": "gsoc.scheduler", - "pk": 13, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=4c0cf685-ebde-41de-a834-0422deedad69\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 7, \"send_to\": \"test-1@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.307" - } - }, - { - "model": "gsoc.scheduler", - "pk": 14, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=1cedb1ba-5681-496d-ab19-ac741500eace\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"Mercurial\"}, \"subject\": \"You have been invited to join Mercurial as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-2@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.313" - } - }, - { - "model": "gsoc.scheduler", - "pk": 15, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=1cedb1ba-5681-496d-ab19-ac741500eace\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 8, \"send_to\": \"test-2@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.315" - } - }, - { - "model": "gsoc.scheduler", - "pk": 16, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=3d9c2d8d-4f21-4eed-8eef-9b751832211b\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"Python Software Foundation GSoC Team\"}, \"subject\": \"You have been invited to join Python Software Foundation GSoC Team as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-3@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.317" - } - }, - { - "model": "gsoc.scheduler", - "pk": 17, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=3d9c2d8d-4f21-4eed-8eef-9b751832211b\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 9, \"send_to\": \"test-3@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.320" - } - }, - { - "model": "gsoc.scheduler", - "pk": 18, - "fields": { - "command": "send_email", - "activation_date": "2019-08-30T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=6be0b2a9-1108-4e14-82da-0d5387b99a4d\", \"role\": 3, \"gsoc_year\": 2019, \"suborg\": \"SciPy\"}, \"subject\": \"You have been invited to join SciPy as a Student for GSoC 2019 with PSF\", \"template\": \"invite.html\", \"send_to\": \"test-4@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.322" - } - }, - { - "model": "gsoc.scheduler", - "pk": 19, - "fields": { - "command": "send_reg_reminder", - "activation_date": "2019-09-02T11:39:22.083", - "data": "{\"template_data\": {\"register_link\": \"https://blogs.python-gsoc.org/accounts/register?reglink_id=6be0b2a9-1108-4e14-82da-0d5387b99a4d\"}, \"subject\": \"Reminder for registration\", \"template\": \"registration_reminder.html\", \"object_pk\": 10, \"send_to\": \"test-4@test.xyz\"}", - "success": null, - "last_error": null, - "created": "2019-08-30T11:56:41.323" - } - }, { "model": "gsoc.blogposthistory", "pk": 1, @@ -6668,74 +6440,6 @@ "log_id": "0c17d26c-3258-4ed3-8707-598f1506ac29" } }, - { - "model": "gsoc.reglink", - "pk": 7, - "fields": { - "is_used": true, - "reglink_id": "4c0cf685-ebde-41de-a834-0422deedad69", - "created_at": "2019-08-30T11:56:41.301", - "user_role": 3, - "user_suborg": 2, - "gsoc_year": 2019, - "adduserlog": 2, - "email": "test-1@test.xyz", - "scheduler": 12, - "reminder": 13, - "send_notifications": true - } - }, - { - "model": "gsoc.reglink", - "pk": 8, - "fields": { - "is_used": true, - "reglink_id": "1cedb1ba-5681-496d-ab19-ac741500eace", - "created_at": "2019-08-30T11:56:41.309", - "user_role": 3, - "user_suborg": 3, - "gsoc_year": 2019, - "adduserlog": 2, - "email": "test-2@test.xyz", - "scheduler": 14, - "reminder": 15, - "send_notifications": true - } - }, - { - "model": "gsoc.reglink", - "pk": 9, - "fields": { - "is_used": true, - "reglink_id": "3d9c2d8d-4f21-4eed-8eef-9b751832211b", - "created_at": "2019-08-30T11:56:41.316", - "user_role": 3, - "user_suborg": 1, - "gsoc_year": 2019, - "adduserlog": 2, - "email": "test-3@test.xyz", - "scheduler": 16, - "reminder": 17, - "send_notifications": true - } - }, - { - "model": "gsoc.reglink", - "pk": 10, - "fields": { - "is_used": true, - "reglink_id": "6be0b2a9-1108-4e14-82da-0d5387b99a4d", - "created_at": "2019-08-30T11:56:41.321", - "user_role": 3, - "user_suborg": 4, - "gsoc_year": 2019, - "adduserlog": 2, - "email": "test-4@test.xyz", - "scheduler": 18, - "reminder": 19, - "send_notifications": true - } - }, { "model": "gsoc.articlereview", "pk": 1, From 119d055f9566278d23c7a66222ed2cf906860a02 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 16 Jul 2022 12:58:52 +0545 Subject: [PATCH 1021/1137] fix event start and end date issue --- gsoc/common/utils/build_tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index a49ee195..c6f8fb13 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -215,8 +215,8 @@ def build_add_event_to_calendar(builder): service = build("calendar", "v3", credentials=creds, cache_discovery=False) event = { "summary": data["title"], - "start": {"date": data["date"]}, - "end": {"date": data["date"]}, + "start": {"date": data["start_date"]}, + "end": {"date": data["end_date"]}, } cal_id = builder.timeline.calendar_id if builder.timeline else "primary" item = Event.objects.get(id=data["id"]) From a30228e1666d96a62bebc4cde35f48e37577fdfe Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 16 Jul 2022 13:06:21 +0545 Subject: [PATCH 1022/1137] remove toolbar and shortcuts menu item --- gsoc/cms_toolbars.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 934b6630..6e435a10 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -147,18 +147,6 @@ def add_admin_menu(self): ) self._admin_menu.add_break(CLIPBOARD_BREAK) - # Disable toolbar - if user and user.is_superuser: - self._admin_menu.add_link_item( - _("Disable toolbar"), - url="?%s" % get_cms_setting("CMS_TOOLBAR_URL__DISABLE"), - ) - self._admin_menu.add_break(TOOLBAR_DISABLE_BREAK) - self._admin_menu.add_link_item( - _("Shortcuts..."), url="#", extra_classes=("cms-show-shortcuts",) - ) - self._admin_menu.add_break(SHORTCUTS_BREAK) - # logout self.add_logout_button(self._admin_menu) From e939455ec2fa6d45d652e92895b862e1b43e7864 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 16 Jul 2022 13:12:19 +0545 Subject: [PATCH 1023/1137] remove page menu --- gsoc/cms_toolbars.py | 274 +------------------------------------------ 1 file changed, 1 insertion(+), 273 deletions(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 6e435a10..8a631ca7 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -176,279 +176,7 @@ def populate(self): def add_page_menu(self): - user = getattr(self.request, "user", None) - if user and user.is_superuser: - if self.page: - edit_mode = self.toolbar.edit_mode_active - refresh = self.toolbar.REFRESH_PAGE - can_change = user_can_change_page( - user=self.request.user, - page=self.page, - site=self.current_site, - ) - - # menu for current page - # NOTE: disabled if the current path is "deeper" into the - # application's url patterns than its root. This is because - # when the Content Manager is at the root of the app-hook, - # some of the page options still make sense. - current_page_menu = self.toolbar.get_or_create_menu( - PAGE_MENU_IDENTIFIER, - _('Page'), position=1, - disabled=self.in_apphook() and not self.in_apphook_root() - ) - - new_page_params = {'edit': 1} - new_sub_page_params = {'edit': 1, 'parent_node': self.page.node_id} - - if self.page.is_page_type: - add_page_url = admin_reverse('cms_pagetype_add') - advanced_url = admin_reverse('cms_pagetype_advanced', args=(self.page.pk,)) - page_settings_url = admin_reverse('cms_pagetype_change', args=(self.page.pk,)) - duplicate_page_url = admin_reverse('cms_pagetype_duplicate', args=[self.page.pk]) - else: - add_page_url = admin_reverse('cms_page_add') - advanced_url = admin_reverse('cms_page_advanced', args=(self.page.pk,)) - page_settings_url = admin_reverse('cms_page_change', args=(self.page.pk,)) - duplicate_page_url = admin_reverse('cms_page_duplicate', args=[self.page.pk]) - - can_add_root_page = page_permissions.user_can_add_page( - user=self.request.user, - site=self.current_site, - ) - - if self.page.parent_page: - new_page_params['parent_node'] = self.page.parent_page.node_id - can_add_sibling_page = page_permissions.user_can_add_subpage( - user=self.request.user, - target=self.page.parent_page, - ) - else: - can_add_sibling_page = can_add_root_page - - can_add_sub_page = page_permissions.user_can_add_subpage( - user=self.request.user, - target=self.page, - ) - - # page operations menu - add_page_menu = current_page_menu.get_or_create_menu( - PAGE_MENU_ADD_IDENTIFIER, - _('Create Page'), - ) - - add_page_menu_modal_items = ( - (_('New Page'), new_page_params, can_add_sibling_page), - (_('New Sub Page'), new_sub_page_params, can_add_sub_page), - ) - - for title, params, has_perm in add_page_menu_modal_items: - params.update(language=self.toolbar.request_language) - add_page_menu.add_modal_item( - title, - url=add_url_parameters(add_page_url, params), - disabled=not has_perm, - ) - - add_page_menu.add_modal_item( - _('Duplicate this Page'), - url=add_url_parameters( - duplicate_page_url, - {'language': self.toolbar.request_language} - ), - disabled=not can_add_sibling_page, - ) - - # first break - current_page_menu.add_break(PAGE_MENU_FIRST_BREAK) - - # page edit - page_edit_url = '?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON') - current_page_menu.add_link_item( - _('Edit this Page'), - disabled=edit_mode, - url=page_edit_url - ) - - # page settings - page_settings_url = add_url_parameters( - page_settings_url, - language=self.toolbar.request_language - ) - settings_disabled = not edit_mode or not can_change - current_page_menu.add_modal_item( - _('Page settings'), - url=page_settings_url, - disabled=settings_disabled, - on_close=refresh - ) - - # advanced settings - advanced_url = add_url_parameters( - advanced_url, - language=self.toolbar.request_language - ) - can_change_advanced = self.page.has_advanced_settings_permission(self.request.user) - advanced_disabled = not edit_mode or not can_change_advanced - current_page_menu.add_modal_item( - _('Advanced settings'), - url=advanced_url, - disabled=advanced_disabled - ) - - # templates menu - if edit_mode: - if self.page.is_page_type: - action = admin_reverse('cms_pagetype_change_template', args=(self.page.pk,)) - else: - action = admin_reverse('cms_page_change_template', args=(self.page.pk,)) - - if can_change_advanced: - templates_menu = current_page_menu.get_or_create_menu( - 'templates', - _('Templates'), - disabled=not can_change, - ) - - for path, name in get_cms_setting('TEMPLATES'): - active = self.page.template == path - if path == TEMPLATE_INHERITANCE_MAGIC: - templates_menu.add_break(TEMPLATE_MENU_BREAK) - templates_menu.add_ajax_item( - name, - action=action, - data={'template': path}, - active=active, - on_success=refresh - ) - - # page type - if not self.page.is_page_type: - page_type_url = admin_reverse('cms_pagetype_add') - page_type_url = add_url_parameters( - page_type_url, - source=self.page.pk, - language=self.toolbar.request_language - ) - page_type_disabled = not edit_mode or not can_add_root_page - current_page_menu.add_modal_item( - _('Save as Page Type'), - page_type_url, - disabled=page_type_disabled - ) - - # second break - current_page_menu.add_break(PAGE_MENU_SECOND_BREAK) - - # permissions - if self.permissions_activated: - permissions_url = admin_reverse('cms_page_permissions', args=(self.page.pk,)) - permission_disabled = not edit_mode - - if not permission_disabled: - permission_disabled = not \ - page_permissions.user_can_change_page_permissions( - user=self.request.user, - page=self.page, - ) - current_page_menu.add_modal_item( - _('Permissions'), - url=permissions_url, - disabled=permission_disabled - ) - - if not self.page.is_page_type: - # dates settings - dates_url = admin_reverse('cms_page_dates', args=(self.page.pk,)) - current_page_menu.add_modal_item( - _('Publishing dates'), - url=dates_url, - disabled=(not edit_mode or not can_change), - ) - - # third break - current_page_menu.add_break(PAGE_MENU_THIRD_BREAK) - - # navigation toggle - nav_title = _('Hide in navigation') if self.page.in_navigation \ - else _('Display in navigation') - nav_action = admin_reverse( - 'cms_page_change_innavigation', - args=(self.page.pk,) - ) - current_page_menu.add_ajax_item( - nav_title, - action=nav_action, - disabled=(not edit_mode or not can_change), - on_success=refresh, - ) - - # publisher - if self.title and not self.page.is_page_type: - if self.title.published: - publish_title = _('Unpublish page') - publish_url = admin_reverse( - 'cms_page_unpublish', - args=(self.page.pk, self.current_lang) - ) - else: - publish_title = _('Publish page') - publish_url = admin_reverse( - 'cms_page_publish_page', - args=(self.page.pk, self.current_lang) - ) - - user_can_publish = user_can_publish_page(self.request.user, page=self.page) - current_page_menu.add_ajax_item( - publish_title, - action=publish_url, - disabled=not edit_mode or not user_can_publish, - on_success=refresh, - ) - - if self.current_lang and not self.page.is_page_type: - # revert to live - current_page_menu.add_break(PAGE_MENU_FOURTH_BREAK) - revert_action = admin_reverse( - 'cms_page_revert_to_live', - args=(self.page.pk, self.current_lang) - ) - revert_question = _('Are you sure you want to revert to live?') - # Only show this action if the page has pending changes and a public version - is_enabled = ( - edit_mode - and can_change - and self.page.is_dirty(self.current_lang) - and self.page.publisher_public - ) - current_page_menu.add_ajax_item( - _('Revert to live'), - action=revert_action, - question=revert_question, - disabled=not is_enabled, - on_success=refresh, - extra_classes=('cms-toolbar-revert',), - ) - - # last break - current_page_menu.add_break(PAGE_MENU_LAST_BREAK) - - # delete - if self.page.is_page_type: - delete_url = admin_reverse('cms_pagetype_delete', args=(self.page.pk,)) - else: - delete_url = admin_reverse('cms_page_delete', args=(self.page.pk,)) - delete_disabled = not edit_mode or not user_can_delete_page( - self.request.user, - page=self.page - ) - on_delete_redirect_url = self.get_on_delete_redirect_url() - current_page_menu.add_modal_item( - _('Delete page'), - url=delete_url, - on_close=on_delete_redirect_url, - disabled=delete_disabled - ) + return None PageToolbar.add_page_menu = add_page_menu From a781820728ffdcf83d09bd06362474bed3e38d17 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 16 Jul 2022 13:14:36 +0545 Subject: [PATCH 1024/1137] remove language menu --- gsoc/cms_toolbars.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 8a631ca7..6153a8a5 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -166,7 +166,6 @@ def populate(self): self.init_from_request() self.clipboard = self.request.toolbar.user_settings.clipboard self.add_admin_menu() - self.add_language_menu() self.add_goto_blog_button() From c9e3f2418d4a674e136ddfd44062fdf0b17dbe48 Mon Sep 17 00:00:00 2001 From: Diwash Date: Wed, 20 Jul 2022 12:55:41 +0545 Subject: [PATCH 1025/1137] set APP_DIRS on TEMPLATES settings --- gsoc/settings.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gsoc/settings.py b/gsoc/settings.py index fe7dbc60..37d1ba57 100644 --- a/gsoc/settings.py +++ b/gsoc/settings.py @@ -66,6 +66,7 @@ def gettext(s): TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, "DIRS": [ os.path.join(BASE_DIR, "gsoc", "templates"), os.path.join(BASE_DIR, "blogs_list", "templates"), @@ -87,10 +88,6 @@ def gettext(s): "gsoc.context_processors.recaptcha_site_key", "gsoc.context_processors.blog_slug", ], - "loaders": [ - "django.template.loaders.filesystem.Loader", - "django.template.loaders.app_directories.Loader", - ], }, } ] From f59989893995712fe72e096c11452f87e0c9521b Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 24 Jul 2022 12:54:00 +0545 Subject: [PATCH 1026/1137] set - to + for pre blog reminder builder --- gsoc/models.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 814c2c06..6d8f5a43 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -208,24 +208,24 @@ class DaysConf(models.Model): # days for reminder -try: - PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") - POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") - POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") +# try: +PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") +POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") +POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") - BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") - UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") +BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") +UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") - REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") -except Exception: - PRE_BLOG_REMINDER = -3 - POST_BLOG_REMINDER_FIRST = -1 - POST_BLOG_REMINDER_SECOND = 3 +REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") +# except Exception: +# PRE_BLOG_REMINDER = -3 +# POST_BLOG_REMINDER_FIRST = -1 +# POST_BLOG_REMINDER_SECOND = 3 - BLOG_POST_DUE_REMINDER = -6 - UPDATE_BLOG_COUNTER = 6 +# BLOG_POST_DUE_REMINDER = -6 +# UPDATE_BLOG_COUNTER = 6 - REGLINK_REMINDER = 3 +# REGLINK_REMINDER = 3 class SubOrg(models.Model): @@ -849,7 +849,7 @@ def create_builders(self): def save(self, *args, **kwargs): try: pre = Builder.objects.get(id=self.pre_blog_reminder_builder.id) - pre.activation_date = self.date - datetime.timedelta( + pre.activation_date = self.date + datetime.timedelta( days=PRE_BLOG_REMINDER.days ) pre.save() From f9439d5754b176aec72dc4e9750a0c4c86b2043c Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 24 Jul 2022 12:55:25 +0545 Subject: [PATCH 1027/1137] update post blog reminder days value --- data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data.json b/data.json index b6e42d5c..3e4be2e8 100644 --- a/data.json +++ b/data.json @@ -6966,7 +6966,7 @@ "pk": 2, "fields": { "title": "POST_BLOG_REMINDER_FIRST", - "days": -1, + "days": 1, "disabled": false } }, From c078e85c8a7b6160e7b9bb292c25e694f1920c9b Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 24 Jul 2022 13:37:07 +0545 Subject: [PATCH 1028/1137] handle error if no DaysConf in Database --- gsoc/models.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 6d8f5a43..6f9f14b1 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -208,24 +208,24 @@ class DaysConf(models.Model): # days for reminder -# try: -PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") -POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") -POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") +try: + PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") + POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") + POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") -BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") -UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") + BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") + UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") -REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") -# except Exception: -# PRE_BLOG_REMINDER = -3 -# POST_BLOG_REMINDER_FIRST = -1 -# POST_BLOG_REMINDER_SECOND = 3 + REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") +except Exception: + PRE_BLOG_REMINDER = DaysConf(days=-3) + POST_BLOG_REMINDER_FIRST = DaysConf(days=1) + POST_BLOG_REMINDER_SECOND = DaysConf(days=3) -# BLOG_POST_DUE_REMINDER = -6 -# UPDATE_BLOG_COUNTER = 6 + BLOG_POST_DUE_REMINDER = DaysConf(days=-6) + UPDATE_BLOG_COUNTER = DaysConf(days=6) -# REGLINK_REMINDER = 3 + REGLINK_REMINDER = DaysConf(days=-3) class SubOrg(models.Model): From 04ae5920e4a48363d2869446fa9c6b6bc295b33b Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 24 Jul 2022 13:50:55 +0545 Subject: [PATCH 1029/1137] remove exception handling for days conf --- gsoc/models.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 6f9f14b1..06d554ab 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -207,25 +207,14 @@ class DaysConf(models.Model): disabled = models.BooleanField(default=False) -# days for reminder -try: - PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") - POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") - POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") +PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") +POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") +POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") - BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") - UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") +BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") +UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") - REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") -except Exception: - PRE_BLOG_REMINDER = DaysConf(days=-3) - POST_BLOG_REMINDER_FIRST = DaysConf(days=1) - POST_BLOG_REMINDER_SECOND = DaysConf(days=3) - - BLOG_POST_DUE_REMINDER = DaysConf(days=-6) - UPDATE_BLOG_COUNTER = DaysConf(days=6) - - REGLINK_REMINDER = DaysConf(days=-3) +REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") class SubOrg(models.Model): From c66ba744d23e8b02a676027af4a06a946f5eb890 Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 28 Jul 2022 12:16:47 +0545 Subject: [PATCH 1030/1137] fix standard date not adding to calendar --- gsoc/common/utils/build_tasks.py | 2 +- gsoc/models.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index fac36e03..dc76ee68 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -318,7 +318,7 @@ def build_add_start_to_calendar(builder): ) -def build_add_end_to_calendar(builder): +def build_add_end_standard_to_calendar(builder): data = json.loads(builder.data) creds = getCreds() if creds: diff --git a/gsoc/models.py b/gsoc/models.py index ef2f341d..ab5556e4 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1028,7 +1028,7 @@ class GsocEndDateDefault(models.Model): def add_to_calendar(self): builder_data = json.dumps({ "id": self.id, - "title": "GSoC End (Standar)", + "title": "GSoC End (Standard)", "date": str(self.date.strftime('%Y-%m-%d')), "event_id": self.event_id }) @@ -1700,6 +1700,12 @@ def due_date_add_to_calendar(sender, instance, **kwargs): instance.add_to_calendar() +# Add GSoCEndDateDefault to Google Calendar +@receiver(models.signals.post_save, sender=GsocEndDateDefault) +def due_date_add_to_calendar(sender, instance, **kwargs): + instance.add_to_calendar() + + # Add GSoCStartDate to Google Calendar @receiver(models.signals.post_save, sender=GsocStartDate) def due_date_add_to_calendar(sender, instance, **kwargs): From 379671e6761b8cf3f69b4b0821830381851da89d Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 28 Jul 2022 12:33:06 +0545 Subject: [PATCH 1031/1137] generate 22 bpdds at max --- gsoc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/models.py b/gsoc/models.py index ab5556e4..418337b3 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1820,7 +1820,7 @@ def auto_bpdd(sender, instance, **kwargs): ) dates = [instance.start + datetime.timedelta(days=i) for i in range( 0, - (end_date.date - start_date.date).days, + (end_date.date - start_date.date).days - 14, instance.recurDays )] From d9f04f734e526de3357f47bcb218138e200bc9e9 Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 28 Jul 2022 15:23:42 +0545 Subject: [PATCH 1032/1137] add declaration for exam schedulers --- gsoc/models.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gsoc/models.py b/gsoc/models.py index 418337b3..d3c38895 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1838,3 +1838,15 @@ def auto_bpdd(sender, instance, **kwargs): timeline=instance.timeline, generator=instance ) + + +# Schedule midterm reminder when Timeline is created +@receiver(models.signals.post_save, sender=Timeline) +def schedule_midterm_reminder(sender, instance, **kwargs): + pass + + +# Schedule final reminder when Timeline is created +@receiver(models.signals.post_save, sender=Timeline) +def schedule_final_reminder(sender, instance, **kwargs): + pass From 52412b4124f8a05224d33f133346279b9c3551bb Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 28 Jul 2022 20:29:24 +0545 Subject: [PATCH 1033/1137] add builder to send final eval reminders --- gsoc/common/utils/build_tasks.py | 29 ++++++++++++++++ gsoc/models.py | 45 +++++++++++++++++++++---- gsoc/templates/email/exam_reminder.html | 1 + 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 gsoc/templates/email/exam_reminder.html diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 2ec46883..45dd7833 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -347,3 +347,32 @@ def build_add_end_standard_to_calendar(builder): f"Please get the Access Token: " + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" ) + + +def build_final_term_reminder(builder): + try: + data = json.loads(builder.data) + end_date = data["date"] + gsoc_year = GsocYear.objects.latest('gsoc_year') + profiles = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role__in=[1] if data["admin"] else [2,3], + gsoc_end=end_date + ).all() + for profile in profiles: + template_data = { + "exam": "final", + "date": end_date, + } + + scheduler_data = build_send_mail_json( + profile.user.email, + template="exam_reminder.html", + subject=f"Final reminder", + template_data=template_data, + ) + + Scheduler.objects.create(command="send_email", data=scheduler_data) + return None + except Exception as e: + return str(e) diff --git a/gsoc/models.py b/gsoc/models.py index e64ae66e..2462b863 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1829,13 +1829,46 @@ def auto_bpdd(sender, instance, **kwargs): ) -# Schedule midterm reminder when Timeline is created -@receiver(models.signals.post_save, sender=Timeline) -def schedule_midterm_reminder(sender, instance, **kwargs): - pass +# build schedulers for midterm reminder when Timeline is created +@receiver(models.signals.post_save, sender=GsocEndDate) +def build_schedule_finalterm_reminder(sender, instance, **kwargs): + start_date = GsocStartDate.objects.latest('date') + end_date = GsocEndDate.objects.latest('date') + + # notify mentors+students 4 days before due + notify_date = end_date.date - datetime.timedelta(days=4) + for i in range(0, 7): + builder_data = json.dumps({ + "date": str(notify_date), + "title": "Final term evaluation reminder", + "admin": False + }) + Builder.objects.create( + category="build_final_term_reminder", + activation_date=start_date.date, + data=builder_data + ) + + notify_date -= datetime.timedelta(days=14) + + # notify admins 2 days before due + notify_date = end_date.date - datetime.timedelta(days=2) + for i in range(0, 7): + builder_data = json.dumps({ + "date": str(notify_date), + "title": "Final term evaluation reminder", + "admin": True + }) + Builder.objects.create( + category="build_final_term_reminder", + activation_date=start_date.date, + data=builder_data + ) + + notify_date -= datetime.timedelta(days=14) -# Schedule final reminder when Timeline is created +# build schedulers for final reminder when Timeline is created @receiver(models.signals.post_save, sender=Timeline) -def schedule_final_reminder(sender, instance, **kwargs): +def build_schedule_final_reminder(sender, instance, **kwargs): pass diff --git a/gsoc/templates/email/exam_reminder.html b/gsoc/templates/email/exam_reminder.html new file mode 100644 index 00000000..525e7fa5 --- /dev/null +++ b/gsoc/templates/email/exam_reminder.html @@ -0,0 +1 @@ +{{ exam }} exam reminder. Kindly check your Gsoc dashboard. \ No newline at end of file From ae05aa55c081612af700a5cf0a4a995bc7a0de28 Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 28 Jul 2022 21:30:16 +0545 Subject: [PATCH 1034/1137] add mid term reminder builders --- gsoc/common/utils/build_tasks.py | 2 +- gsoc/models.py | 51 +++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 45dd7833..ff821839 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -349,7 +349,7 @@ def build_add_end_standard_to_calendar(builder): ) -def build_final_term_reminder(builder): +def build_exam_reminder(builder): try: data = json.loads(builder.data) end_date = data["date"] diff --git a/gsoc/models.py b/gsoc/models.py index 2462b863..3d38e992 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1829,7 +1829,7 @@ def auto_bpdd(sender, instance, **kwargs): ) -# build schedulers for midterm reminder when Timeline is created +# build schedulers for final reminder when Timeline is created @receiver(models.signals.post_save, sender=GsocEndDate) def build_schedule_finalterm_reminder(sender, instance, **kwargs): start_date = GsocStartDate.objects.latest('date') @@ -1838,8 +1838,9 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): # notify mentors+students 4 days before due notify_date = end_date.date - datetime.timedelta(days=4) for i in range(0, 7): + builder_data = json.dumps({ - "date": str(notify_date), + "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "title": "Final term evaluation reminder", "admin": False }) @@ -1855,7 +1856,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): notify_date = end_date.date - datetime.timedelta(days=2) for i in range(0, 7): builder_data = json.dumps({ - "date": str(notify_date), + "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "title": "Final term evaluation reminder", "admin": True }) @@ -1868,7 +1869,43 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): notify_date -= datetime.timedelta(days=14) -# build schedulers for final reminder when Timeline is created -@receiver(models.signals.post_save, sender=Timeline) -def build_schedule_final_reminder(sender, instance, **kwargs): - pass +# build schedulers for midterm reminder when Timeline is created +@receiver(models.signals.post_save, sender=GsocEndDate) +def build_schedule_midterm_reminder(sender, instance, **kwargs): + start_date = GsocStartDate.objects.latest('date') + end_date = GsocEndDate.objects.latest('date') + + # notify mentors+students 4 days before due + gap = (end_date.date - datetime.timedelta(days=7) - start_date.date) + half_gap = gap.days // 2 - 4 + notify_date = start_date.date + datetime.timedelta(days=half_gap) + for i in range(0, 7): + builder_data = json.dumps({ + "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), + "title": "Mid term evaluation reminder", + "admin": False + }) + Builder.objects.create( + category="build_exam_reminder", + activation_date=start_date.date, + data=builder_data + ) + + notify_date -= datetime.timedelta(days=7) + + # notify admins 2 days before due + half_gap = gap.days // 2 - 2 + notify_date = start_date.date + datetime.timedelta(days=half_gap) + for i in range(0, 7): + builder_data = json.dumps({ + "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), + "title": "Mid term evaluation reminder", + "admin": True + }) + Builder.objects.create( + category="build_exam_reminder", + activation_date=start_date.date, + data=builder_data + ) + + notify_date -= datetime.timedelta(days=7) From 694fc085ce6676c5a4da30d5b5fe065782365125 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 31 Jul 2022 12:23:28 +0545 Subject: [PATCH 1035/1137] filter students by gsoc_end gte due_date --- gsoc/common/utils/build_tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index ff821839..45a161d8 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -29,7 +29,7 @@ def build_pre_blog_reminders(builder): profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=3, - gsoc_end__lte=due_date.date + gsoc_end__gte=due_date.date ).all() categories = ((0, "Weekly Check-In"), (1, "Blog Post")) category = categories[due_date.category][1] @@ -73,7 +73,7 @@ def build_post_blog_reminders(builder): profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=3, - gsoc_end__lte=due_date.date + gsoc_end__gte=due_date.date ).all() for profile in profiles: if profile.current_blog_count > blogs_count and not ( From 18cfcd9d51481324d0e68b3bb8bb5da1b804df0f Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 31 Jul 2022 12:42:11 +0545 Subject: [PATCH 1036/1137] add exam reminder builder --- gsoc/common/utils/build_tasks.py | 75 ++++++++++++++++++++++++++++---- gsoc/models.py | 4 +- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 45a161d8..9cb47a65 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -1,4 +1,6 @@ +from datetime import datetime, timedelta import json +from tracemalloc import start import uuid from django.utils import timezone @@ -349,26 +351,37 @@ def build_add_end_standard_to_calendar(builder): ) -def build_exam_reminder(builder): +def build_final_term_reminder(builder): try: data = json.loads(builder.data) - end_date = data["date"] + date = data["date"] gsoc_year = GsocYear.objects.latest('gsoc_year') - profiles = UserProfile.objects.filter( - gsoc_year=gsoc_year, - role__in=[1] if data["admin"] else [2,3], - gsoc_end=end_date - ).all() + is_admin = data["admin"] + + if is_admin: + end_date = date + timedelta(days=2) + profiles = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role=1 + ).all() + else: + end_date = date + timedelta(days=4) + profiles = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role__in=[2, 3], + gsoc_end=end_date + ).all() + for profile in profiles: template_data = { - "exam": "final", + "exam": "Final", "date": end_date, } scheduler_data = build_send_mail_json( profile.user.email, template="exam_reminder.html", - subject=f"Final reminder", + subject=f"Final evaluation reminder", template_data=template_data, ) @@ -376,3 +389,47 @@ def build_exam_reminder(builder): return None except Exception as e: return str(e) + + +def build_mid_term_reminder(builder): + try: + data = json.loads(builder.data) + date = data["date"] + start_date = GsocStartDate.objects.latest('date') + gsoc_year = GsocYear.objects.latest('gsoc_year') + is_admin = data["admin"] + + if is_admin: + exam_date = date + timedelta(days=2) + gap = (date - start_date).days * 2 + 2 + 7 + profiles = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role=1 + ).all() + else: + exam_date = date + timedelta(days=4) + gap = (date - start_date).days * 2 + 4 + 7 + end_date = start_date + timedelta(days=gap) + profiles = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role__in=[2, 3], + gsoc_end=end_date + ).all() + + for profile in profiles: + template_data = { + "exam": "Midterm", + "date": exam_date, + } + + scheduler_data = build_send_mail_json( + profile.user.email, + template="exam_reminder.html", + subject=f"Midterm evaluation reminder", + template_data=template_data, + ) + + Scheduler.objects.create(command="send_email", data=scheduler_data) + return None + except Exception as e: + return str(e) \ No newline at end of file diff --git a/gsoc/models.py b/gsoc/models.py index 3d38e992..e0ad28e6 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1886,7 +1886,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): "admin": False }) Builder.objects.create( - category="build_exam_reminder", + category="build_mid_term_reminder", activation_date=start_date.date, data=builder_data ) @@ -1903,7 +1903,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): "admin": True }) Builder.objects.create( - category="build_exam_reminder", + category="build_mid_term_reminder", activation_date=start_date.date, data=builder_data ) From e136dc86ff20ff07d89892570b8a68df5e168b85 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sun, 31 Jul 2022 13:56:09 +0545 Subject: [PATCH 1037/1137] datetime issue on scheduler creation --- gsoc/common/utils/build_tasks.py | 18 +++++++++--------- gsoc/models.py | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 9cb47a65..50e48bb6 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -359,13 +359,13 @@ def build_final_term_reminder(builder): is_admin = data["admin"] if is_admin: - end_date = date + timedelta(days=2) + end_date = datetime.fromisoformat(date) + timedelta(days=9) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=1 ).all() else: - end_date = date + timedelta(days=4) + end_date = datetime.fromisoformat(date) + timedelta(days=11) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role__in=[2, 3], @@ -375,7 +375,7 @@ def build_final_term_reminder(builder): for profile in profiles: template_data = { "exam": "Final", - "date": end_date, + "date": str(end_date), } scheduler_data = build_send_mail_json( @@ -400,16 +400,16 @@ def build_mid_term_reminder(builder): is_admin = data["admin"] if is_admin: - exam_date = date + timedelta(days=2) - gap = (date - start_date).days * 2 + 2 + 7 + exam_date = datetime.fromisoformat(date) + timedelta(days=2) + gap = (datetime.fromisoformat(date) - start_date.date).days * 2 + 2 + 7 profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=1 ).all() else: - exam_date = date + timedelta(days=4) - gap = (date - start_date).days * 2 + 4 + 7 - end_date = start_date + timedelta(days=gap) + exam_date = datetime.fromisoformat(date) + timedelta(days=4) + gap = (datetime.fromisoformat(date) - start_date.date).days * 2 + 4 + 7 + end_date = start_date.date + timedelta(days=gap) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role__in=[2, 3], @@ -419,7 +419,7 @@ def build_mid_term_reminder(builder): for profile in profiles: template_data = { "exam": "Midterm", - "date": exam_date, + "date": str(exam_date), } scheduler_data = build_send_mail_json( diff --git a/gsoc/models.py b/gsoc/models.py index e0ad28e6..a98ccf09 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1846,7 +1846,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data ) @@ -1862,7 +1862,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data ) @@ -1876,7 +1876,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): end_date = GsocEndDate.objects.latest('date') # notify mentors+students 4 days before due - gap = (end_date.date - datetime.timedelta(days=7) - start_date.date) + gap = end_date.date - datetime.timedelta(days=7) - start_date.date half_gap = gap.days // 2 - 4 notify_date = start_date.date + datetime.timedelta(days=half_gap) for i in range(0, 7): @@ -1887,7 +1887,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data ) @@ -1904,7 +1904,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data ) From c1053b0e8a1dd3418fe4ad115c97b3dfb4d06b65 Mon Sep 17 00:00:00 2001 From: Diwash Date: Mon, 1 Aug 2022 21:56:58 +0545 Subject: [PATCH 1038/1137] fix date vs datetime object issue --- gsoc/common/utils/build_tasks.py | 8 ++++---- gsoc/models.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 50e48bb6..b7b6a44b 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -400,15 +400,15 @@ def build_mid_term_reminder(builder): is_admin = data["admin"] if is_admin: - exam_date = datetime.fromisoformat(date) + timedelta(days=2) - gap = (datetime.fromisoformat(date) - start_date.date).days * 2 + 2 + 7 + exam_date = datetime.strptime(date, "%Y-%m-%d").date() + timedelta(days=2) + gap = (datetime.strptime(date, "%Y-%m-%d").date() - start_date.date).days * 2 + 2 + 7 profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=1 ).all() else: - exam_date = datetime.fromisoformat(date) + timedelta(days=4) - gap = (datetime.fromisoformat(date) - start_date.date).days * 2 + 4 + 7 + exam_date = datetime.strptime(date, "%Y-%m-%d").date() + timedelta(days=4) + gap = (datetime.strptime(date, "%Y-%m-%d").date() - start_date.date).days * 2 + 4 + 7 end_date = start_date.date + timedelta(days=gap) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, diff --git a/gsoc/models.py b/gsoc/models.py index a98ccf09..83bfae5c 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1836,7 +1836,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): end_date = GsocEndDate.objects.latest('date') # notify mentors+students 4 days before due - notify_date = end_date.date - datetime.timedelta(days=4) + notify_date = end_date.date - datetime.timedelta(days=11) for i in range(0, 7): builder_data = json.dumps({ @@ -1853,7 +1853,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): notify_date -= datetime.timedelta(days=14) # notify admins 2 days before due - notify_date = end_date.date - datetime.timedelta(days=2) + notify_date = end_date.date - datetime.timedelta(days=9) for i in range(0, 7): builder_data = json.dumps({ "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), From 3159d94c8dd9ea8c186347d77e94c5b61a1b2465 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 2 Aug 2022 20:19:06 +0545 Subject: [PATCH 1039/1137] add todos --- gsoc/common/utils/build_tasks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index b7b6a44b..3d085011 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -432,4 +432,8 @@ def build_mid_term_reminder(builder): Scheduler.objects.create(command="send_email", data=scheduler_data) return None except Exception as e: - return str(e) \ No newline at end of file + return str(e) + + # TODO: different reminder for mentor and student on final term + # TODO: fix midterm email not sending + # TODO: fix send_to not working \ No newline at end of file From ad96232e44f862d8cea7de2247078db263d61cb1 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 2 Aug 2022 20:56:56 +0545 Subject: [PATCH 1040/1137] fix issues with mid term reminder builder --- gsoc/common/utils/build_tasks.py | 21 ++++++++++++++------- gsoc/models.py | 25 +++++++++++++++---------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 3d085011..effcb55a 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -394,26 +394,33 @@ def build_final_term_reminder(builder): def build_mid_term_reminder(builder): try: data = json.loads(builder.data) - date = data["date"] + end_date = data["end_date"] start_date = GsocStartDate.objects.latest('date') gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] if is_admin: - exam_date = datetime.strptime(date, "%Y-%m-%d").date() + timedelta(days=2) - gap = (datetime.strptime(date, "%Y-%m-%d").date() - start_date.date).days * 2 + 2 + 7 + gap = datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date + half_gap = gap.days // 2 - 2 + date = start_date.date + timedelta(days=half_gap) + + exam_date = date + timedelta(days=2) + profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=1 ).all() else: - exam_date = datetime.strptime(date, "%Y-%m-%d").date() + timedelta(days=4) - gap = (datetime.strptime(date, "%Y-%m-%d").date() - start_date.date).days * 2 + 4 + 7 - end_date = start_date.date + timedelta(days=gap) + gap = datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date + half_gap = gap.days // 2 - 4 + date = start_date.date + timedelta(days=half_gap) + + exam_date = date + timedelta(days=4) + profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role__in=[2, 3], - gsoc_end=end_date + gsoc_end=datetime.strptime(end_date, "%Y-%m-%d").date() ).all() for profile in profiles: diff --git a/gsoc/models.py b/gsoc/models.py index 83bfae5c..d746f4fe 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1873,15 +1873,18 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): @receiver(models.signals.post_save, sender=GsocEndDate) def build_schedule_midterm_reminder(sender, instance, **kwargs): start_date = GsocStartDate.objects.latest('date') - end_date = GsocEndDate.objects.latest('date') + end_date_max = GsocEndDate.objects.latest('date') + end_date = end_date_max.date # notify mentors+students 4 days before due - gap = end_date.date - datetime.timedelta(days=7) - start_date.date - half_gap = gap.days // 2 - 4 - notify_date = start_date.date + datetime.timedelta(days=half_gap) + # gap = end_date.date - datetime.timedelta(days=7) - start_date.date + # half_gap = gap.days // 2 - 4 + # notify_date = start_date.date + datetime.timedelta(days=half_gap) + for i in range(0, 7): builder_data = json.dumps({ - "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), + # "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), + "end_date": str(end_date), "title": "Mid term evaluation reminder", "admin": False }) @@ -1891,14 +1894,16 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): data=builder_data ) - notify_date -= datetime.timedelta(days=7) + end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) # notify admins 2 days before due - half_gap = gap.days // 2 - 2 - notify_date = start_date.date + datetime.timedelta(days=half_gap) + end_date = end_date_max.date + # half_gap = gap.days // 2 - 2 + # notify_date = start_date.date + datetime.timedelta(days=half_gap) for i in range(0, 7): builder_data = json.dumps({ - "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), + # "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), + "end_date": str(end_date), "title": "Mid term evaluation reminder", "admin": True }) @@ -1908,4 +1913,4 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): data=builder_data ) - notify_date -= datetime.timedelta(days=7) + end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) From 4e3238d3c8f4c4c3e1ad4821c1add27bbf50fb5c Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 2 Aug 2022 21:06:07 +0545 Subject: [PATCH 1041/1137] midterm scheduler working good --- gsoc/common/utils/build_tasks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index effcb55a..6762a4b0 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -400,9 +400,9 @@ def build_mid_term_reminder(builder): is_admin = data["admin"] if is_admin: - gap = datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date - half_gap = gap.days // 2 - 2 - date = start_date.date + timedelta(days=half_gap) + date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 + # half_gap = gap.days // 2 - 2 + # date = start_date.date + timedelta(days=half_gap) exam_date = date + timedelta(days=2) @@ -411,9 +411,9 @@ def build_mid_term_reminder(builder): role=1 ).all() else: - gap = datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date - half_gap = gap.days // 2 - 4 - date = start_date.date + timedelta(days=half_gap) + date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 + # half_gap = gap.days // 2 - 4 + # date = start_date.date + timedelta(days=half_gap) exam_date = date + timedelta(days=4) From 7901cfa4929bbf363a374922dc645d60e0eb6199 Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 4 Aug 2022 20:19:39 +0545 Subject: [PATCH 1042/1137] clean midterm reminder builder --- gsoc/common/utils/build_tasks.py | 17 ++++------------- gsoc/models.py | 21 ++++----------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 6762a4b0..2469c8f9 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -399,28 +399,19 @@ def build_mid_term_reminder(builder): gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] - if is_admin: - date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 - # half_gap = gap.days // 2 - 2 - # date = start_date.date + timedelta(days=half_gap) + date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 + if is_admin: exam_date = date + timedelta(days=2) - profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role=1 + role__in=[1,2] ).all() else: - date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 - # half_gap = gap.days // 2 - 4 - # date = start_date.date + timedelta(days=half_gap) - exam_date = date + timedelta(days=4) - profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role__in=[2, 3], - gsoc_end=datetime.strptime(end_date, "%Y-%m-%d").date() + role=2, ).all() for profile in profiles: diff --git a/gsoc/models.py b/gsoc/models.py index d746f4fe..4be46bd9 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1872,40 +1872,27 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): # build schedulers for midterm reminder when Timeline is created @receiver(models.signals.post_save, sender=GsocEndDate) def build_schedule_midterm_reminder(sender, instance, **kwargs): - start_date = GsocStartDate.objects.latest('date') end_date_max = GsocEndDate.objects.latest('date') end_date = end_date_max.date - # notify mentors+students 4 days before due - # gap = end_date.date - datetime.timedelta(days=7) - start_date.date - # half_gap = gap.days // 2 - 4 - # notify_date = start_date.date + datetime.timedelta(days=half_gap) - for i in range(0, 7): + # notify mentors+admins 4 days before due builder_data = json.dumps({ - # "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "end_date": str(end_date), "title": "Mid term evaluation reminder", - "admin": False + "admin": True }) Builder.objects.create( category="build_mid_term_reminder", activation_date=datetime.datetime.now(), data=builder_data ) - - end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) - # notify admins 2 days before due - end_date = end_date_max.date - # half_gap = gap.days // 2 - 2 - # notify_date = start_date.date + datetime.timedelta(days=half_gap) - for i in range(0, 7): + # notify mentors 2 days before due builder_data = json.dumps({ - # "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "end_date": str(end_date), "title": "Mid term evaluation reminder", - "admin": True + "admin": False }) Builder.objects.create( category="build_mid_term_reminder", From ae2ac10302596912d96e2d8a92f96d76ce47785b Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 4 Aug 2022 20:32:11 +0545 Subject: [PATCH 1043/1137] correct dates for reminders --- gsoc/common/utils/build_tasks.py | 13 ++++--------- gsoc/models.py | 12 ++++++------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 2469c8f9..6f2df3d3 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -359,17 +359,16 @@ def build_final_term_reminder(builder): is_admin = data["admin"] if is_admin: - end_date = datetime.fromisoformat(date) + timedelta(days=9) + end_date = datetime.fromisoformat(date) + timedelta(days=2) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role=1 + role__in=[1,2] ).all() else: - end_date = datetime.fromisoformat(date) + timedelta(days=11) + end_date = datetime.fromisoformat(date) + timedelta(days=4) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role__in=[2, 3], - gsoc_end=end_date + role=2 ).all() for profile in profiles: @@ -431,7 +430,3 @@ def build_mid_term_reminder(builder): return None except Exception as e: return str(e) - - # TODO: different reminder for mentor and student on final term - # TODO: fix midterm email not sending - # TODO: fix send_to not working \ No newline at end of file diff --git a/gsoc/models.py b/gsoc/models.py index 4be46bd9..55a88ed5 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1835,14 +1835,14 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): start_date = GsocStartDate.objects.latest('date') end_date = GsocEndDate.objects.latest('date') - # notify mentors+students 4 days before due - notify_date = end_date.date - datetime.timedelta(days=11) + # notify mentors+admins 4 days before due + notify_date = end_date.date - datetime.timedelta(days=4) for i in range(0, 7): builder_data = json.dumps({ "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "title": "Final term evaluation reminder", - "admin": False + "admin": True }) Builder.objects.create( category="build_final_term_reminder", @@ -1852,13 +1852,13 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): notify_date -= datetime.timedelta(days=14) - # notify admins 2 days before due - notify_date = end_date.date - datetime.timedelta(days=9) + # notify mentors 2 days before due + notify_date = end_date.date - datetime.timedelta(days=2) for i in range(0, 7): builder_data = json.dumps({ "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "title": "Final term evaluation reminder", - "admin": True + "admin": False }) Builder.objects.create( category="build_final_term_reminder", From 6dc32fc4b303daa79de3867da5dcdc1f312a6c6d Mon Sep 17 00:00:00 2001 From: Diwash Date: Thu, 4 Aug 2022 21:07:54 +0545 Subject: [PATCH 1044/1137] send reminder to ADMINS and update reminder email --- gsoc/common/utils/build_tasks.py | 29 +++++++++++++++++++++++-- gsoc/templates/email/exam_reminder.html | 8 ++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 6f2df3d3..b103b3a4 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -1,10 +1,9 @@ from datetime import datetime, timedelta import json -from tracemalloc import start import uuid from django.utils import timezone -from django.conf import settings +from gsoc.settings import ADMINS from gsoc.models import ( Event, @@ -364,6 +363,19 @@ def build_final_term_reminder(builder): gsoc_year=gsoc_year, role__in=[1,2] ).all() + + template_data = { + "exam": "Final", + "date": str(end_date), + } + + scheduler_data = build_send_mail_json( + ADMINS, + template="exam_reminder.html", + subject="Final evaluation reminder", + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) else: end_date = datetime.fromisoformat(date) + timedelta(days=4) profiles = UserProfile.objects.filter( @@ -406,6 +418,19 @@ def build_mid_term_reminder(builder): gsoc_year=gsoc_year, role__in=[1,2] ).all() + + template_data = { + "exam": "Midterm", + "date": str(exam_date), + } + + scheduler_data = build_send_mail_json( + ADMINS, + template="exam_reminder.html", + subject="Midterm evaluation reminder", + template_data=template_data, + ) + Scheduler.objects.create(command="send_email", data=scheduler_data) else: exam_date = date + timedelta(days=4) profiles = UserProfile.objects.filter( diff --git a/gsoc/templates/email/exam_reminder.html b/gsoc/templates/email/exam_reminder.html index 525e7fa5..98121198 100644 --- a/gsoc/templates/email/exam_reminder.html +++ b/gsoc/templates/email/exam_reminder.html @@ -1 +1,7 @@ -{{ exam }} exam reminder. Kindly check your Gsoc dashboard. \ No newline at end of file +This is a reminder for your {{ exam }} evaluation due by {{ date }}. +Kindly visit your GSoC Dashboard and complete the required evaluations. + +If you have any issues please DO NOT reply to this email, and email gsoc-admins@python.org + +Thank you +Python GSoC Team! From 6bb9d0559a033b199494330c447527455f89b0e0 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 13:10:40 +0545 Subject: [PATCH 1045/1137] update all terminals builders date --- gsoc/common/utils/build_tasks.py | 14 +++++++++----- gsoc/models.py | 7 ++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index b103b3a4..0116d24f 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta import json import uuid +from django.conf import settings from django.utils import timezone from gsoc.settings import ADMINS @@ -356,12 +357,13 @@ def build_final_term_reminder(builder): date = data["date"] gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] + end_date = datetime.fromisoformat(date) + timedelta(days=4) if is_admin: - end_date = datetime.fromisoformat(date) + timedelta(days=2) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role__in=[1,2] + role__in=[1,2], + gsoc_end=end_date ).all() template_data = { @@ -377,10 +379,10 @@ def build_final_term_reminder(builder): ) Scheduler.objects.create(command="send_email", data=scheduler_data) else: - end_date = datetime.fromisoformat(date) + timedelta(days=4) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role=2 + role=2, + gsoc_end=end_date ).all() for profile in profiles: @@ -416,7 +418,8 @@ def build_mid_term_reminder(builder): exam_date = date + timedelta(days=2) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, - role__in=[1,2] + role__in=[1,2], + gsoc_end=end_date ).all() template_data = { @@ -436,6 +439,7 @@ def build_mid_term_reminder(builder): profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=2, + gsoc_end=end_date ).all() for profile in profiles: diff --git a/gsoc/models.py b/gsoc/models.py index 55a88ed5..b88160ac 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1832,7 +1832,6 @@ def auto_bpdd(sender, instance, **kwargs): # build schedulers for final reminder when Timeline is created @receiver(models.signals.post_save, sender=GsocEndDate) def build_schedule_finalterm_reminder(sender, instance, **kwargs): - start_date = GsocStartDate.objects.latest('date') end_date = GsocEndDate.objects.latest('date') # notify mentors+admins 4 days before due @@ -1885,7 +1884,8 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): Builder.objects.create( category="build_mid_term_reminder", activation_date=datetime.datetime.now(), - data=builder_data + data=builder_data, + timeline=instance.timeline ) # notify mentors 2 days before due @@ -1897,7 +1897,8 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): Builder.objects.create( category="build_mid_term_reminder", activation_date=datetime.datetime.now(), - data=builder_data + data=builder_data, + timeline=instance.timeline ) end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) From 71ac32605141a6fc929994967249b47cbc71de97 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 13:51:33 +0545 Subject: [PATCH 1046/1137] fix exam dates on midterm schedulers --- gsoc/common/utils/build_tasks.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 0116d24f..13972a75 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -357,9 +357,9 @@ def build_final_term_reminder(builder): date = data["date"] gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] - end_date = datetime.fromisoformat(date) + timedelta(days=4) - + if is_admin: + end_date = datetime.fromisoformat(date) + timedelta(days=4) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role__in=[1,2], @@ -377,8 +377,9 @@ def build_final_term_reminder(builder): subject="Final evaluation reminder", template_data=template_data, ) - Scheduler.objects.create(command="send_email", data=scheduler_data) + # Scheduler.objects.create(command="send_email", data=scheduler_data) else: + end_date = datetime.fromisoformat(date) + timedelta(days=2) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=2, @@ -408,14 +409,16 @@ def build_mid_term_reminder(builder): try: data = json.loads(builder.data) end_date = data["end_date"] + end_date_max = GsocEndDate.objects.latest('date') start_date = GsocStartDate.objects.latest('date') gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 + exam_date = date + timedelta(days=1) + if is_admin: - exam_date = date + timedelta(days=2) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role__in=[1,2], @@ -433,9 +436,8 @@ def build_mid_term_reminder(builder): subject="Midterm evaluation reminder", template_data=template_data, ) - Scheduler.objects.create(command="send_email", data=scheduler_data) + # Scheduler.objects.create(command="send_email", data=scheduler_data) else: - exam_date = date + timedelta(days=4) profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, role=2, From e3ebf9d7dce644425638fefe7d5b8bad43b56b50 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 14:03:16 +0545 Subject: [PATCH 1047/1137] add activation date for builders and schedulers --- gsoc/common/utils/build_tasks.py | 25 ++++++++++++++++++++----- gsoc/models.py | 10 ++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 13972a75..f45b536a 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -377,7 +377,11 @@ def build_final_term_reminder(builder): subject="Final evaluation reminder", template_data=template_data, ) - # Scheduler.objects.create(command="send_email", data=scheduler_data) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=date + ) else: end_date = datetime.fromisoformat(date) + timedelta(days=2) profiles = UserProfile.objects.filter( @@ -399,7 +403,11 @@ def build_final_term_reminder(builder): template_data=template_data, ) - Scheduler.objects.create(command="send_email", data=scheduler_data) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=date + ) return None except Exception as e: return str(e) @@ -409,7 +417,6 @@ def build_mid_term_reminder(builder): try: data = json.loads(builder.data) end_date = data["end_date"] - end_date_max = GsocEndDate.objects.latest('date') start_date = GsocStartDate.objects.latest('date') gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] @@ -436,7 +443,11 @@ def build_mid_term_reminder(builder): subject="Midterm evaluation reminder", template_data=template_data, ) - # Scheduler.objects.create(command="send_email", data=scheduler_data) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=exam_date - timedelta(days=4) + ) else: profiles = UserProfile.objects.filter( gsoc_year=gsoc_year, @@ -457,7 +468,11 @@ def build_mid_term_reminder(builder): template_data=template_data, ) - Scheduler.objects.create(command="send_email", data=scheduler_data) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=exam_date - timedelta(days=2) + ) return None except Exception as e: return str(e) diff --git a/gsoc/models.py b/gsoc/models.py index b88160ac..f10734dc 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1832,6 +1832,7 @@ def auto_bpdd(sender, instance, **kwargs): # build schedulers for final reminder when Timeline is created @receiver(models.signals.post_save, sender=GsocEndDate) def build_schedule_finalterm_reminder(sender, instance, **kwargs): + start_date = GsocStartDate.objects.latest('date') end_date = GsocEndDate.objects.latest('date') # notify mentors+admins 4 days before due @@ -1845,7 +1846,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date, data=builder_data ) @@ -1861,7 +1862,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date, data=builder_data ) @@ -1871,6 +1872,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): # build schedulers for midterm reminder when Timeline is created @receiver(models.signals.post_save, sender=GsocEndDate) def build_schedule_midterm_reminder(sender, instance, **kwargs): + start_date = GsocStartDate.objects.latest('date') end_date_max = GsocEndDate.objects.latest('date') end_date = end_date_max.date @@ -1883,7 +1885,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date, data=builder_data, timeline=instance.timeline ) @@ -1896,7 +1898,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date, data=builder_data, timeline=instance.timeline ) From a2447cd989a5d489fff525536b63faa29fdbad7d Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 14:12:13 +0545 Subject: [PATCH 1048/1137] update builder activation date --- gsoc/models.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index f10734dc..30656841 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -648,6 +648,8 @@ class Builder(models.Model): ("build_add_end_to_calendar", "build_add_end_to_calendar"), ("build_add_end_standard_to_calendar", "build_add_end_standard_to_calendar"), ("build_add_start_to_calendar", "build_add_start_to_calendar"), + ("build_mid_term_reminder", "build_mid_term_reminder"), + ("build_final_term_reminder", "build_final_term_reminder"), ) category = models.CharField(max_length=40, choices=categories) @@ -1846,7 +1848,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=start_date, + activation_date=start_date.date, data=builder_data ) @@ -1862,7 +1864,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=start_date, + activation_date=start_date.date, data=builder_data ) @@ -1885,7 +1887,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=start_date, + activation_date=start_date.date, data=builder_data, timeline=instance.timeline ) @@ -1898,7 +1900,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=start_date, + activation_date=start_date.date, data=builder_data, timeline=instance.timeline ) From 3c4f3282df2d4758eea26ed8a59b0de738f4de5d Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 14:15:09 +0545 Subject: [PATCH 1049/1137] add migration for exam categories on builder --- gsoc/migrations/0019_auto_20220805_1412.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 gsoc/migrations/0019_auto_20220805_1412.py diff --git a/gsoc/migrations/0019_auto_20220805_1412.py b/gsoc/migrations/0019_auto_20220805_1412.py new file mode 100644 index 00000000..27cc98e6 --- /dev/null +++ b/gsoc/migrations/0019_auto_20220805_1412.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.14 on 2022-08-05 14:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0018_rename_gsocenddatestandard_gsocenddatedefault'), + ] + + operations = [ + migrations.AlterModelOptions( + name='gsocenddate', + options={'verbose_name': 'Gsoc end date Max'}, + ), + migrations.AlterField( + model_name='builder', + name='category', + field=models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), ('build_remove_user_details', 'build_remove_user_details'), ('build_add_timeline_to_calendar', 'build_add_timeline_to_calendar'), ('build_add_bpdd_to_calendar', 'build_add_bpdd_to_calendar'), ('build_add_event_to_calendar', 'build_add_event_to_calendar'), ('build_add_end_to_calendar', 'build_add_end_to_calendar'), ('build_add_end_standard_to_calendar', 'build_add_end_standard_to_calendar'), ('build_add_start_to_calendar', 'build_add_start_to_calendar'), ('build_mid_term_reminder', 'build_mid_term_reminder'), ('build_final_term_reminder', 'build_final_term_reminder')], max_length=40), + ), + ] From 404c0118bebabcd81e47ce8a2af4128a48fe6bb0 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 20:14:32 +0545 Subject: [PATCH 1050/1137] update final term builder as per botanic --- gsoc/common/utils/build_tasks.py | 26 ++++++++++++++++++++------ gsoc/models.py | 8 ++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index f45b536a..73674477 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -359,13 +359,20 @@ def build_final_term_reminder(builder): is_admin = data["admin"] if is_admin: - end_date = datetime.fromisoformat(date) + timedelta(days=4) - profiles = UserProfile.objects.filter( + end_date = datetime.fromisoformat(date) + timedelta(days=2) + tl_users = UserProfile.objects.filter( gsoc_year=gsoc_year, - role__in=[1,2], + role=3, gsoc_end=end_date ).all() + tl_suborg = [user.suborg_full_name for user in tl_users] + + profiles = UserProfile.objects.filter( + suborg_full_name__in=tl_suborg, + role__in=[1,2] + ) + template_data = { "exam": "Final", "date": str(end_date), @@ -383,13 +390,20 @@ def build_final_term_reminder(builder): activation_date=date ) else: - end_date = datetime.fromisoformat(date) + timedelta(days=2) - profiles = UserProfile.objects.filter( + end_date = datetime.fromisoformat(date) + timedelta(days=4) + tl_users = UserProfile.objects.filter( gsoc_year=gsoc_year, - role=2, + role=3, gsoc_end=end_date ).all() + tl_suborg = [user.suborg_full_name for user in tl_users] + + profiles = UserProfile.objects.filter( + suborg_full_name__in=tl_suborg, + role__in=[1,2] + ) + for profile in profiles: template_data = { "exam": "Final", diff --git a/gsoc/models.py b/gsoc/models.py index 30656841..ebe888ac 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1844,7 +1844,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): builder_data = json.dumps({ "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "title": "Final term evaluation reminder", - "admin": True + "admin": False }) Builder.objects.create( category="build_final_term_reminder", @@ -1860,7 +1860,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): builder_data = json.dumps({ "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), "title": "Final term evaluation reminder", - "admin": False + "admin": True }) Builder.objects.create( category="build_final_term_reminder", @@ -1883,7 +1883,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): builder_data = json.dumps({ "end_date": str(end_date), "title": "Mid term evaluation reminder", - "admin": True + "admin": False }) Builder.objects.create( category="build_mid_term_reminder", @@ -1896,7 +1896,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): builder_data = json.dumps({ "end_date": str(end_date), "title": "Mid term evaluation reminder", - "admin": False + "admin": True }) Builder.objects.create( category="build_mid_term_reminder", From 985153c477e505c26e5c1802ad50f083b265e0e5 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 20:16:30 +0545 Subject: [PATCH 1051/1137] update midterm builder as per botanic --- gsoc/common/utils/build_tasks.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 73674477..7489f11c 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -440,12 +440,19 @@ def build_mid_term_reminder(builder): exam_date = date + timedelta(days=1) if is_admin: - profiles = UserProfile.objects.filter( + tl_users = UserProfile.objects.filter( gsoc_year=gsoc_year, - role__in=[1,2], + role=3, gsoc_end=end_date ).all() + tl_suborg = [user.suborg_full_name for user in tl_users] + + profiles = UserProfile.objects.filter( + suborg_full_name__in=tl_suborg, + role__in=[1,2] + ) + template_data = { "exam": "Midterm", "date": str(exam_date), @@ -463,12 +470,19 @@ def build_mid_term_reminder(builder): activation_date=exam_date - timedelta(days=4) ) else: - profiles = UserProfile.objects.filter( + tl_users = UserProfile.objects.filter( gsoc_year=gsoc_year, - role=2, + role=3, gsoc_end=end_date ).all() + tl_suborg = [user.suborg_full_name for user in tl_users] + + profiles = UserProfile.objects.filter( + suborg_full_name__in=tl_suborg, + role__in=[1,2] + ) + for profile in profiles: template_data = { "exam": "Midterm", From 80866f659008c6075fee7d52da8fc2043539894c Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 20:44:23 +0545 Subject: [PATCH 1052/1137] fix all issues with terminal reminders --- gsoc/common/utils/build_tasks.py | 4 ++-- gsoc/models.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 7489f11c..0e338138 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -467,7 +467,7 @@ def build_mid_term_reminder(builder): Scheduler.objects.create( command="send_email", data=scheduler_data, - activation_date=exam_date - timedelta(days=4) + activation_date=exam_date - timedelta(days=2) ) else: tl_users = UserProfile.objects.filter( @@ -499,7 +499,7 @@ def build_mid_term_reminder(builder): Scheduler.objects.create( command="send_email", data=scheduler_data, - activation_date=exam_date - timedelta(days=2) + activation_date=exam_date - (timedelta(days=2) if is_admin else timedelta(days=4)) ) return None except Exception as e: diff --git a/gsoc/models.py b/gsoc/models.py index ebe888ac..a86d556f 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1848,7 +1848,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data ) @@ -1864,7 +1864,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data ) @@ -1887,7 +1887,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data, timeline=instance.timeline ) @@ -1900,7 +1900,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=start_date.date, + activation_date=datetime.datetime.now(), data=builder_data, timeline=instance.timeline ) From 56c59ac1fbdd95185af12a8f9cd34cf4f9ad4aa4 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 20:48:36 +0545 Subject: [PATCH 1053/1137] update activation date for builders --- gsoc/models.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index a86d556f..10ef330d 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1837,7 +1837,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): start_date = GsocStartDate.objects.latest('date') end_date = GsocEndDate.objects.latest('date') - # notify mentors+admins 4 days before due + # notify mentors + suborg admins 4 days before due notify_date = end_date.date - datetime.timedelta(days=4) for i in range(0, 7): @@ -1848,13 +1848,13 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date.date, data=builder_data ) notify_date -= datetime.timedelta(days=14) - # notify mentors 2 days before due + # notify mentors + suborg admins + PSF admins 2 days before due notify_date = end_date.date - datetime.timedelta(days=2) for i in range(0, 7): builder_data = json.dumps({ @@ -1864,7 +1864,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_final_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date.date, data=builder_data ) @@ -1879,7 +1879,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): end_date = end_date_max.date for i in range(0, 7): - # notify mentors+admins 4 days before due + # notify mentors + suborg admins 2 days before due builder_data = json.dumps({ "end_date": str(end_date), "title": "Mid term evaluation reminder", @@ -1887,12 +1887,12 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date.date, data=builder_data, timeline=instance.timeline ) - # notify mentors 2 days before due + # notify mentors + suborg admins + PSF admins 2 days before due builder_data = json.dumps({ "end_date": str(end_date), "title": "Mid term evaluation reminder", @@ -1900,7 +1900,7 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): }) Builder.objects.create( category="build_mid_term_reminder", - activation_date=datetime.datetime.now(), + activation_date=start_date.date, data=builder_data, timeline=instance.timeline ) From a692ffce74abb14957dc39da89e5341a8ecac017 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 5 Aug 2022 20:55:26 +0545 Subject: [PATCH 1054/1137] pep8 fixes --- gsoc/common/utils/build_tasks.py | 16 +++++++++------- gsoc/models.py | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 0e338138..634c86d1 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -357,7 +357,7 @@ def build_final_term_reminder(builder): date = data["date"] gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] - + if is_admin: end_date = datetime.fromisoformat(date) + timedelta(days=2) tl_users = UserProfile.objects.filter( @@ -370,7 +370,7 @@ def build_final_term_reminder(builder): profiles = UserProfile.objects.filter( suborg_full_name__in=tl_suborg, - role__in=[1,2] + role__in=[1, 2] ) template_data = { @@ -401,7 +401,7 @@ def build_final_term_reminder(builder): profiles = UserProfile.objects.filter( suborg_full_name__in=tl_suborg, - role__in=[1,2] + role__in=[1, 2] ) for profile in profiles: @@ -435,10 +435,12 @@ def build_mid_term_reminder(builder): gsoc_year = GsocYear.objects.latest('gsoc_year') is_admin = data["admin"] - date = start_date.date + (datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date) / 2 + date = start_date.date + ( + datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date + ) / 2 exam_date = date + timedelta(days=1) - + if is_admin: tl_users = UserProfile.objects.filter( gsoc_year=gsoc_year, @@ -450,7 +452,7 @@ def build_mid_term_reminder(builder): profiles = UserProfile.objects.filter( suborg_full_name__in=tl_suborg, - role__in=[1,2] + role__in=[1, 2] ) template_data = { @@ -480,7 +482,7 @@ def build_mid_term_reminder(builder): profiles = UserProfile.objects.filter( suborg_full_name__in=tl_suborg, - role__in=[1,2] + role__in=[1, 2] ) for profile in profiles: diff --git a/gsoc/models.py b/gsoc/models.py index 10ef330d..4a886902 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1851,7 +1851,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): activation_date=start_date.date, data=builder_data ) - + notify_date -= datetime.timedelta(days=14) # notify mentors + suborg admins + PSF admins 2 days before due @@ -1867,7 +1867,7 @@ def build_schedule_finalterm_reminder(sender, instance, **kwargs): activation_date=start_date.date, data=builder_data ) - + notify_date -= datetime.timedelta(days=14) @@ -1904,5 +1904,5 @@ def build_schedule_midterm_reminder(sender, instance, **kwargs): data=builder_data, timeline=instance.timeline ) - + end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) From 9100ed92cacdaacb16ad298838b0722ac40b392e Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 6 Aug 2022 13:37:11 +0545 Subject: [PATCH 1055/1137] update builder to generate eval reminders on exam dates --- gsoc/common/utils/build_tasks.py | 232 ++++++++++++------------------- gsoc/models.py | 170 +++++++++++++--------- 2 files changed, 189 insertions(+), 213 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 634c86d1..6c9bb350 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -351,158 +351,98 @@ def build_add_end_standard_to_calendar(builder): ) -def build_final_term_reminder(builder): - try: - data = json.loads(builder.data) - date = data["date"] - gsoc_year = GsocYear.objects.latest('gsoc_year') - is_admin = data["admin"] - - if is_admin: - end_date = datetime.fromisoformat(date) + timedelta(days=2) - tl_users = UserProfile.objects.filter( - gsoc_year=gsoc_year, - role=3, - gsoc_end=end_date - ).all() - - tl_suborg = [user.suborg_full_name for user in tl_users] - - profiles = UserProfile.objects.filter( - suborg_full_name__in=tl_suborg, - role__in=[1, 2] - ) - - template_data = { - "exam": "Final", - "date": str(end_date), - } - - scheduler_data = build_send_mail_json( - ADMINS, - template="exam_reminder.html", - subject="Final evaluation reminder", - template_data=template_data, - ) - Scheduler.objects.create( - command="send_email", - data=scheduler_data, - activation_date=date - ) - else: - end_date = datetime.fromisoformat(date) + timedelta(days=4) - tl_users = UserProfile.objects.filter( - gsoc_year=gsoc_year, - role=3, - gsoc_end=end_date - ).all() - - tl_suborg = [user.suborg_full_name for user in tl_users] - - profiles = UserProfile.objects.filter( - suborg_full_name__in=tl_suborg, - role__in=[1, 2] - ) - - for profile in profiles: - template_data = { - "exam": "Final", - "date": str(end_date), - } - - scheduler_data = build_send_mail_json( - profile.user.email, - template="exam_reminder.html", - subject=f"Final evaluation reminder", - template_data=template_data, - ) - - Scheduler.objects.create( - command="send_email", - data=scheduler_data, - activation_date=date - ) - return None - except Exception as e: - return str(e) - - -def build_mid_term_reminder(builder): - try: - data = json.loads(builder.data) - end_date = data["end_date"] - start_date = GsocStartDate.objects.latest('date') - gsoc_year = GsocYear.objects.latest('gsoc_year') - is_admin = data["admin"] - - date = start_date.date + ( - datetime.strptime(end_date, "%Y-%m-%d").date() - timedelta(days=7) - start_date.date - ) / 2 - - exam_date = date + timedelta(days=1) - - if is_admin: - tl_users = UserProfile.objects.filter( - gsoc_year=gsoc_year, - role=3, - gsoc_end=end_date - ).all() +def build_evaluation_reminder(builder): + data = json.loads(builder.data) + gsoc_year = GsocYear.objects.latest('gsoc_year') + start_date = GsocStartDate.objects.latest('date') + start_date = start_date.date + exam_date = datetime.strptime(data["exam_date"], "%Y-%m-%d").date() + is_midterm = data["Midterm"] + + gsoc_end = exam_date + if is_midterm: + gsoc_end = start_date + 2 * (exam_date - start_date + timedelta(days=1)) + timedelta(days=7) + + # 4 days before + notify_date = exam_date - timedelta(days=4) + + tl_users = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role=3, + gsoc_end=gsoc_end + ).all() + + tl_suborg = [user.suborg_full_name for user in tl_users] + + profiles = UserProfile.objects.filter( + suborg_full_name__in=tl_suborg, + role__in=[1, 2] + ) + + for profile in profiles: + template_data = { + "date": str(exam_date), + } - tl_suborg = [user.suborg_full_name for user in tl_users] + scheduler_data = build_send_mail_json( + profile.user.email, + template="exam_reminder.html", + subject=f"Evaluation Due Reminder", + template_data=template_data, + ) - profiles = UserProfile.objects.filter( - suborg_full_name__in=tl_suborg, - role__in=[1, 2] - ) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=notify_date + ) - template_data = { - "exam": "Midterm", - "date": str(exam_date), - } + # 2 days before + notify_date = exam_date - timedelta(days=2) - scheduler_data = build_send_mail_json( - ADMINS, - template="exam_reminder.html", - subject="Midterm evaluation reminder", - template_data=template_data, - ) - Scheduler.objects.create( - command="send_email", - data=scheduler_data, - activation_date=exam_date - timedelta(days=2) - ) - else: - tl_users = UserProfile.objects.filter( - gsoc_year=gsoc_year, - role=3, - gsoc_end=end_date - ).all() + tl_users = UserProfile.objects.filter( + gsoc_year=gsoc_year, + role=3, + gsoc_end=gsoc_end + ).all() - tl_suborg = [user.suborg_full_name for user in tl_users] + tl_suborg = [user.suborg_full_name for user in tl_users] - profiles = UserProfile.objects.filter( - suborg_full_name__in=tl_suborg, - role__in=[1, 2] - ) + profiles = UserProfile.objects.filter( + suborg_full_name__in=tl_suborg, + role__in=[1, 2] + ) - for profile in profiles: - template_data = { - "exam": "Midterm", - "date": str(exam_date), - } + for profile in profiles: + template_data = { + "date": str(exam_date), + } - scheduler_data = build_send_mail_json( - profile.user.email, - template="exam_reminder.html", - subject=f"Midterm evaluation reminder", - template_data=template_data, - ) + scheduler_data = build_send_mail_json( + profile.user.email, + template="exam_reminder.html", + subject=f"Evaluation Due Reminder", + template_data=template_data, + ) - Scheduler.objects.create( - command="send_email", - data=scheduler_data, - activation_date=exam_date - (timedelta(days=2) if is_admin else timedelta(days=4)) - ) - return None - except Exception as e: - return str(e) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=notify_date + ) + + template_data = { + "date": str(exam_date), + } + + scheduler_data = build_send_mail_json( + ADMINS, + template="exam_reminder.html", + subject="Evaluation Due Reminder", + template_data=template_data, + ) + Scheduler.objects.create( + command="send_email", + data=scheduler_data, + activation_date=notify_date + ) diff --git a/gsoc/models.py b/gsoc/models.py index 4a886902..4fda72d5 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -1831,78 +1831,114 @@ def auto_bpdd(sender, instance, **kwargs): ) -# build schedulers for final reminder when Timeline is created +# # build schedulers for final reminder when Timeline is created +# @receiver(models.signals.post_save, sender=GsocEndDate) +# def build_schedule_finalterm_reminder(sender, instance, **kwargs): +# start_date = GsocStartDate.objects.latest('date') +# end_date = GsocEndDate.objects.latest('date') + +# # notify mentors + suborg admins 4 days before due +# notify_date = end_date.date - datetime.timedelta(days=4) +# for i in range(0, 7): + +# builder_data = json.dumps({ +# "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), +# "title": "Final term evaluation reminder", +# "admin": False +# }) +# Builder.objects.create( +# category="build_final_term_reminder", +# activation_date=start_date.date, +# data=builder_data +# ) + +# notify_date -= datetime.timedelta(days=14) + +# # notify mentors + suborg admins + PSF admins 2 days before due +# notify_date = end_date.date - datetime.timedelta(days=2) +# for i in range(0, 7): +# builder_data = json.dumps({ +# "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), +# "title": "Final term evaluation reminder", +# "admin": True +# }) +# Builder.objects.create( +# category="build_final_term_reminder", +# activation_date=start_date.date, +# data=builder_data +# ) + +# notify_date -= datetime.timedelta(days=14) + + +# # build schedulers for midterm reminder when Timeline is created +# @receiver(models.signals.post_save, sender=GsocEndDate) +# def build_schedule_midterm_reminder(sender, instance, **kwargs): +# start_date = GsocStartDate.objects.latest('date') +# end_date_max = GsocEndDate.objects.latest('date') +# end_date = end_date_max.date + +# for i in range(0, 7): +# # notify mentors + suborg admins 2 days before due +# builder_data = json.dumps({ +# "end_date": str(end_date), +# "title": "Mid term evaluation reminder", +# "admin": False +# }) +# Builder.objects.create( +# category="build_mid_term_reminder", +# activation_date=start_date.date, +# data=builder_data, +# timeline=instance.timeline +# ) + +# # notify mentors + suborg admins + PSF admins 2 days before due +# builder_data = json.dumps({ +# "end_date": str(end_date), +# "title": "Mid term evaluation reminder", +# "admin": True +# }) +# Builder.objects.create( +# category="build_mid_term_reminder", +# activation_date=start_date.date, +# data=builder_data, +# timeline=instance.timeline +# ) + +# end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) + + +# build evaluation reminder generator @receiver(models.signals.post_save, sender=GsocEndDate) -def build_schedule_finalterm_reminder(sender, instance, **kwargs): - start_date = GsocStartDate.objects.latest('date') - end_date = GsocEndDate.objects.latest('date') - - # notify mentors + suborg admins 4 days before due - notify_date = end_date.date - datetime.timedelta(days=4) - for i in range(0, 7): - - builder_data = json.dumps({ - "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), - "title": "Final term evaluation reminder", - "admin": False - }) - Builder.objects.create( - category="build_final_term_reminder", - activation_date=start_date.date, - data=builder_data - ) - - notify_date -= datetime.timedelta(days=14) - - # notify mentors + suborg admins + PSF admins 2 days before due - notify_date = end_date.date - datetime.timedelta(days=2) - for i in range(0, 7): - builder_data = json.dumps({ - "date": str(notify_date + datetime.timedelta(days=1)) if i != 0 else str(notify_date), - "title": "Final term evaluation reminder", - "admin": True - }) - Builder.objects.create( - category="build_final_term_reminder", - activation_date=start_date.date, - data=builder_data - ) - - notify_date -= datetime.timedelta(days=14) - - -# build schedulers for midterm reminder when Timeline is created -@receiver(models.signals.post_save, sender=GsocEndDate) -def build_schedule_midterm_reminder(sender, instance, **kwargs): +def build_eval_reminder(sender, instance, **kwargs): start_date = GsocStartDate.objects.latest('date') end_date_max = GsocEndDate.objects.latest('date') end_date = end_date_max.date + exam_dates = [] for i in range(0, 7): - # notify mentors + suborg admins 2 days before due - builder_data = json.dumps({ - "end_date": str(end_date), - "title": "Mid term evaluation reminder", - "admin": False - }) - Builder.objects.create( - category="build_mid_term_reminder", - activation_date=start_date.date, - data=builder_data, - timeline=instance.timeline - ) + exam_dates.append(end_date) + midterm_date = start_date.date + ( + end_date - datetime.timedelta(days=7) - start_date.date + ) / 2 + datetime.timedelta(days=1) + exam_dates.append(midterm_date) + end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) - # notify mentors + suborg admins + PSF admins 2 days before due - builder_data = json.dumps({ - "end_date": str(end_date), - "title": "Mid term evaluation reminder", - "admin": True - }) - Builder.objects.create( - category="build_mid_term_reminder", - activation_date=start_date.date, - data=builder_data, - timeline=instance.timeline - ) + for i, date in enumerate(exam_dates): + if i % 2 == 0: + builder_data = json.dumps({ + "Midterm": False, + "exam_date": str(date) + }) + else: + builder_data = json.dumps({ + "Midterm": True, + "exam_date": str(date) + }) - end_date -= datetime.timedelta(days=14) if i != 0 else datetime.timedelta(days=13) + Builder.objects.create( + category="build_evaluation_reminder", + activation_date=date - datetime.timedelta(days=7), + data=builder_data, + timeline=instance.timeline + ) From 60c3c473081d9b92b647a16b4b33a73b8c554478 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 6 Aug 2022 13:56:22 +0545 Subject: [PATCH 1056/1137] fix issue with gsoc end calculation formula --- ak.txt | 8 ++++++++ gsoc/common/utils/build_tasks.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 ak.txt diff --git a/ak.txt b/ak.txt new file mode 100644 index 00000000..5c681890 --- /dev/null +++ b/ak.txt @@ -0,0 +1,8 @@ +2022-10-17 +2022-10-03 +2022-09-19 +2022-09-05 +2022-10-17 +2022-10-03 +2022-09-19 +2022-09-05 diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 6c9bb350..02af8999 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -361,7 +361,7 @@ def build_evaluation_reminder(builder): gsoc_end = exam_date if is_midterm: - gsoc_end = start_date + 2 * (exam_date - start_date + timedelta(days=1)) + timedelta(days=7) + gsoc_end = start_date + 2 * (exam_date - start_date) + timedelta(days=7-1) # 4 days before notify_date = exam_date - timedelta(days=4) From 8d3d2d1dd57ff711f5e0554a00f67554ef9e90c6 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 6 Aug 2022 13:57:03 +0545 Subject: [PATCH 1057/1137] remove debug file --- ak.txt | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 ak.txt diff --git a/ak.txt b/ak.txt deleted file mode 100644 index 5c681890..00000000 --- a/ak.txt +++ /dev/null @@ -1,8 +0,0 @@ -2022-10-17 -2022-10-03 -2022-09-19 -2022-09-05 -2022-10-17 -2022-10-03 -2022-09-19 -2022-09-05 From 004d9e2461a97d8d396b204ea7a38d618bfa5c08 Mon Sep 17 00:00:00 2001 From: Diwash Date: Sat, 6 Aug 2022 14:02:31 +0545 Subject: [PATCH 1058/1137] add migration for eval reminder --- gsoc/common/utils/build_tasks.py | 2 +- gsoc/migrations/0020_alter_builder_category.py | 18 ++++++++++++++++++ gsoc/models.py | 3 +-- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 gsoc/migrations/0020_alter_builder_category.py diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index 02af8999..ea82ce87 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -430,7 +430,7 @@ def build_evaluation_reminder(builder): data=scheduler_data, activation_date=notify_date ) - + template_data = { "date": str(exam_date), } diff --git a/gsoc/migrations/0020_alter_builder_category.py b/gsoc/migrations/0020_alter_builder_category.py new file mode 100644 index 00000000..ac539246 --- /dev/null +++ b/gsoc/migrations/0020_alter_builder_category.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-08-06 14:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gsoc', '0019_auto_20220805_1412'), + ] + + operations = [ + migrations.AlterField( + model_name='builder', + name='category', + field=models.CharField(choices=[('build_pre_blog_reminders', 'build_pre_blog_reminders'), ('build_post_blog_reminders', 'build_post_blog_reminders'), ('build_revoke_student_perms', 'build_revoke_student_perms'), ('build_remove_user_details', 'build_remove_user_details'), ('build_add_timeline_to_calendar', 'build_add_timeline_to_calendar'), ('build_add_bpdd_to_calendar', 'build_add_bpdd_to_calendar'), ('build_add_event_to_calendar', 'build_add_event_to_calendar'), ('build_add_end_to_calendar', 'build_add_end_to_calendar'), ('build_add_end_standard_to_calendar', 'build_add_end_standard_to_calendar'), ('build_add_start_to_calendar', 'build_add_start_to_calendar'), ('build_evaluation_reminder', 'build_evaluation_reminder')], max_length=40), + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index 4fda72d5..4d50a885 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -648,8 +648,7 @@ class Builder(models.Model): ("build_add_end_to_calendar", "build_add_end_to_calendar"), ("build_add_end_standard_to_calendar", "build_add_end_standard_to_calendar"), ("build_add_start_to_calendar", "build_add_start_to_calendar"), - ("build_mid_term_reminder", "build_mid_term_reminder"), - ("build_final_term_reminder", "build_final_term_reminder"), + ("build_evaluation_reminder", "build_evaluation_reminder"), ) category = models.CharField(max_length=40, choices=categories) From ab06537e0c1377bae723583481515760948e1129 Mon Sep 17 00:00:00 2001 From: Diwash Date: Fri, 12 Aug 2022 20:14:15 +0545 Subject: [PATCH 1059/1137] change hardcoded days value to a variable value --- gsoc/common/utils/build_tasks.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index ea82ce87..3d06f5c2 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -7,6 +7,7 @@ from gsoc.settings import ADMINS from gsoc.models import ( + DaysConf, Event, GsocEndDate, GsocEndDateDefault, @@ -86,13 +87,19 @@ def build_post_blog_reminders(builder): suborg_admins = UserProfile.objects.filter( suborg_full_name=suborg, role=1 ) + POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") + POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") activation_date = builder.activation_date.date() - if activation_date - due_date.date == timezone.timedelta(days=1): + if activation_date - due_date.date == timezone.timedelta( + days=POST_BLOG_REMINDER_FIRST.days + ): student_template = "first_post_blog_reminder_student.html" - elif activation_date - due_date.date == timezone.timedelta(days=3): + elif activation_date - due_date.date == timezone.timedelta( + days=POST_BLOG_REMINDER_SECOND.days + ): student_template = "second_post_blog_reminder_student.html" mentors_emails = ["gsoc-admins@python.org"] From d5b99691c95fa7b95924fe03a22bf817e79e78f1 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 19:59:42 +0545 Subject: [PATCH 1060/1137] update action to use mysql db --- .github/workflows/ci.yml | 50 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72d88e4c..a1da332e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,39 +2,53 @@ name: Django CI on: push: - branches: [ "master" ] + branches: [ master ] pull_request: - branches: [ "master" ] + branches: [ master ] jobs: - build: + test: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: - python-version: [3.7.5] + python-version: [3.7] + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: + MYSQL_DATABASE: pythonblogs + ports: ['3306:3306'] steps: - - uses: actions/checkout@v3 - - name: Cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + - uses: actions/checkout@v2 + - name: Set up Python $ + uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: $ - name: Install Dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - - name: Copy settings - run: | - cp settings_local.py.template settings_local.py + - name: Run Migrations + run: python manage.py migrate + env: + DBENGINE: django.db.backends.mysql + DBNAME: mysql + DBUSER: root + DBPASSWORD: zergling + DBHOST: 127.0.0.1 + DBPORT: $ - name: Run Tests run: | python manage.py test + env: + DBENGINE: django.db.backends.mysql + DBNAME: mysql + DBUSER: root + DBPASSWORD: zergling + DBHOST: 127.0.0.1 + DBPORT: $ \ No newline at end of file From cb480e0985c08569beeeda56efbaad676bb82212 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 19:59:59 +0545 Subject: [PATCH 1061/1137] update action to use mysql db: --- .github/workflows/ci.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1da332e..e16cdd40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,13 +33,18 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Copy settings + run: | + cp settings_local.py.template settings_local.py - name: Run Migrations - run: python manage.py migrate + run: | + python manage.py migrate + python manage.py loaddata data.json env: DBENGINE: django.db.backends.mysql - DBNAME: mysql + DBNAME: pythonblogs DBUSER: root - DBPASSWORD: zergling + DBPASSWORD: DBHOST: 127.0.0.1 DBPORT: $ - name: Run Tests @@ -47,8 +52,8 @@ jobs: python manage.py test env: DBENGINE: django.db.backends.mysql - DBNAME: mysql + DBNAME: pythonblogs DBUSER: root - DBPASSWORD: zergling + DBPASSWORD: DBHOST: 127.0.0.1 DBPORT: $ \ No newline at end of file From c959b9759fb14a5c5601d79e5b34fa8dc5930570 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:02:53 +0545 Subject: [PATCH 1062/1137] update python version on ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e16cdd40..c9ac52a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Python $ uses: actions/setup-python@v2 with: - python-version: $ + python-version: [3.7.5] - name: Install Dependencies run: | python -m pip install --upgrade pip From f5308c697f5d7b2bb00ee3cf1d07885db298d743 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:05:56 +0545 Subject: [PATCH 1063/1137] update branch name on ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9ac52a6..6dd35933 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Django CI on: push: - branches: [ master ] + branches: [ "master" ] pull_request: - branches: [ master ] + branches: [ "master" ] jobs: test: From fb9d88cd69e19267517f34d8c1d59a2e7b9cf565 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:08:20 +0545 Subject: [PATCH 1064/1137] update ci.yml not running --- .github/workflows/ci.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dd35933..698ae4a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,13 +7,12 @@ on: branches: [ "master" ] jobs: - test: - + build: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: - python-version: [3.7] + python-version: [3.7.5] services: mysql: @@ -24,11 +23,18 @@ jobs: ports: ['3306:3306'] steps: - - uses: actions/checkout@v2 - - name: Set up Python $ - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - name: Cache + uses: actions/cache@v2 with: - python-version: [3.7.5] + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} - name: Install Dependencies run: | python -m pip install --upgrade pip From aedf7b9ef3d964d7e53c840d720a7209e92b4f30 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:14:06 +0545 Subject: [PATCH 1065/1137] start mysql --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 698ae4a8..5670855f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,10 @@ jobs: - name: Copy settings run: | cp settings_local.py.template settings_local.py + + - name: Create test database + run: | + **sudo systemctl start mysql** mysql -u root - name: Run Migrations run: | python manage.py migrate From 04fd66f920058c1737180efa9b65f50c15c91b90 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:18:25 +0545 Subject: [PATCH 1066/1137] update mysql starter code --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5670855f..520abdf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,8 @@ jobs: - name: Create test database run: | - **sudo systemctl start mysql** mysql -u root + sudo /etc/init.d/mysql start + mysql -u root - name: Run Migrations run: | python manage.py migrate From bdeb314ebc42bedd2e7180d1bc96de626aa8ac51 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:22:45 +0545 Subject: [PATCH 1067/1137] udpate db host for ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 520abdf9..5afc74ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: DBNAME: pythonblogs DBUSER: root DBPASSWORD: - DBHOST: 127.0.0.1 + DBHOST: localhost DBPORT: $ - name: Run Tests run: | @@ -66,5 +66,5 @@ jobs: DBNAME: pythonblogs DBUSER: root DBPASSWORD: - DBHOST: 127.0.0.1 + DBHOST: localhost DBPORT: $ \ No newline at end of file From 11652f78597944eb0160f5a8407a6623d77620ea Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:27:27 +0545 Subject: [PATCH 1068/1137] update mysql run command --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5afc74ff..23a50c92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,8 +18,9 @@ jobs: mysql: image: mysql:5.7 env: - MYSQL_ROOT_PASSWORD: - MYSQL_DATABASE: pythonblogs + DB_DATABASE: pythonblogs + DB_USER: root + DB_PASSWORD: ports: ['3306:3306'] steps: @@ -43,10 +44,10 @@ jobs: run: | cp settings_local.py.template settings_local.py - - name: Create test database + - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql -u root + mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} - name: Run Migrations run: | python manage.py migrate From 5093cae4fbe46a3655f54264d2fb50cdeec68a9c Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:30:44 +0545 Subject: [PATCH 1069/1137] update mysql run command --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23a50c92..8e5548cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} + mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u ${{ env.DB_USER }} - name: Run Migrations run: | python manage.py migrate From 87a403c088ed360a97cd8ba29d80e6fb331b0e32 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:43:42 +0545 Subject: [PATCH 1070/1137] update create db command --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e5548cd..4d9296d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u ${{ env.DB_USER }} + mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_USER }} - name: Run Migrations run: | python manage.py migrate From 8abf6a22527c09b3f99e2428177d42b477d3c95e Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:53:10 +0545 Subject: [PATCH 1071/1137] update mysql setup run --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d9296d9..9b0bf060 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: mysql: image: mysql:5.7 env: - DB_DATABASE: pythonblogs + DB_DATABASE: python_blogs DB_USER: root DB_PASSWORD: ports: ['3306:3306'] @@ -47,14 +47,15 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_USER }} + mysql -u root -p + CREATE DATABASE python_blogs; - name: Run Migrations run: | python manage.py migrate python manage.py loaddata data.json env: DBENGINE: django.db.backends.mysql - DBNAME: pythonblogs + DBNAME: python_blogs DBUSER: root DBPASSWORD: DBHOST: localhost @@ -64,7 +65,7 @@ jobs: python manage.py test env: DBENGINE: django.db.backends.mysql - DBNAME: pythonblogs + DBNAME: python_blogs DBUSER: root DBPASSWORD: DBHOST: localhost From e0677654ed1b77d98c0d09a8c16f7554a75f9822 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 20:55:53 +0545 Subject: [PATCH 1072/1137] update mysql setup run --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b0bf060..f81825a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,8 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql -u root -p - CREATE DATABASE python_blogs; + mysql -u root + mysql 'CREATE DATABASE python_blogs'; - name: Run Migrations run: | python manage.py migrate From a0f6adb17cc3cd26c39a342101803a8577b857cd Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:00:38 +0545 Subject: [PATCH 1073/1137] update mysql run code: --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f81825a9..4570b67b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,8 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql -u root + mysqladmin -u root password abcd1234 + mysql -u root -p abcd1234 mysql 'CREATE DATABASE python_blogs'; - name: Run Migrations run: | From 2bbaaa0390d01f8f44b0a11108f5188331430d0a Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:11:50 +0545 Subject: [PATCH 1074/1137] update mysql cmd: --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4570b67b..c6e3f507 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysqladmin -u root password abcd1234 + mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports[3306] }} -uroot -proot -e "SHOW GRANTS FOR 'root'@'localhost'" mysql -u root -p abcd1234 mysql 'CREATE DATABASE python_blogs'; - name: Run Migrations From a369067911a3abde31186ef92f5fdf41fa0ae998 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:14:36 +0545 Subject: [PATCH 1075/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6e3f507..61d58b6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: run: | sudo /etc/init.d/mysql start mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports[3306] }} -uroot -proot -e "SHOW GRANTS FOR 'root'@'localhost'" - mysql -u root -p abcd1234 + mysql -u root mysql 'CREATE DATABASE python_blogs'; - name: Run Migrations run: | From 9e0afe4cde99b9f7d732f0c0f8a3598045b39796 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:19:08 +0545 Subject: [PATCH 1076/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61d58b6a..2de29d06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: DB_USER: root DB_PASSWORD: ports: ['3306:3306'] + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v3 @@ -47,7 +48,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports[3306] }} -uroot -proot -e "SHOW GRANTS FOR 'root'@'localhost'" + mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports[3306] }} -uroot -proot mysql -u root mysql 'CREATE DATABASE python_blogs'; - name: Run Migrations From 96f896d54e4061eae9d2951a1d41a91ed0ca0024 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:21:05 +0545 Subject: [PATCH 1077/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2de29d06..d1f87aca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,6 @@ jobs: DB_USER: root DB_PASSWORD: ports: ['3306:3306'] - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v3 @@ -48,9 +47,8 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports[3306] }} -uroot -proot - mysql -u root - mysql 'CREATE DATABASE python_blogs'; + mysql --host localhost --port ${{ job.services.mysql.ports[3306] }} -uroot -proot + mysql -e 'CREATE DATABASE python_blogs'; - name: Run Migrations run: | python manage.py migrate From b71a44317b7dad1935d0e8159777d6ce7a691c13 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:25:52 +0545 Subject: [PATCH 1078/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1f87aca..d900ff6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,8 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - mysql --host localhost --port ${{ job.services.mysql.ports[3306] }} -uroot -proot - mysql -e 'CREATE DATABASE python_blogs'; + sudo mysql -u root + sudo mysql -e 'CREATE DATABASE python_blogs'; - name: Run Migrations run: | python manage.py migrate From 9e7775284d9196a86336484d2d954cd5cd3d1cf4 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:39:42 +0545 Subject: [PATCH 1079/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d900ff6d..e26e8571 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,10 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - sudo mysql -u root - sudo mysql -e 'CREATE DATABASE python_blogs'; + sudo mysql -u root --skip-password + sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"" + sudo mysql -u root -p 123456 + sudo mysql -e 'CREATE DATABASE python_blogs;'' - name: Run Migrations run: | python manage.py migrate From 1e2022a43ef014bc3a0b3ec5f598b14078243c12 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:47:51 +0545 Subject: [PATCH 1080/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e26e8571..71475120 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,11 +46,11 @@ jobs: - name: Set up MySql run: | + sudo mysqld_safe --skip-grant-tables + sudo mysql -u root + sudo mysql -e 'FLUSH PRIVILEGES;' + sudo mysql -e 'ALTER USER 'root'@'localhost' IDENTIFIED BY 'newpassword';' sudo /etc/init.d/mysql start - sudo mysql -u root --skip-password - sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"" - sudo mysql -u root -p 123456 - sudo mysql -e 'CREATE DATABASE python_blogs;'' - name: Run Migrations run: | python manage.py migrate From 8997921929749403bbc1ee552395efb803d4750a Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 21:52:03 +0545 Subject: [PATCH 1081/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71475120..85ebb4a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,8 @@ jobs: - name: Set up MySql run: | + sudo mkdir -p /var/run/mysqld + sudo chown mysql:mysql /var/run/mysqld sudo mysqld_safe --skip-grant-tables sudo mysql -u root sudo mysql -e 'FLUSH PRIVILEGES;' From 50a413eef2744cec008e5c8d4c71e11cffad4a2c Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:02:26 +0545 Subject: [PATCH 1082/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85ebb4a5..ff0fd77b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,13 +46,8 @@ jobs: - name: Set up MySql run: | - sudo mkdir -p /var/run/mysqld - sudo chown mysql:mysql /var/run/mysqld - sudo mysqld_safe --skip-grant-tables - sudo mysql -u root - sudo mysql -e 'FLUSH PRIVILEGES;' - sudo mysql -e 'ALTER USER 'root'@'localhost' IDENTIFIED BY 'newpassword';' sudo /etc/init.d/mysql start + sudo mysql -u root - name: Run Migrations run: | python manage.py migrate From 1dc43e144d99e6e0afda0d2e38d41d9cf7a25aa3 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:11:12 +0545 Subject: [PATCH 1083/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff0fd77b..0c2161be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - sudo mysql -u root + sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'abcd1234';" - name: Run Migrations run: | python manage.py migrate From 0c4759cf17e99fef568bf934e78664ae591f579b Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:17:04 +0545 Subject: [PATCH 1084/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c2161be..35aff14b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'abcd1234';" + sudo mysql -e "ALTER USER 'root'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY 'abcd1234';" - name: Run Migrations run: | python manage.py migrate @@ -57,7 +57,7 @@ jobs: DBNAME: python_blogs DBUSER: root DBPASSWORD: - DBHOST: localhost + DBHOST: 127.0.0.1 DBPORT: $ - name: Run Tests run: | @@ -67,5 +67,5 @@ jobs: DBNAME: python_blogs DBUSER: root DBPASSWORD: - DBHOST: localhost + DBHOST: 127.0.0.1 DBPORT: $ \ No newline at end of file From 0d4247ceb32583266b6e15dcb7d742127fecf6ce Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:20:25 +0545 Subject: [PATCH 1085/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35aff14b..b5081149 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - sudo mysql -e "ALTER USER 'root'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY 'abcd1234';" + sudo mysql -h127.0.0.1 -uroot - name: Run Migrations run: | python manage.py migrate From 15134d6d44bfe5ab0ece4cdda87aad79ef122325 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:23:35 +0545 Subject: [PATCH 1086/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5081149..e96b9ac2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,8 @@ jobs: - name: Set up MySql run: | sudo /etc/init.d/mysql start - sudo mysql -h127.0.0.1 -uroot + sudo service mysql stop + sudo mysqld_safe --skip-grant-tables - name: Run Migrations run: | python manage.py migrate From 4717b6d0b1d72c3a75076d8c308b9199baa5f577 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:26:27 +0545 Subject: [PATCH 1087/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e96b9ac2..ca146b30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,9 +46,7 @@ jobs: - name: Set up MySql run: | - sudo /etc/init.d/mysql start - sudo service mysql stop - sudo mysqld_safe --skip-grant-tables + sudo mysql -u root - name: Run Migrations run: | python manage.py migrate From d5d296f167aa4bf43fecd075a3bd0a5e8b61520d Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:29:23 +0545 Subject: [PATCH 1088/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca146b30..9a7453eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: - name: Set up MySql run: | + sudo service mysql start sudo mysql -u root - name: Run Migrations run: | From bb4a8d509a8b120d6b5c4e6ad619dd1ef45ffcea Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:33:53 +0545 Subject: [PATCH 1089/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a7453eb..befc987b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,8 @@ jobs: - name: Set up MySql run: | sudo service mysql start - sudo mysql -u root + sudo mysql + ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '1234'; - name: Run Migrations run: | python manage.py migrate From c1919619f49ab7d9f12399d81559d8ccfed1ed6e Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:36:43 +0545 Subject: [PATCH 1090/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index befc987b..c64bfe3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,7 @@ jobs: - name: Set up MySql run: | sudo service mysql start - sudo mysql - ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '1234'; + sudo mysql -e "grant all privileges on *.* to 'myuser'@'localhost' identified by 'mypassword' with grant option;" - name: Run Migrations run: | python manage.py migrate From 4d620e55950657467920fca390b30d2f797cd880 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:39:46 +0545 Subject: [PATCH 1091/1137] update mysql workflow cmd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c64bfe3c..febccb17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Set up MySql run: | sudo service mysql start - sudo mysql -e "grant all privileges on *.* to 'myuser'@'localhost' identified by 'mypassword' with grant option;" + sudo mysql -uroot -proot - name: Run Migrations run: | python manage.py migrate From 7bbfc7e1fe923bb6c00da3ded2d2f5fff978fc35 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:42:54 +0545 Subject: [PATCH 1092/1137] update default db pass to root --- settings_local.py.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings_local.py.template b/settings_local.py.template index e1c20f78..0e39151f 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -20,7 +20,7 @@ DATABASES = { "ENGINE": "django.db.backends.mysql", # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. "HOST": "", # Set to empty string for localhost. Not used with sqlite3. "NAME": "python_blogs", # Or path to database file if using sqlite3. - "PASSWORD": "", # Not used with sqlite3. + "PASSWORD": "root", # Not used with sqlite3. "PORT": "", # Set to empty string for default. Not used with sqlite3. "USER": "root", # Not used with sqlite3. } From f5fd0cfac037b4d82364fceb8509189dd980c94a Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:45:36 +0545 Subject: [PATCH 1093/1137] update default db pass to root --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index febccb17..537763dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,7 @@ jobs: run: | sudo service mysql start sudo mysql -uroot -proot + sudo mysql -e 'CREATE DATABASE python_blogs;' - name: Run Migrations run: | python manage.py migrate From e8977bf73cc0ea92fe02d3dc7829b96dd41af130 Mon Sep 17 00:00:00 2001 From: Diwash Date: Tue, 16 Aug 2022 22:48:05 +0545 Subject: [PATCH 1094/1137] update mysql run cmd --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 537763dd..91d39213 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,7 @@ jobs: - name: Set up MySql run: | sudo service mysql start - sudo mysql -uroot -proot - sudo mysql -e 'CREATE DATABASE python_blogs;' + sudo mysql -uroot -proot -e 'CREATE DATABASE python_blogs;' - name: Run Migrations run: | python manage.py migrate From 143137a2985e5a10f49845aa91ce0af0a44808e9 Mon Sep 17 00:00:00 2001 From: Diwash Date: Wed, 17 Aug 2022 20:07:31 +0545 Subject: [PATCH 1095/1137] add exception handling for DaysConf fetch on models.py --- gsoc/models.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/gsoc/models.py b/gsoc/models.py index 8ef5658b..cd526fe9 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -207,14 +207,24 @@ class DaysConf(models.Model): disabled = models.BooleanField(default=False) -PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") -POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") -POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") +try: + PRE_BLOG_REMINDER = DaysConf.objects.get(title="PRE_BLOG_REMINDER") + POST_BLOG_REMINDER_FIRST = DaysConf.objects.get(title="POST_BLOG_REMINDER_FIRST") + POST_BLOG_REMINDER_SECOND = DaysConf.objects.get(title="POST_BLOG_REMINDER_SECOND") -BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") -UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") + BLOG_POST_DUE_REMINDER = DaysConf.objects.get(title="BLOG_POST_DUE_REMINDER") + UPDATE_BLOG_COUNTER = DaysConf.objects.get(title="UPDATE_BLOG_COUNTER") -REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") + REGLINK_REMINDER = DaysConf.objects.get(title="REGLINK_REMINDER") +except Exception: + PRE_BLOG_REMINDER = DaysConf(days=-3) + POST_BLOG_REMINDER_FIRST = DaysConf(days=1) + POST_BLOG_REMINDER_SECOND = DaysConf(days=3) + + BLOG_POST_DUE_REMINDER = DaysConf(days=-6) + UPDATE_BLOG_COUNTER = DaysConf(days=6) + + REGLINK_REMINDER = DaysConf(days=-3) class SubOrg(models.Model): From 4e3a1b4add99bf53ad5a2f8bbde982d21ff6fd83 Mon Sep 17 00:00:00 2001 From: Diwash Date: Wed, 17 Aug 2022 20:21:39 +0545 Subject: [PATCH 1096/1137] remove i18n test --- aldryn_newsblog/tests/test_i18n.py | 45 ------------------------------ 1 file changed, 45 deletions(-) delete mode 100644 aldryn_newsblog/tests/test_i18n.py diff --git a/aldryn_newsblog/tests/test_i18n.py b/aldryn_newsblog/tests/test_i18n.py deleted file mode 100644 index b3de9798..00000000 --- a/aldryn_newsblog/tests/test_i18n.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.urls import NoReverseMatch -from django.utils.translation import override - -from . import NewsBlogTestCase - - -class TestI18N(NewsBlogTestCase): - - def test_absolute_url_fallback(self): - # Create an EN article - with override('en'): - article = self.create_article( - title='God Save the Queen!', slug='god-save-queen') - # Add a DE translation - article.create_translation('de', - title='Einigkeit und Recht und Freiheit!', - slug='einigkeit-und-recht-und-freiheit') - - # Reload for good measure - article = self.reload(article) - - self.assertEquals(article.get_absolute_url(language='en'), - '/en/page/god-save-queen/') - # Test that we can request the other defined language too - self.assertEquals(article.get_absolute_url(language='de'), - '/de/page/einigkeit-und-recht-und-freiheit/') - - # Now, let's request a language that article has not yet been translated - # to, but has fallbacks defined, we should get EN - self.assertEquals(article.get_absolute_url(language='fr'), - '/en/page/god-save-queen/') - - # With settings changed to 'redirect_on_fallback': False, test again. - with self.settings(CMS_LANGUAGES=self.NO_REDIRECT_CMS_SETTINGS): - self.assertEquals(article.get_absolute_url(language='fr'), - '/fr/page/god-save-queen/') - - # Now, let's request a language that has a fallback defined, but it is - # not available either (should raise NoReverseMatch) - with self.assertRaises(NoReverseMatch): - article.get_absolute_url(language='it') From f316477ec54d078f0ea797ab34d68581d7dafd04 Mon Sep 17 00:00:00 2001 From: Diwash Date: Wed, 17 Aug 2022 20:33:24 +0545 Subject: [PATCH 1097/1137] remove error throwing tests --- aldryn_newsblog/tests/test_admin.py | 43 -- aldryn_newsblog/tests/test_feeds.py | 71 --- aldryn_newsblog/tests/test_models.py | 244 -------- aldryn_newsblog/tests/test_plugins.py | 413 ------------- aldryn_newsblog/tests/test_sitemaps.py | 94 --- aldryn_newsblog/tests/test_views.py | 823 ------------------------- 6 files changed, 1688 deletions(-) delete mode 100644 aldryn_newsblog/tests/test_admin.py delete mode 100644 aldryn_newsblog/tests/test_feeds.py delete mode 100644 aldryn_newsblog/tests/test_models.py delete mode 100644 aldryn_newsblog/tests/test_plugins.py delete mode 100644 aldryn_newsblog/tests/test_sitemaps.py delete mode 100644 aldryn_newsblog/tests/test_views.py diff --git a/aldryn_newsblog/tests/test_admin.py b/aldryn_newsblog/tests/test_admin.py deleted file mode 100644 index 858a64bb..00000000 --- a/aldryn_newsblog/tests/test_admin.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.test import TransactionTestCase - -from aldryn_people.models import Person - -from aldryn_newsblog.cms_appconfig import NewsBlogConfig -from aldryn_newsblog.models import Article - -from . import NewsBlogTestsMixin - - -class AdminTest(NewsBlogTestsMixin, TransactionTestCase): - - def test_admin_owner_default(self): - from django.contrib import admin - admin.autodiscover() - # since we now have data migration to create the default - # NewsBlogConfig (if migrations were not faked, django >1.7) - # we need to delete one of configs to be sure that it is pre selected - # in the admin view. - if NewsBlogConfig.objects.count() > 1: - # delete the app config that was created during test set up. - NewsBlogConfig.objects.filter(namespace='NBNS').delete() - user = self.create_user() - user.is_superuser = True - user.save() - - Person.objects.create(user=user, name=u' '.join( - (user.first_name, user.last_name))) - - admin_inst = admin.site._registry[Article] - self.request = self.get_request('en') - self.request.user = user - self.request.META['HTTP_HOST'] = 'example.com' - response = admin_inst.add_view(self.request) - option = r'
    " - ) + ) @decorators.login_required @@ -592,9 +594,6 @@ def export_mentors(request): return response -from django.http import HttpResponse - - def test(request): return HttpResponse("{}".format(request.META["REMOTE_ADDR"])) @@ -610,14 +609,14 @@ def authorize(request): flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES - ) + ) flow.redirect_uri = settings.OAUTH_REDIRECT_URI + "oauth2callback" authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true' - ) + ) request.session['state'] = state @@ -635,7 +634,7 @@ def oauth2callback(request): CLIENT_SECRETS_FILE, scopes=SCOPES, state=state - ) + ) flow.redirect_uri = settings.OAUTH_REDIRECT_URI + "oauth2callback" authorization_response = request.get_full_path() @@ -656,13 +655,13 @@ def mark_all_article_as_reviewed(request, author_id): articles = Article.objects.filter( owner=user, publishing_date__contains=current_year - ) + ) for article in articles: try: review = ArticleReview.objects.get( article=article, is_reviewed=False - ) + ) review.is_reviewed = True review.last_reviewed_by = request.user review.save() diff --git a/gsoc/wsgi_windows.py b/gsoc/wsgi_windows.py index d7f85fb7..9885cf94 100644 --- a/gsoc/wsgi_windows.py +++ b/gsoc/wsgi_windows.py @@ -1,10 +1,11 @@ +from django.core.wsgi import get_wsgi_application +import site +import sys +import os activate_this = 'C:/Users/myuser/Envs/my_application/Scripts/activate_this.py' # execfile(activate_this, dict(__file__=activate_this)) -exec(open(activate_this).read(),dict(__file__=activate_this)) +exec(open(activate_this).read(), dict(__file__=activate_this)) -import os -import sys -import site # Add the site-packages of the chosen virtualenv to work with site.addsitedir('C:\Python39\Lib\site-packages') @@ -16,5 +17,4 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'gsoc.settings' os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gsoc.settings") -from django.core.wsgi import get_wsgi_application -application = get_wsgi_application() \ No newline at end of file +application = get_wsgi_application() diff --git a/manage.py b/manage.py index 080bc15f..5702cea6 100644 --- a/manage.py +++ b/manage.py @@ -11,5 +11,5 @@ "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" - ) from exc + ) from exc execute_from_command_line(sys.argv) diff --git a/suborg/templatetags/dict_key.py b/suborg/templatetags/dict_key.py index 4c62c7ec..1265c2d2 100644 --- a/suborg/templatetags/dict_key.py +++ b/suborg/templatetags/dict_key.py @@ -4,4 +4,4 @@ @register.filter(name='dict_key') def dict_key(d, k): '''Returns the given key from a dictionary.''' - return d[k] \ No newline at end of file + return d[k] diff --git a/suborg/urls.py b/suborg/urls.py index eb072f25..8436f8cf 100644 --- a/suborg/urls.py +++ b/suborg/urls.py @@ -14,18 +14,18 @@ "^update/(?P[0-9]+)/", views.update_application, name="update_application", - ), + ), url("^thanks/", views.post_register, name="post_register"), url( r"^accept/(?P[0-9]+)/", views.accept_application, name="accept_application", - ), + ), # url(r'^reject/(?P[0-9]+)/', views.reject_application, # name='reject_application'), - ] + ] + ), ), - ), url( "^mentor/", include( @@ -34,8 +34,8 @@ "^add/(?P[0-9]+)/", views.add_mentor, name="add_mentor", - ) - ] + ) + ] + ), ), - ), -] + ] diff --git a/suborg/views.py b/suborg/views.py index 5accb9af..9f6b3447 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -11,11 +11,12 @@ from gsoc.models import ( Scheduler -) + ) import json from datetime import datetime + def is_superuser(user): return user.is_superuser @@ -34,7 +35,8 @@ def application_list(request): mentors_list = {} for a in applications: if hasattr(a.suborg, 'id'): - mentors_list[a.suborg.id] = UserProfile.objects.filter(role=2, suborg_full_name=a.suborg.id, gsoc_year=GsocYear.objects.first()) + mentors_list[a.suborg.id] = UserProfile.objects.filter( + role=2, suborg_full_name=a.suborg.id, gsoc_year=GsocYear.objects.first()) gsoc_year = GsocYear.objects.first() if len(applications) == 0: return redirect(reverse("suborg:register_suborg")) @@ -50,7 +52,7 @@ def register_suborg(request): if request.method == "GET": form = SubOrgApplicationForm( initial={"gsoc_year": gsoc_year, "suborg_admin_email": email} - ) + ) elif request.method == "POST": form = SubOrgApplicationForm(request.POST, request.FILES) @@ -91,21 +93,21 @@ def update_application(request, application_id): command="update_site_template", data=json.dumps({"template": "ideas.html"}), success=None, - ).all() + ).all() if len(s) == 0: - time = timezone.now() + time = timezone.now() Scheduler.objects.create( command="update_site_template", data=json.dumps({"template": "ideas.html"}), activation_date=time, - ) + ) return redirect(reverse("suborg:post_register")) return render( request, "update_suborg.html", {"form": form, "message": message, "id": application_id}, - ) + ) def post_register(request): @@ -131,7 +133,7 @@ def accept_application(request, application_id): application.suborg_admin.email, application.suborg_admin_2_email, application.suborg_admin_3_email - ] + ] for email in emails: try: admin = User.objects.get(email=email) @@ -143,7 +145,7 @@ def accept_application(request, application_id): suborg_full_name=suborg, reminder_disabled=False, github_handle=None, - ) + ) user.save() except Exception: pass @@ -153,7 +155,7 @@ def accept_application(request, application_id): user_suborg=suborg, gsoc_year=gsoc_year, email=email - ) + ) return redirect(reverse("admin:gsoc_suborgdetails_change", args=[application_id])) @@ -177,7 +179,7 @@ def add_mentor(request, application_id): if application.suborg_admin_email != request.user.email: messages.error( request, "You are not authorized to add mentors for this suborg." - ) + ) return redirect(reverse("suborg:application_list")) MentorFormSet = modelformset_factory(RegLink, fields=("email",)) @@ -199,7 +201,7 @@ def add_mentor(request, application_id): gsoc_year=application.gsoc_year, user_suborg=application.suborg, user_role=2, + ) ) - ) return render(request, "add_mentor.html", {"formset": formset}) From 634966beddd2298aae0420ceadb9c0e837cab459 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 2 Feb 2023 02:29:50 -0700 Subject: [PATCH 1125/1137] Update pep8.yml --- .github/workflows/pep8.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pep8.yml b/.github/workflows/pep8.yml index 6f5e127e..3a36fc7d 100644 --- a/.github/workflows/pep8.yml +++ b/.github/workflows/pep8.yml @@ -26,6 +26,6 @@ jobs: run: | for file in ${{ steps.changed-files.outputs.all_changed_files }}; do if [[ $file == *.py ]]; - then pycodestyle --first $file; + then pycodestyle --max-line-length=150 --first $file; fi; done From f30af235bbe8361e68f1803b422deed6d04e3ba8 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 2 Feb 2023 15:34:56 -0700 Subject: [PATCH 1126/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index b47d189e..c16411c8 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -112,7 +112,7 @@

    Friends of the PSF

    Here's some more interesting organizations that use Python!

      -
    • TARDIS TARDIS is an open-source Monte Carlo radiative-transfer spectral synthesis code for 1D models of supernova ejecta. It is designed for rapid spectral modelling of supernovae. It is developed and maintained by a multi-disciplinary team iincluding software engineers, computer scientists, statisticians, and astrophysicists.
    • +
    • TARDIS TARDIS is an open-source Monte Carlo radiative-transfer spectral synthesis code for 1D models of supernova ejecta. It is designed for rapid spectral modelling of supernovae. It is developed and maintained by a multi-disciplinary team iincluding software engineers, computer scientists, statisticians, and astrophysicists.
    From b0baed07172d9d58ddb38a24a786026e883d37ba Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 2 Feb 2023 15:40:55 -0700 Subject: [PATCH 1127/1137] Update deadlines.html --- gsoc/templates/site/deadlines.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index a2c2f7f8..db842428 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -1,3 +1,6 @@ + + + From 0058c1b5079f5a73dd2613e39581b6a71e3772db Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Thu, 2 Feb 2023 15:41:28 -0700 Subject: [PATCH 1128/1137] Update ideas.html --- gsoc/templates/site/ideas.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index c16411c8..9e66a5a9 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -1,3 +1,5 @@ + + From e91ac86a414fcf2bdab9832e21498c7efdba2a74 Mon Sep 17 00:00:00 2001 From: Apache Date: Fri, 3 Feb 2023 16:01:42 -0800 Subject: [PATCH 1129/1137] update calander code --- gsoc/common/utils/build_tasks.py | 58 +++++++++++++++--------------- gsoc/templates/site/deadlines.html | 4 +-- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/gsoc/common/utils/build_tasks.py b/gsoc/common/utils/build_tasks.py index b67db020..6a948a0a 100644 --- a/gsoc/common/utils/build_tasks.py +++ b/gsoc/common/utils/build_tasks.py @@ -307,33 +307,36 @@ def build_add_end_to_calendar(builder): def build_add_start_to_calendar(builder): data = json.loads(builder.data) - creds = getCreds() - if creds: - service = build("calendar", "v3", credentials=creds, cache_discovery=False) - event = { - "summary": data["title"], - "start": {"date": data["date"]}, - "end": {"date": data["date"]}, + try: + creds = getCreds() + if creds: + service = build("calendar", "v3", credentials=creds, cache_discovery=False) + event = { + "summary": data["title"], + "start": {"date": data["date"]}, + "end": {"date": data["date"]}, } - cal_id = builder.timeline.calendar_id if builder.timeline else "primary" - if not data["event_id"]: - event = ( - service.events() - .insert(calendarId=cal_id, body=event) - .execute() + cal_id = builder.timeline.calendar_id if builder.timeline else "primary" + if not data["event_id"]: + event = ( + service.events() + .insert(calendarId=cal_id, body=event) + .execute() ) - item = GsocStartDate.objects.get(id=data["id"]) - item.event_id = event.get("id") - item.save() - else: - service.events().update( - calendarId=cal_id, eventId=data["event_id"], body=event + item = GsocStartDate.objects.get(id=data["id"]) + item.event_id = event.get("id") + item.save() + else: + service.events().update( + calendarId=cal_id, eventId=data["event_id"], body=event ).execute() - else: - raise Exception( - f"Please get the Access Token: " + - f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" + else: + raise Exception( + f"Please get the Access Token: " + + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" ) + except Exception as e: + return str(e) def build_add_end_standard_to_calendar(builder): @@ -345,27 +348,26 @@ def build_add_end_standard_to_calendar(builder): "summary": data["title"], "start": {"date": data["date"]}, "end": {"date": data["date"]}, - } + } cal_id = builder.timeline.calendar_id if builder.timeline else "primary" if not data["event_id"]: event = ( service.events() .insert(calendarId=cal_id, body=event) .execute() - ) + ) item = GsocEndDateDefault.objects.get(id=data["id"]) item.event_id = event.get("id") item.save() else: service.events().update( calendarId=cal_id, eventId=data["event_id"], body=event - ).execute() + ).execute() else: raise Exception( f"Please get the Access Token: " + f"{settings.OAUTH_REDIRECT_URI + 'authorize'}" - ) - + ) def build_evaluation_reminder(builder): data = json.loads(builder.data) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index db842428..1f3e517e 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -143,9 +143,9 @@

    Blogging schedule (Contributor Deadlines)

    }; var offset = new Date().getTimezoneOffset(); var timezone = moment.tz.guess(offset).replaceAll('/', '%2F') - cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=oivfirmu8r2mc15kv1uhmmr01g%40group.calendar.google.com&color=%23853104&ctz=${timezone}` + cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&color=%23853104&ctz=${timezone}` document.getElementById('cal1').src = cal1Url; - cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=oivfirmu8r2mc15kv1uhmmr01g%40group.calendar.google.com&color=%23853104&ctz=${timezone}&mode=AGENDA` + cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&color=%23853104&ctz=${timezone}&mode=AGENDA` document.getElementById('cal2').src = cal2Url; From 7865e0d673732f2cefa0aa905e0c7129c2e6de46 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 3 Feb 2023 17:05:28 -0700 Subject: [PATCH 1130/1137] Update deadlines.html --- gsoc/templates/site/deadlines.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index 1f3e517e..4a1092f6 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -125,7 +125,7 @@

    Blogging schedule (Contributor Deadlines)

    - iCal Link + iCal Link

    Please note Google's GSoC dates and deadlines.

    @@ -143,9 +143,9 @@

    Blogging schedule (Contributor Deadlines)

    }; var offset = new Date().getTimezoneOffset(); var timezone = moment.tz.guess(offset).replaceAll('/', '%2F') - cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&color=%23853104&ctz=${timezone}` + cal1Url = `https://calendar.google.com/calendar/embed?src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&ctz=America%2FDenver&ctz=${timezone}` document.getElementById('cal1').src = cal1Url; - cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&color=%23853104&ctz=${timezone}&mode=AGENDA` + cal2Url = `https://calendar.google.com/calendar/embed?src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&ctz=America%2FDenver&ctz=${timezone}&mode=AGENDA` document.getElementById('cal2').src = cal2Url; From da4689e4de44ad382f510653457a67fcfd133f7a Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Fri, 3 Feb 2023 17:07:12 -0700 Subject: [PATCH 1131/1137] Update deadlines.html --- gsoc/templates/site/deadlines.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index 4a1092f6..7cd1b8a8 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -143,9 +143,9 @@

    Blogging schedule (Contributor Deadlines)

    }; var offset = new Date().getTimezoneOffset(); var timezone = moment.tz.guess(offset).replaceAll('/', '%2F') - cal1Url = `https://calendar.google.com/calendar/embed?src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&ctz=America%2FDenver&ctz=${timezone}` + cal1Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&color=%23853104&ctz=${timezone}` document.getElementById('cal1').src = cal1Url; - cal2Url = `https://calendar.google.com/calendar/embed?src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&ctz=America%2FDenver&ctz=${timezone}&mode=AGENDA` + cal2Url = `https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23FFFFFF&src=gbnrvjn3uci0jb6gur6utombd8%40group.calendar.google.com&color=%23853104&ctz=${timezone}&mode=AGENDA` document.getElementById('cal2').src = cal2Url; From d605bf0800eaf9cde80b31ad35755a7304d500c7 Mon Sep 17 00:00:00 2001 From: Apache Date: Sun, 5 Feb 2023 14:19:07 -0800 Subject: [PATCH 1132/1137] fix 500 --- suborg/views.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/suborg/views.py b/suborg/views.py index 9f6b3447..bfd13911 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -150,12 +150,13 @@ def accept_application(request, application_id): except Exception: pass except User.DoesNotExist: - RegLink.objects.create( - user_role=1, - user_suborg=suborg, - gsoc_year=gsoc_year, - email=email - ) + if email is not None: + RegLink.objects.create( + user_role=1, + user_suborg=suborg, + gsoc_year=gsoc_year, + email=email + ) return redirect(reverse("admin:gsoc_suborgdetails_change", args=[application_id])) From 3d4034ca1cf2213b0228c8fe99a461b94164b24e Mon Sep 17 00:00:00 2001 From: Apache Date: Sat, 11 Feb 2023 17:40:23 -0800 Subject: [PATCH 1133/1137] admin edit suborg app --- gsoc/admin.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gsoc/admin.py b/gsoc/admin.py index 4a22927c..9ff061d1 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -698,19 +698,19 @@ class SubOrgDetailsAdmin(admin.ModelAdmin): "suborg_admin_3_email", "past_gsoc_experience", "suborg_in_past", - "source_code", - "docs", +# "source_code", +# "docs", "anything_else", "suborg_name", - "description", - "logo", +# "description", +# "logo", "primary_os_license", - "ideas_list", - "chat", - "mailing_list", - "twitter_url", - "blog_url", - "homepage", +# "ideas_list", +# "chat", +# "mailing_list", +# "twitter_url", +# "blog_url", +# "homepage", "accepted", "changed", "last_reviewed_at", From 579e874e07b82b538472e2e36b153ef33f654ff0 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Wed, 22 Mar 2023 14:21:49 -0600 Subject: [PATCH 1134/1137] make accept update form --- suborg/views.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/suborg/views.py b/suborg/views.py index bfd13911..5ec14934 100644 --- a/suborg/views.py +++ b/suborg/views.py @@ -158,6 +158,26 @@ def accept_application(request, application_id): email=email ) + form = SubOrgApplicationForm(request.POST, request.FILES, instance=instance) + if form.is_valid(): + suborg_details = form.save() + suborg_details.changed = True + suborg_details.updated_at = timezone.now() + suborg_details.save() + suborg_details.send_update_notification() + s = Scheduler.objects.filter( + command="update_site_template", + data=json.dumps({"template": "ideas.html"}), + success=None, + ).all() + if len(s) == 0: + time = timezone.now() + Scheduler.objects.create( + command="update_site_template", + data=json.dumps({"template": "ideas.html"}), + activation_date=time, + ) + return redirect(reverse("admin:gsoc_suborgdetails_change", args=[application_id])) From 0cd720ba465817d6fa5ba9b29125723427db69a6 Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Tue, 21 Nov 2023 16:04:27 -0700 Subject: [PATCH 1135/1137] dont let people register themselves --- gsoc/templates/registration/login.html | 3 --- gsoc/urls.py | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/gsoc/templates/registration/login.html b/gsoc/templates/registration/login.html index bff5de33..98fc55de 100644 --- a/gsoc/templates/registration/login.html +++ b/gsoc/templates/registration/login.html @@ -49,9 +49,6 @@

    - {% endif %} diff --git a/gsoc/urls.py b/gsoc/urls.py index 234e687e..080c1bf2 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -37,7 +37,8 @@ # Add Django site authentication urls (for login, logout, password management) urlpatterns += [ url("accounts/", include("django.contrib.auth.urls")), - url("accounts/new", gsoc.views.new_account_view, name="new_account"), +# remove accounts/new url so people cant register themselfs... +# url("accounts/new", gsoc.views.new_account_view, name="new_account"), url("accounts/register", gsoc.views.register_view, name="register"), url("accounts/change_password", gsoc.views.change_password, name="change_password"), url("accounts/change_info", gsoc.views.change_info, name="change_info"), From 4c34aa015a013f401463a4bc52a0c93b9a32d53e Mon Sep 17 00:00:00 2001 From: Terri Oda Date: Wed, 24 Jan 2024 17:01:40 -0800 Subject: [PATCH 1136/1137] fix: update ideas page with some student info --- gsoc/templates/site/ideas.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gsoc/templates/site/ideas.html b/gsoc/templates/site/ideas.html index 9e66a5a9..a50d56b0 100644 --- a/gsoc/templates/site/ideas.html +++ b/gsoc/templates/site/ideas.html @@ -61,7 +61,9 @@

    Ideas

    -

    If you're a sub-org who wants to join, please +

    Students: Instructions on getting started. Right now, we're still preparing for GSoC 2024 and we expect to have a close to complete list of projects by Feb 5, 2024.

    + +

    Sub-orgs: if you're a sub-org who wants to join, please read the information for sub-orgs.

    From 0b4be4ceb6c6e56559840d11274ef38407b91e7f Mon Sep 17 00:00:00 2001 From: Terri Oda Date: Mon, 26 Aug 2024 12:13:12 -0700 Subject: [PATCH 1137/1137] docs: indicate that this system is no longer in use as of 2024. --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 5a399744..768faa85 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # Python-GSoC Blogging platform -Blog and management platform for PSF for running GSoC +Blog and management platform for PSF for running GSoC. This system was in use until 2024 but will no longer be maintained. ## Build Status