From 543d7bc9dccd2ba2c52c2a15cb02d7f7c35e7cde Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Sat, 8 Nov 2025 11:35:50 -0500 Subject: [PATCH] feat: Core models implementation - Phase 1 complete Settings Configuration: - Split settings into base.py, local.py, production.py - Configured all 60+ installed packages - Set up PostgreSQL, Redis, Celery, Channels - Configured caching, sessions, logging - Added security settings for production Core Models (apps/core/models.py): - BaseModel: UUID primary key + timestamps + lifecycle hooks - VersionedModel: Automatic version tracking with DirtyFieldsMixin - Country, Subdivision, Locality: Location reference data - DatePrecisionMixin: Track date precision (year/month/day) - SoftDeleteMixin: Soft-delete functionality - ActiveManager & AllObjectsManager: Query managers User Models (apps/users/models.py): - Custom User model with UUID, email-based auth - OAuth support (Google, Discord) - MFA support fields - Ban/unban functionality - UserRole: Role-based permissions (user/moderator/admin) - UserProfile: Extended user info and preferences App Structure: - Created 7 Django apps with proper configs - Set up migrations for core and users apps - All migrations applied successfully to SQLite Testing: - Django check passes with only 1 warning (static dir) - Database migrations successful - Ready for entity models (Park, Ride, Company) Next: Implement entity models for parks, rides, companies --- django/apps/__init__.py | 0 .../apps/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 168 bytes django/apps/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 173 bytes .../core/__pycache__/apps.cpython-313.pyc | Bin 0 -> 614 bytes .../core/__pycache__/models.cpython-313.pyc | Bin 0 -> 11562 bytes django/apps/core/apps.py | 11 + django/apps/core/migrations/0001_initial.py | 194 +++++++++ django/apps/core/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 4748 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 184 bytes django/apps/core/models.py | 264 +++++++++++++ django/apps/entities/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 177 bytes .../entities/__pycache__/apps.cpython-313.pyc | Bin 0 -> 634 bytes .../__pycache__/models.cpython-313.pyc | Bin 0 -> 175 bytes django/apps/entities/apps.py | 11 + django/apps/entities/models.py | 0 django/apps/media/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 174 bytes .../media/__pycache__/apps.cpython-313.pyc | Bin 0 -> 619 bytes .../media/__pycache__/models.cpython-313.pyc | Bin 0 -> 172 bytes django/apps/media/apps.py | 11 + django/apps/media/models.py | 0 django/apps/moderation/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 179 bytes .../__pycache__/apps.cpython-313.pyc | Bin 0 -> 644 bytes .../__pycache__/models.cpython-313.pyc | Bin 0 -> 177 bytes django/apps/moderation/apps.py | 11 + django/apps/moderation/models.py | 0 django/apps/notifications/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 182 bytes .../__pycache__/apps.cpython-313.pyc | Bin 0 -> 659 bytes .../__pycache__/models.cpython-313.pyc | Bin 0 -> 180 bytes django/apps/notifications/apps.py | 11 + django/apps/notifications/models.py | 0 django/apps/users/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 174 bytes .../users/__pycache__/apps.cpython-313.pyc | Bin 0 -> 781 bytes .../users/__pycache__/models.cpython-313.pyc | Bin 0 -> 8781 bytes django/apps/users/apps.py | 17 + django/apps/users/migrations/0001_initial.py | 370 ++++++++++++++++++ django/apps/users/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 8929 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 185 bytes django/apps/users/models.py | 257 ++++++++++++ django/apps/versioning/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 179 bytes .../__pycache__/apps.cpython-313.pyc | Bin 0 -> 644 bytes .../__pycache__/models.cpython-313.pyc | Bin 0 -> 177 bytes django/apps/versioning/apps.py | 11 + django/apps/versioning/models.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 170 bytes .../config/__pycache__/urls.cpython-313.pyc | Bin 0 -> 1036 bytes django/config/settings.py | 123 ------ django/config/settings/__init__.py | 17 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 613 bytes .../settings/__pycache__/base.cpython-313.pyc | Bin 0 -> 7566 bytes .../__pycache__/local.cpython-313.pyc | Bin 0 -> 1075 bytes django/config/settings/base.py | 322 +++++++++++++++ django/config/settings/local.py | 51 +++ django/config/settings/production.py | 67 ++++ django/db.sqlite3 | Bin 0 -> 507904 bytes 63 files changed, 1625 insertions(+), 123 deletions(-) create mode 100644 django/apps/__init__.py create mode 100644 django/apps/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/core/__init__.py create mode 100644 django/apps/core/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/core/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/core/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/core/apps.py create mode 100644 django/apps/core/migrations/0001_initial.py create mode 100644 django/apps/core/migrations/__init__.py create mode 100644 django/apps/core/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 django/apps/core/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/core/models.py create mode 100644 django/apps/entities/__init__.py create mode 100644 django/apps/entities/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/entities/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/entities/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/entities/apps.py create mode 100644 django/apps/entities/models.py create mode 100644 django/apps/media/__init__.py create mode 100644 django/apps/media/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/media/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/media/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/media/apps.py create mode 100644 django/apps/media/models.py create mode 100644 django/apps/moderation/__init__.py create mode 100644 django/apps/moderation/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/moderation/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/moderation/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/moderation/apps.py create mode 100644 django/apps/moderation/models.py create mode 100644 django/apps/notifications/__init__.py create mode 100644 django/apps/notifications/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/notifications/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/notifications/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/notifications/apps.py create mode 100644 django/apps/notifications/models.py create mode 100644 django/apps/users/__init__.py create mode 100644 django/apps/users/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/users/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/users/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/users/apps.py create mode 100644 django/apps/users/migrations/0001_initial.py create mode 100644 django/apps/users/migrations/__init__.py create mode 100644 django/apps/users/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 django/apps/users/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/users/models.py create mode 100644 django/apps/versioning/__init__.py create mode 100644 django/apps/versioning/__pycache__/__init__.cpython-313.pyc create mode 100644 django/apps/versioning/__pycache__/apps.cpython-313.pyc create mode 100644 django/apps/versioning/__pycache__/models.cpython-313.pyc create mode 100644 django/apps/versioning/apps.py create mode 100644 django/apps/versioning/models.py create mode 100644 django/config/__pycache__/__init__.cpython-313.pyc create mode 100644 django/config/__pycache__/urls.cpython-313.pyc delete mode 100644 django/config/settings.py create mode 100644 django/config/settings/__init__.py create mode 100644 django/config/settings/__pycache__/__init__.cpython-313.pyc create mode 100644 django/config/settings/__pycache__/base.cpython-313.pyc create mode 100644 django/config/settings/__pycache__/local.cpython-313.pyc create mode 100644 django/config/settings/base.py create mode 100644 django/config/settings/local.py create mode 100644 django/config/settings/production.py create mode 100644 django/db.sqlite3 diff --git a/django/apps/__init__.py b/django/apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/__pycache__/__init__.cpython-313.pyc b/django/apps/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf8e4e226af33003923e94af38b6fb5992674179 GIT binary patch literal 168 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 literal 0 HcmV?d00001 diff --git a/django/apps/core/apps.py b/django/apps/core/apps.py new file mode 100644 index 00000000..8acdc11d --- /dev/null +++ b/django/apps/core/apps.py @@ -0,0 +1,11 @@ +""" +Core app configuration. +""" + +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.core' + verbose_name = 'Core' diff --git a/django/apps/core/migrations/0001_initial.py b/django/apps/core/migrations/0001_initial.py new file mode 100644 index 00000000..4bedf241 --- /dev/null +++ b/django/apps/core/migrations/0001_initial.py @@ -0,0 +1,194 @@ +# Generated by Django 4.2.8 on 2025-11-08 16:35 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import django_lifecycle.mixins +import model_utils.fields +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Country", + fields=[ + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=255, unique=True)), + ( + "code", + models.CharField( + help_text="ISO 3166-1 alpha-2 country code", + max_length=2, + unique=True, + ), + ), + ( + "code3", + models.CharField( + blank=True, + help_text="ISO 3166-1 alpha-3 country code", + max_length=3, + ), + ), + ], + options={ + "verbose_name_plural": "countries", + "db_table": "countries", + "ordering": ["name"], + }, + bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model), + ), + migrations.CreateModel( + name="Subdivision", + fields=[ + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=255)), + ( + "code", + models.CharField( + help_text="ISO 3166-2 subdivision code (without country prefix)", + max_length=10, + ), + ), + ( + "subdivision_type", + models.CharField( + blank=True, + help_text="Type of subdivision (state, province, region, etc.)", + max_length=50, + ), + ), + ( + "country", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="subdivisions", + to="core.country", + ), + ), + ], + options={ + "db_table": "subdivisions", + "ordering": ["country", "name"], + "unique_together": {("country", "code")}, + }, + bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model), + ), + migrations.CreateModel( + name="Locality", + fields=[ + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=255)), + ( + "latitude", + models.DecimalField( + blank=True, decimal_places=6, max_digits=9, null=True + ), + ), + ( + "longitude", + models.DecimalField( + blank=True, decimal_places=6, max_digits=9, null=True + ), + ), + ( + "subdivision", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="localities", + to="core.subdivision", + ), + ), + ], + options={ + "verbose_name_plural": "localities", + "db_table": "localities", + "ordering": ["subdivision", "name"], + "indexes": [ + models.Index( + fields=["subdivision", "name"], + name="localities_subdivi_675d5a_idx", + ) + ], + }, + bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model), + ), + ] diff --git a/django/apps/core/migrations/__init__.py b/django/apps/core/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/core/migrations/__pycache__/0001_initial.cpython-313.pyc b/django/apps/core/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88ad81531b8a85a61ca833aea99280be424e3a0b GIT binary patch literal 4748 zcmeHK&2JmW72jQce^{pYAzQLO))e)zwnXaNW~>@<<;bxuCjci#f>iBd$sLg!uekK= z(w5XqfC4=i1&ZeCligeX1ozY&MP0ZUT?`Z`pc@6Z$T5AhOGy+cxdDWArn>WL^7cPW3_&j)eQTtav$NiIajy}G2e)3-QxNQ!g#kF zq7~p5%6lQ+FxXCZ>i~Wc(2C{-(7AA~P7k^WS}|wkz2|BPD2@^+$+U8Ql!9+K$D_Wi zXFXMz>Ylrh?UsS>nQke>b}8>YmjRc5VZaY$K!2WpXZ{D!6*TB@Wau5)A4Vf?_K^r$ zbKQ9arbY@2-SUEXhHU8!+0AYlx{A_h)ZttJ+9GgHK$%${js4`j8~=rOKML)XX@Q0w9UZm@5eVWF8dx#vRf}xXK#5?*!s@Ezp#c?dD0SVUL9A+)Qq(bx zKEtGFm{_hVo0taSoYpoptWrT!B`;xD3&I@INR4Ql3fY#Q;B6W*G0_xV+riY!><+#I z73auTwqzYw%T|T@>s9S(9TZA%^zV$^M<0vxvr9|Uv!bHcD$4YnSaKtTw~7x0=D|Zl z6|C1}3vXE@!RAh~xqVDy{tO@Ur+u(%2>9?7b*1`*Tw?aj2@Sb3Y1o9(MCXF#nqDW0 zPJ?PucJT-rM1{pw%dq-hX6y$zbk(E*qvqaad_~2?CXJwaQPrMlCR}hN-kE4;;dqsE zqIojxtWcbMu2~hMZXL&5BX~pGk`5TilF$)vtix8ewrhB2{u6kL#>OeHlcoiUof2!r zc&1fL&?2}D(M*ZhDrKbuR*ND1Z5~8BoSHk)l0k5`J;rc1sxsWrumy}DSZ9QB86j8( z#KtZfgx#zp4;fpBhTMishXO(Pvq)3ENkheYngoCS1_4xNn3ZMnR3 zV^LjHWKG?IMX+0%lT(O<-SawD640tD-oobJxR0c(G$=c3mt`83W!MpQ9YZ@R%TMcy z&iD?!i?Y0-5!2GODy|wJ02i7Cn}SAFT*Kh5T7ozU<0Or&ID7CBy9CpSa~GEDmZqCd zOf+_S?M2wR5H1?UE)<_=o9bN2%pz3XPyoyK>cVM@bB4IljtNXatetX7g0%OYB_ zZedfF2|M85!jGfB$vuYBV&*JGH%JaRP^lMlC7=TaS%ql2ZV{#QWEyYPKmn7SyOf+# ztC=~*h}_Aaj@-=5%&gqbO18F57!#cbD`@6#0j@uCZ+hZi-g}cseYw{1bD=~tIJ_4e zwtEMlmm0Q{BhBP^BRSqo-e@Fm*y8xh=!-PpZ1$$=B&^ol*2u}4M^1D%l#o+u2bZAYg=;U0fI+0*)* z=O*Xv!3le4^s5iqXiqE5B{R*$wMODvGjY3-xc%DykLcf{yYWYdWqu(PZhgUbz&HFe zgRjw_-;KR@8obZ!(Xm6HhvDr9yklHy;xuxfGKEvn0%0I=V|&RlJ3aYQdLcE_bB**| zGkvR(zST^xGyv_fdHaggVfL7x1HdahH+0<|8h_>A9n9I88@r=7{}=3a{uaQ#)d~BR zpDFAvZWDXS341vG_3Z23Z<2=|fg$e$|5%uH&IB`iR8z^XSv5?x$DKnUdwd35a>Xb0JL{Hj z@{k$D9arx0lHG9bF64a{$%pLW)Ie5m-RM4p77Z?g37m1a$?u@c!_Sm22-2E3J3>i}Z literal 0 HcmV?d00001 diff --git a/django/apps/core/migrations/__pycache__/__init__.cpython-313.pyc b/django/apps/core/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6da67ee594c05b6176f7833f76a770b9acb68103 GIT binary patch literal 184 zcmey&%ge<81fGTbnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}rXKeRZts93)w zF(1FUB6o%PkHXWNL@y$eZPlDjt zOaBP{YdkK4H&4AOYwx~EvIFzxy?MX)dtd9xWK8hb=EHymFk}N5<-=%XuWxd7T$dq+EvMN# zQRY>g&ExYblZB3-s{F7q)k`IdY&pTQbS(QKZMdQMPL89{hMbq;Tyk#5oMU)XIDMP* z_l+oe&kpAc1=V*v}{T39_QNNBvLMn0X1)o z)SQE>dX1OZqvVa2pc5mC3X)uz(`KHeMO8}tI|Wq~21I)HKz^(dAR*~mOrdd8*b0|G@1pohR(Br#3~sHq_CG&g-JE_of*TvqaTM4Q=UJ6vM_c_29Wl5CcVv#J si*Rf>P{wB#AY&>+I)f&o-%5reCLr%KNa~iKerR!OQL%nW zVorXMetKp}Mro3Ma!!6;Do`w=C^ILgq$n{tTQ{|$0H`3fNIxYjF)uw|Ke3>oSU)wd uB(o$Fs4_P{B{ip5ub}c4hYgTjnv-f*#0oSXWPLG+@sXL4k+Fyw$N~U$J}vS9 literal 0 HcmV?d00001 diff --git a/django/apps/entities/apps.py b/django/apps/entities/apps.py new file mode 100644 index 00000000..4b090053 --- /dev/null +++ b/django/apps/entities/apps.py @@ -0,0 +1,11 @@ +""" +Entities app configuration. +""" + +from django.apps import AppConfig + + +class EntitiesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.entities' + verbose_name = 'Entities' diff --git a/django/apps/entities/models.py b/django/apps/entities/models.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/media/__init__.py b/django/apps/media/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/media/__pycache__/__init__.cpython-313.pyc b/django/apps/media/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ba36b500daca3e90da38cfdb7c7108c21a59f72 GIT binary patch literal 174 zcmey&%ge<81P%rKnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4PIKeRZts93)w zF(`275aS~=BO_xGGmr%U$8ao` literal 0 HcmV?d00001 diff --git a/django/apps/media/__pycache__/apps.cpython-313.pyc b/django/apps/media/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4296cff53dcf0c54c6854d00e31234c37c5cc5d5 GIT binary patch literal 619 zcmXw0OK;RL5Vn)-ZpgN(T2vleA+34~63GdP141ewBwoE(+$ve##G4wjNfITzPiTfq#l~_p5ZNxbO+tTSM9YWF$U1_zLFVGO8QYoz8J zT)k_&#J)~HXbCzsqO2h;lsRo@X;#*i#J^KeWobZUpB~Bc1_LCdeUqtcCheN()TH?W zZfl)z?0OHyce1+w$1?H%mi1RckL9dVx58MZUtjLxxw+h|YbhA{cqn&8uc{{xC?s__5- literal 0 HcmV?d00001 diff --git a/django/apps/media/__pycache__/models.cpython-313.pyc b/django/apps/media/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10abc1d6d0ac7898b7a7f6c0e4a08466fb4f38ac GIT binary patch literal 172 zcmey&%ge<81dj^%GePuY5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iierR!OQL%nW zVorXMetKp}Mro3Ma!!6;Do`w=C^ILgq$n{tTQ{|$0H`3fNIxYjF)uw|Ke3>oSU)#4 sB{NY!H$Npcr&zC`@)m~;kX@RSYFESxG#g}hF^KVznURsPh#ANN0L#}biU0rr literal 0 HcmV?d00001 diff --git a/django/apps/media/apps.py b/django/apps/media/apps.py new file mode 100644 index 00000000..9eab08e5 --- /dev/null +++ b/django/apps/media/apps.py @@ -0,0 +1,11 @@ +""" +Media app configuration. +""" + +from django.apps import AppConfig + + +class MediaConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.media' + verbose_name = 'Media' diff --git a/django/apps/media/models.py b/django/apps/media/models.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/moderation/__init__.py b/django/apps/moderation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/moderation/__pycache__/__init__.cpython-313.pyc b/django/apps/moderation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..496944e2d9ca4147a87f15f4a9b9beac798666c6 GIT binary patch literal 179 zcmey&%ge<81P%rKnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}rDKeRZts93)w zF(2yU1^f+;D_`N#8))gBal92+@_5GI%r~#q*4CKd`PNLTpZNZ;#^o8TmDPPz&Iltw z7-il|`oaS*A~(q%Vg7Bxfp1KR1V{jx%w}gV>=HqaB;7A zo7JUCidk}u<;XU9Qk4gtR?jOTi^T*BYkS0%W&SJrE1AZj2|2H`W5Kx@bB+|9wE8CJ z@4HN{kqPJX3R*3zTGR^R?Ly46PHLWECd4q639Pf(JP|Nct>{V34(F}L3FSP~8md{R zMawzZ%2#-anWk@A0d1-?sUR&Yebmj;T&h~&-w{+&YRK|;55$KC9R#GV-PE>~+J;?Q zy`40tVAt6To1)!D@tG`dJ^UWrSZ>_?_-uK7_tPu5cIh;Y12f{hP&xLrv7V%*2D@rU uW{F&cWi#O;H>1a}gQi_#dk4iYpHlkU^Js7p61w+i=+o%S=>@^35B>}89=2!z literal 0 HcmV?d00001 diff --git a/django/apps/moderation/__pycache__/models.cpython-313.pyc b/django/apps/moderation/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1917e1a2483843f891b08a58cd6b8af16bf8c8e6 GIT binary patch literal 177 zcmey&%ge<81dj^%GePuY5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|LAerR!OQL%nW zVorXMetKp}Mro3Ma!!6;Do`w=C^ILgq$n{tTQ{|$0H`3fNIxYjF)uw|Ke3>oSU)#E uCABEABr`t`%*-j)E2zB1VFMH{%}KQ@Vg*_NazHVN@sXL4k+Fyw$N~W3CoaJN literal 0 HcmV?d00001 diff --git a/django/apps/moderation/apps.py b/django/apps/moderation/apps.py new file mode 100644 index 00000000..7989d7f3 --- /dev/null +++ b/django/apps/moderation/apps.py @@ -0,0 +1,11 @@ +""" +Moderation app configuration. +""" + +from django.apps import AppConfig + + +class ModerationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.moderation' + verbose_name = 'Moderation' diff --git a/django/apps/moderation/models.py b/django/apps/moderation/models.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/notifications/__init__.py b/django/apps/notifications/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/notifications/__pycache__/__init__.cpython-313.pyc b/django/apps/notifications/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6c363196f0e240dcccced31f0255d965ecebdaf GIT binary patch literal 182 zcmey&%ge<81P%rKnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}rPKeRZts93)w zF(lxc3JTQvD8z9O-_Ob5mv>6N^hmAZG|Bwerr^S>&s^)|x#0Sae?5g?2* z?-hONfj1#H$&@hvHetcB_t5WR!7qAIe>ok_l#vUWrADesPtv-cWU5-oqJh>F56@jf zMD4Ysx_;KWvJRr7bfc^%D4daF0u<|ejDpWR2$&B&7C@i%U{Lgs%#*BCl|b7G$g(t$X73Nhry2tUB;C-78!p)z zt~JH=XK>eTiL0W=NWPKPtw%qD8>^jrU!Je_r(a*g^{b#+6xadhxyo>?oy|%eI=IVs uU=FCuaA-Ds+^y*e9H8nBs_!7V@F}H#JdXyKA)(X1eV^X{9$gY-ZSp?=O1#Vf literal 0 HcmV?d00001 diff --git a/django/apps/notifications/__pycache__/models.cpython-313.pyc b/django/apps/notifications/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70b457c5a0c85008233c2b509ae9cad302a967d0 GIT binary patch literal 180 zcmey&%ge<81dj^%GePuY5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|LwerR!OQL%nW zVorXMetKp}Mro3Ma!!6;Do`w=C^ILgq$n{tTQ{|$0H`3fNIxYjF)uw|Ke3>oSU)en zBr`2DIk6-&Kd)FnH$Npcr&zC`@)m~;kX@RSYFESxv;*XZVi4maGb1Bo5i^hl0F2f! A3;+NC literal 0 HcmV?d00001 diff --git a/django/apps/notifications/apps.py b/django/apps/notifications/apps.py new file mode 100644 index 00000000..a581e111 --- /dev/null +++ b/django/apps/notifications/apps.py @@ -0,0 +1,11 @@ +""" +Notifications app configuration. +""" + +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.notifications' + verbose_name = 'Notifications' diff --git a/django/apps/notifications/models.py b/django/apps/notifications/models.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/users/__init__.py b/django/apps/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/users/__pycache__/__init__.cpython-313.pyc b/django/apps/users/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcee91e3a9ab90bcb6ae25141f97c69932baf8d9 GIT binary patch literal 174 zcmey&%ge<81P%rKnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4PIKeRZts93)w zF(2KczG$)vkyYXgbLDVi4maGb1Bo5i^hl0MPR+$N&HU literal 0 HcmV?d00001 diff --git a/django/apps/users/__pycache__/apps.cpython-313.pyc b/django/apps/users/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62883dc3459179d5e39db24a933b92a98098cd91 GIT binary patch literal 781 zcmYLHzmL-}6t>f(O@DC$1jiL2MK>UVL^2^UAfy9?bbz`?U-Eu@-+Ry7@pypr`BCo`-!MYI&BxX8cEO`f`JHVT?c7Z&WpSODvPR&2InR) z#HxQ(*DtIqZBtv+8tmEL*Vh`IAi$c}NicL02i&9sUgAPG@g@j*Nf-RQ7y4>w>DqGV zzRq0U%ihy6ucB-gomLs+B6?orM~$pr6^v)gfmSA>#)wp&O-`?v4ZX2VM_X6J8s#{2 zmJg0k>k4EbioB#eSkN-#rpM0?@y;lAba`)PN8@kcJz3M99a6@||aoka+<@@}gvArQJ6&Hm41jgzC9z zpB|iYO|Tk$Vlb-&vqfQ~Nz^Sugw#7u36&CxStFSs#0r3GM#|8Dc~Tb4lEl7-uV{1d zbdY_tHoB@Nc(|$KS(Cq%WKN4_fxop#T5? literal 0 HcmV?d00001 diff --git a/django/apps/users/__pycache__/models.cpython-313.pyc b/django/apps/users/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebc5caf94aa766bb977e0bb513574299d1165422 GIT binary patch literal 8781 zcmbtZS!^3gdT!p$7De&UE!l4BuuUJ5M?S{(&S6={u5DS~QA<0ML|LLGwxqTxwx*i2 zEh393L9lD@4B&Bq#5>7@y-yoIWq<$)f&f7R1j#GKS%5?X11t~(h~Fg3n?drDe1CP5 z)IoWJqybh}*Zu|=C@yA3)Y zJD1#a7V`qmKwxgg#XO3edFOpIq21_qAN%*r2WBD%A&OV=UGcCW=CvM#(mchl1g^MQ z6EHd(Vu*@o7^nD=OOceOD_+)oC}s%Mnw2ot3j^n9Luy{7MTua&2r&8@VtACO5(7pI z7{?F6h%2p18`f!k%Q_yVUFiT$+gs>#@|D_wF?7gE9;Hj^29-|Wj5NgQ0p!9#2+qQz z^eRV`qgVWla4sLj!TBFkWPTn;lmt7f^bNb%F)pM1kkNsQkO6v_tPFMwETNoMPIL=* zU93+ziSPdT0VUZjT(pq{HmICp$Cr;M&sbg(w^t-7g?~Owbm!)7jLU0GUsc(|e2$aj+>o*`oBHfz(qlC_ z?54o36-aAKYCe;*L=8Nv3mL6ovnOK{OfzjA-n(4VZTa(A{nNZ=1y{7pS}LbK%)O#} zO?m(mEbp|D)d*{uZ^H81o54sRZEwd4IQuFbxA(iojQcwHB-@bYc4Oa~UQ1e4AEq)| zb~(3V`4%#2_MR13Tru=T&1B73{MCQcO*!<;{K8#rF(-p24i~q;G_w5IfLQ1RiL8vx*wY`V_4$@Jo6ltGOj%x2%Pd(TTE|S;s}zosMTN*f&>pN9 z&f1{o@RRgeK_i=j(baMq>!EX?Jy;>8Wz?LOPG!|qjdR9G^$KVBmUl@dNwfyCbk$&c z(a7eQzK|ycFfE@(+F^;r5fR#B*lSL10fY}4b{JU~^UeEb!T|4kU?#l7E*Nsxp>V$q zA9gcQ39#U@m<$$z#Qe~0EvLS^2a1-DpHv}qHjS;O?ixC-g~@=BU$V8`^4sV563$Dq z#1ub|RLYW4Dd=K8qoE&4r9RE88QLJeT2iScote1|sAUZd&;@gCv_vSs#`Nqm^YZ0y z(6w6ue*2nfwWU%f>H1jnW+>z0o)z@1nbr?ZNMDPsTqR}FT>sD7ls!zP*99< z*G!)E-dXM88W1!#oW84OmyKa{ZOt6!dmSbNk}-$Y)-5lcgH=BgIuVl;|Hncl-u+0d zwDuL_{g1?7g^z!9s739eK1Clp z2ybw~bHYpwRPPK3FI|WW__4oqv0gw-nkX~k#R|}wFgj#@cg~kdSY*sMgi1^NN#Sv! zD0Xo!rWc)f2S7ofW9A(EAv%boD25Vly0RX`OP)C)Ln6(ePa>JxygAQIHRh;-@viz5 z%&H2m@QIx9S5dILmTJHLMx5Y9oB%)tq1Bf@@3abW*fJ_Jt2Y{W2!|4hV5-?0q8X$r=yX z2Q>B}eR@sxzLxaz6Sw^KQXInPnRIkA9{U%+_w%Mo zxBPiar7Q8g_siZtKZ=oXrK=B(7~iKZ+Y) zHJRFbXZ)*vYeJ)2+jIV&q`%NIm&FF^H!#WQ)~4BeXxTM1NnicQ>ZirV3H~mc-NNb1 z6ETa2$wR#V`-E{8&4%!+_8$9Kdhx=+9I=nYe*LCHp>1)&{V#M5Q=OB5(UXn}q+B&& zwnwS0VyH=uQ3up|VSeC^=7xpyo|&4c*Cx~yVSv`GiGLmbzN@-w8Ue3NSYjPo?aee* zd-b>CzUW-F;-Q5Hgrv7{X-3QCS=|G|DI@M!%c$!z8C?0~5`3WDg^MF2N5e_WC0nj* zwto37iCbiDyRBeGHFI{?2h*aju>{uQ_Q&#pu%0_`&mm_;dsdXI!S11-fPnc4nhjyg zFN7jbK78`w&(hDc&$G~tXU0>b*mnGd`WN2648IH)PrO@B*SL&vGxhP)dS_54rJ+{TOrpN3O#*!x(9Xi7` z3+xm%r_qq{OdE@EA*5q4^@wjLDTcMY`MjQn`$K!da-%EpFqQ-DlL(HjsLVzV8x+2! zs?iqW1^$hw%nDsMjEtscZN#S4B{k&)RWKGaQ}(cA2T`d!%UHo#gqQZP2XRwf)vV@m z_~2QXK@R1bXGxRrV)}CS7J^$*nV3yY&rMC))+=-)o70vxhs8)eAW89n#0t+T6EoAJ zwr()-*A@9qNpBq9J zpigSZ`q^D9P+P)p|O8$-OTVbI&UJe~8 zg^u99E3pgPK2NiMV`3{LwD*?V21{*&m3Uhv9DD8dMx`xDh<|508f^D(OmDRc(T;LN zE=A-@yd4;o*i{gX`ZsQXXuPW&8!W{JE0MNJ>>7y1{TsJHG}ig-*dHI`*h3hN`8RHE zi9%DfEOnQpZW;%zSRV*A`8UQuDBN0ZmP^et5sHG=FhF7d#w1vYb(W)vQZ&J-_0_2n zTLY!YK&3g#L1(ILNu1+EDUzs&;R;w8emd+Rwe?qGBiqdZ&VH-Vd9>Vds?>3+5{cUE zclwLo)~%S(eq>|1(vkQgvvKEJuiqza3usLZO~3D?(AT+2}j}i=q>T)yog9MR-;zyIOPNhP>P%PJ&05N^FAe@ zh)NKJf)KDM7TWg!OH`yQE))s;jf(_)b$Q=3d}5xcG%2AgJ{ILkax;?Ta4kuWDJ@8f zy-FguHcnyl&= zL&(3XW|18c2&pm6=6Nw$;;XWfLnTOMX*+p2=_II>-8z}90i-cFWQ3^58Z{J*796@r zzEDJR&@`S{UicDddDOQE5!#v}&mpr@BWp9j7PkP`T-#Xeu7&TlOMRYbM7-2W+P>e+6tyqAjNQ ziriF9zw|e91?&v*+{mDvC%`%yB4?7?Q2c-FGtt%~0`CdeYLUqlAcsXJjpGRl?Pkao z?RzNFfUkE?7d-M=8F?|CHj}9_so58HajR`bYKP(E1b+=wlO`c2h_ydUK24I@DMtHV z^uI{|LjSq`;&+NI?-a#%_{R3^i6SQ+ZPE0@_N2Hdo+dsEgJUb&;yqbks-^U5RZtOw zP)Ubm3H1U4GuWqSkl8faiWSmL$|LOr-D<1tY4`BoV!laI$fJu$>y!1z>&4!Y&6bg( zIP!nq%+>EXo9Q~Z8K@&|fOF0^u=4&ZutYFu>kW^FxKLJy{+{@a@8$Pr-`#BKFN*!P zP@Mp9I@#G!P-g+NLNj-!CQ@VHyK`e~Lb0Ql<~zua3^aCpK!R!o#zvK~(eVjeV164z z_9>HgS(2lGNSj!iK-!+sh4oz_z+Hm>0X2U_O(Q*_oP<$BrqTQxH01aLKJj(T+v?w# zg3k@b%1y^gO~;V(kkcD_I#g-vflmziH^$*c3EWd^>LEWL>8(WC;b*slJ`N>^U5*Zx zqJv*u*onk}p-BQODEZ8S6!9vj3=rYD*f^vT%mnYnoavhcXjt)mFeE#Bx|=w$L-9 zEowR(Ew9YUS%V6ycD>qUUBpN~PX$mcMA>nVH%6Jy@zUwHA~B7swOYDxs+qwYFc=v7vX2M!~MBW#2!K2O~W@~nYkmz>S7E?}F+(jJah)wEVP zI$eWRx!aAjN5=oRj+Xlt=XPvgyMvQKy@h%moc#=0?tM?=5`p5m;%ZbT*j0(E z8m<5TTJ*1=`!}ps;~e~=%haSe0kzGA%#N&YM;=M@o^}rJPo46?gY2y9P4T$)ME^s5#`s83@^V z7X`vUrDXyJ|98ctigyP80oaTC7L7bA`CkHq{TVgmXut^7XZbvngtl59>~JcPb(VM~ z+qC+3OW7ev{|Q270S#3X->W1>%89F+iL2$r=w@Q{A9DY){*UX$#Aq?`Q91GPX5!wtT|iglkLi3`*bng{H{k!Ou=? z1%zPpFU15LVz8O&k;TM?;-#sge4DD0(_F5xJHAue^K`P~(CCE6BkU)@va11)sK>SK zchR%r+_EAY*kxI*UV9M#wCp;f>YX))R?Vmtb}UZHHjtKN!fd8ELAFF|*Dr}19$wUV zPJl*n%;yB4hP;mX?`SBF@uGmyIDk1?4Pe4TPoms?w$yzV#SN+t_&hyG);n;d1EXc(EU# zvAUg_x%1D}5EZwo3PP663OWes1v?UraBZT$y{c$r1Z`Q!P>&z+3F`6xMMj8-_#V^` ze;0kgUgDD+k$D}>mdE9CZM!`#?~Ww6Lf;7IzY%)B5sts{Uvs;zJbLeqK<_t`-VxWp ZqxCleUT^NY-gn(`eUbdNKySXr{{XYn`;-6x literal 0 HcmV?d00001 diff --git a/django/apps/users/apps.py b/django/apps/users/apps.py new file mode 100644 index 00000000..0a698b2f --- /dev/null +++ b/django/apps/users/apps.py @@ -0,0 +1,17 @@ +""" +Users app configuration. +""" + +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.users' + verbose_name = 'Users' + + def ready(self): + """Import signal handlers when app is ready""" + # Import signals here to avoid circular imports + # import apps.users.signals + pass diff --git a/django/apps/users/migrations/0001_initial.py b/django/apps/users/migrations/0001_initial.py new file mode 100644 index 00000000..2dc5b86d --- /dev/null +++ b/django/apps/users/migrations/0001_initial.py @@ -0,0 +1,370 @@ +# Generated by Django 4.2.8 on 2025-11-08 16:35 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import django_lifecycle.mixins +import model_utils.fields +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "email", + models.EmailField( + help_text="Email address for authentication", + max_length=254, + unique=True, + ), + ), + ( + "oauth_provider", + models.CharField( + blank=True, + choices=[ + ("", "None"), + ("google", "Google"), + ("discord", "Discord"), + ], + help_text="OAuth provider used for authentication", + max_length=50, + ), + ), + ( + "oauth_sub", + models.CharField( + blank=True, + help_text="OAuth subject identifier from provider", + max_length=255, + ), + ), + ( + "mfa_enabled", + models.BooleanField( + default=False, + help_text="Whether two-factor authentication is enabled", + ), + ), + ( + "avatar_url", + models.URLField(blank=True, help_text="URL to user's avatar image"), + ), + ( + "bio", + models.TextField( + blank=True, help_text="User biography", max_length=500 + ), + ), + ( + "banned", + models.BooleanField( + db_index=True, + default=False, + help_text="Whether this user is banned", + ), + ), + ( + "ban_reason", + models.TextField(blank=True, help_text="Reason for ban"), + ), + ( + "banned_at", + models.DateTimeField( + blank=True, help_text="When the user was banned", null=True + ), + ), + ( + "reputation_score", + models.IntegerField( + default=0, + help_text="User reputation score based on contributions", + ), + ), + ( + "banned_by", + models.ForeignKey( + blank=True, + help_text="Moderator who banned this user", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="users_banned", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "db_table": "users", + "ordering": ["-date_joined"], + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name="UserRole", + fields=[ + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "role", + models.CharField( + choices=[ + ("user", "User"), + ("moderator", "Moderator"), + ("admin", "Admin"), + ], + db_index=True, + default="user", + max_length=20, + ), + ), + ("granted_at", models.DateTimeField(auto_now_add=True)), + ( + "granted_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="roles_granted", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="role", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "user_roles", + }, + bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model), + ), + migrations.CreateModel( + name="UserProfile", + fields=[ + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "email_notifications", + models.BooleanField( + default=True, help_text="Receive email notifications" + ), + ), + ( + "email_on_submission_approved", + models.BooleanField( + default=True, help_text="Email when submissions are approved" + ), + ), + ( + "email_on_submission_rejected", + models.BooleanField( + default=True, help_text="Email when submissions are rejected" + ), + ), + ( + "profile_public", + models.BooleanField( + default=True, help_text="Make profile publicly visible" + ), + ), + ( + "show_email", + models.BooleanField( + default=False, help_text="Show email on public profile" + ), + ), + ( + "total_submissions", + models.IntegerField( + default=0, help_text="Total number of submissions made" + ), + ), + ( + "approved_submissions", + models.IntegerField( + default=0, help_text="Number of approved submissions" + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="profile", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "user_profiles", + }, + bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["email"], name="users_email_4b85f2_idx"), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["banned"], name="users_banned_ee00ad_idx"), + ), + ] diff --git a/django/apps/users/migrations/__init__.py b/django/apps/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django/apps/users/migrations/__pycache__/0001_initial.cpython-313.pyc b/django/apps/users/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19b98e3aac3e220789d3f22080d5ad9eda2c9a84 GIT binary patch literal 8929 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 literal 0 HcmV?d00001 diff --git a/django/apps/versioning/__pycache__/models.cpython-313.pyc b/django/apps/versioning/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fb55383523cf2f77c5a0c2f8c96b78b3524dc22 GIT binary patch literal 177 zcmey&%ge<81dj^%GePuY5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|LAerR!OQL%nW zVorXMetKp}Mro3Ma!!6;Do`w=C^ILgq$n{tTQ{|$0H`3fNIxYjF)uw|Ke3>oSicOY wGc!LgGcR2~H$Npcr&zC`@)m~;kX@RSYFESxv;gFQVi4maGb1Bo5i^hl0OkNL(*OVf literal 0 HcmV?d00001 diff --git a/django/apps/versioning/apps.py b/django/apps/versioning/apps.py new file mode 100644 index 00000000..84c20f0c --- /dev/null +++ b/django/apps/versioning/apps.py @@ -0,0 +1,11 @@ +""" +Versioning app configuration. +""" + +from django.apps import AppConfig + + +class VersioningConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.versioning' + verbose_name = 'Versioning' diff --git a/django/apps/versioning/models.py b/django/apps/versioning/models.py new file mode 100644 index 00000000..e69de29b diff --git a/django/config/__pycache__/__init__.cpython-313.pyc b/django/config/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2b166ccc706e2b3e6327916084ecc05d3bacf60 GIT binary patch literal 170 zcmey&%ge<81fhBSnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4PEKeRZts93)w zF(} rAD@|*SrQ+wS5SG2!zMRBr8Fniu80+AGRWp)5aS~=BO_xGGmr%U3rQSc73s2P@0~Z;yVmX+5+n*b zN>ZTw27V8nB`PYqL?6u;DUEgLc`s6mPD+^-L~AO!Izbsz8c9)Vst|xE zqy>7-=o=NHT|mf%q=<0<%Ny7tqeM}pD2+S>mTFz72;)S=DojoZpNOtqEOJaaz8`L4 zEeaMZY>F|oSJ>M*BROEH$lEP>Tr;lQ)}Z%7^f*Zn;fUphkUBzWEJcp;^C2mUHrddu z>o?naGl3akWO-Q224pSxgV7$3voc|P0wUD?%xGr8oj!ZDFbJr?dQ2@{3(&ZgGHT>% z))5-PJCbefdM(O7vmIq(fx&|r;tk}%Q7uV{Az=q6;MDWZaU!RhSVo8EY7RN2f0Yfj6RYpfNH~|HsE#Dvu*p@ s>D%s)Rm;Bf)w+E>Z_VBKaO>mNWpoi;ZeMJFaqnLBoWApZNt!GA3nGv=sQ>@~ literal 0 HcmV?d00001 diff --git a/django/config/settings.py b/django/config/settings.py deleted file mode 100644 index d2be1b2f..00000000 --- a/django/config/settings.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Django settings for config project. - -Generated by 'django-admin startproject' using Django 4.2.8. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.2/ref/settings/ -""" - -from pathlib import Path - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-6h8r1j#p%g5^x%7970n6fe&9)5o9e4p-i#_okjib7=2--#a8b=" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", -] - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -] - -ROOT_URLCONF = "config.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "config.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/4.2/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } -} - - -# Password validation -# https://docs.djangoproject.com/en/4.2/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", - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/4.2/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.2/howto/static-files/ - -STATIC_URL = "static/" - -# Default primary key field type -# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/django/config/settings/__init__.py b/django/config/settings/__init__.py new file mode 100644 index 00000000..8c404068 --- /dev/null +++ b/django/config/settings/__init__.py @@ -0,0 +1,17 @@ +""" +Django settings package. +Automatically loads the correct settings based on DJANGO_SETTINGS_MODULE environment variable. +""" + +import os + +# Determine which settings to use +settings_module = os.getenv('DJANGO_SETTINGS_MODULE', 'config.settings.local') + +if settings_module == 'config.settings.production': + from .production import * +elif settings_module == 'config.settings.local': + from .local import * +else: + # Default to local for development + from .local import * diff --git a/django/config/settings/__pycache__/__init__.cpython-313.pyc b/django/config/settings/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f923913e04b5b9828f9878f4f3b89a9f8787dd3a GIT binary patch literal 613 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 literal 0 HcmV?d00001 diff --git a/django/config/settings/__pycache__/local.cpython-313.pyc b/django/config/settings/__pycache__/local.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a2faef810359a29d842be484a4fb1aa2ce6531f GIT binary patch literal 1075 zcmZ`%&rcIU6rOFj?QWrTQAETBHUU9Q+*M2jf5c?lu9dJ*(kUS%CYx<{D631S%x;Nt z!W*2#gAq@9HD3G^JaXK6B6Bh^G4WP|e}J>arXF-QJM-Q*-@N(WdvlsfB>@}KcG~$E z1K?ZebVvM;ar&JZuK@%EKn_M0xw(i89!E-vF0r@5DCA-6PK5AmzpsaPEf|LhD5#MR zCk{o%N_Y2qD@?+^ka2?}0_-OuOp)Y&?i=`XUmxU2Kl@WK`gj1QStb~qD-*@UYPu{>pE^iXYb=RDyj@%&l399-^ zvb>2r)T3Dh$xRR0vfU)?ob2MN<^CN}N&euNx*3jg*Jv!SXIBel<|J&iVKrT!rZmGS zuIYL6Q4y9Q6*~bovFGoKH)rRR8|=GDLglnSua&i|26c0_Wb6)k+pgoIyQ-?%73Ivi z^>A0QJ2@#;Opvl=Ij&N%s!vhf_83(6u!|HugtKQT9m(m2URpQH8eB9rV@+F!rmhwA z66FcTK8?9yacIKDwHhKGji1G*ywzxU%QI=3Fijg*O_TB!%R@AZ>O0KD*d=@@ns*(~ zC*cF55xYXgZVr%2IW6}{FRrlrmbI**=V?k`(iRLetK}B;LBNaLR%JT2xm(re5_e7ZbJS_?yj&T6J+9 zF|5;MpRj^lN6#9JfQWjQrOHaS;ndXbqfk4|Q5w&waDNNiO&2{NV=R8yVDFes?I_1_ zKO<3&|CI#XP&*F9{+GgmaCq_kUtNu Q-2$UWb6>y&$*^R81GD%gSpWb4 literal 0 HcmV?d00001 diff --git a/django/config/settings/base.py b/django/config/settings/base.py new file mode 100644 index 00000000..a315d74f --- /dev/null +++ b/django/config/settings/base.py @@ -0,0 +1,322 @@ +""" +Django base settings for ThrillWiki project. +These settings are common across all environments. +""" + +from pathlib import Path +import environ + +# Build paths +BASE_DIR = Path(__file__).resolve().parent.parent.parent + +# Initialize environment variables +env = environ.Env( + DEBUG=(bool, False), + ALLOWED_HOSTS=(list, []), +) + +# Read .env file if it exists +environ.Env.read_env(BASE_DIR / '.env') + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = env('SECRET_KEY', default='django-insecure-change-this-in-production') + +# Application definition +INSTALLED_APPS = [ + # Django apps + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + # Third-party apps + 'rest_framework', + 'rest_framework_simplejwt', + 'ninja', + 'django_filters', + 'corsheaders', + 'guardian', + 'django_otp', + 'django_otp.plugins.otp_totp', + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'allauth.socialaccount.providers.google', + 'allauth.socialaccount.providers.discord', + 'django_celery_beat', + 'django_celery_results', + 'django_extensions', + 'channels', + 'storages', + 'defender', + + # Local apps + 'apps.core', + 'apps.users', + 'apps.entities', + 'apps.moderation', + 'apps.versioning', + 'apps.media', + 'apps.notifications', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', + 'allauth.account.middleware.AccountMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'defender.middleware.FailedLoginMiddleware', +] + +ROOT_URLCONF = 'config.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'config.wsgi.application' +ASGI_APPLICATION = 'config.asgi.application' + +# Database +DATABASES = { + 'default': env.db('DATABASE_URL', default='postgresql://localhost/thrillwiki') +} + +# Password validation +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', + }, +] + +# Internationalization +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_TZ = True + +# Static files +STATIC_URL = 'static/' +STATIC_ROOT = BASE_DIR / 'staticfiles' +STATICFILES_DIRS = [BASE_DIR / 'static'] + +# Media files +MEDIA_URL = 'media/' +MEDIA_ROOT = BASE_DIR / 'media' + +# Default primary key field type +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Custom User Model +AUTH_USER_MODEL = 'users.User' + +# Authentication Backends +AUTHENTICATION_BACKENDS = [ + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', + 'guardian.backends.ObjectPermissionBackend', +] + +# Django REST Framework +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticatedOrReadOnly', + ], + 'DEFAULT_FILTER_BACKENDS': [ + 'django_filters.rest_framework.DjangoFilterBackend', + ], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 50, +} + +# JWT Settings +from datetime import timedelta + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'AUTH_HEADER_TYPES': ('Bearer',), +} + +# CORS Settings +CORS_ALLOWED_ORIGINS = env.list( + 'CORS_ALLOWED_ORIGINS', + default=['http://localhost:5173', 'http://localhost:3000'] +) +CORS_ALLOW_CREDENTIALS = True + +# Redis Configuration +REDIS_URL = env('REDIS_URL', default='redis://localhost:6379/0') + +# Caching +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': REDIS_URL, + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + 'PARSER_CLASS': 'redis.connection.HiredisParser', + }, + 'KEY_PREFIX': 'thrillwiki', + 'TIMEOUT': 300, + } +} + +# Session Configuration +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' +SESSION_CACHE_ALIAS = 'default' +SESSION_COOKIE_AGE = 86400 * 30 # 30 days + +# Celery Configuration +CELERY_BROKER_URL = env('CELERY_BROKER_URL', default=REDIS_URL) +CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND', default='redis://localhost:6379/1') +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = TIME_ZONE +CELERY_TASK_TRACK_STARTED = True +CELERY_TASK_TIME_LIMIT = 30 * 60 # 30 minutes + +# Django Channels +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': { + 'hosts': [REDIS_URL], + }, + }, +} + +# Django Cacheops +CACHEOPS_REDIS = REDIS_URL +CACHEOPS_DEFAULTS = { + 'timeout': 60*15 # 15 minutes +} +CACHEOPS = { + 'entities.park': {'ops': 'all', 'timeout': 60*15}, + 'entities.ride': {'ops': 'all', 'timeout': 60*15}, + 'entities.company': {'ops': 'all', 'timeout': 60*15}, + 'core.*': {'ops': 'all', 'timeout': 60*60}, # 1 hour for reference data + '*.*': {'timeout': 60*60}, +} + +# Django Allauth +ACCOUNT_AUTHENTICATION_METHOD = 'email' +ACCOUNT_EMAIL_REQUIRED = True +ACCOUNT_USERNAME_REQUIRED = False +ACCOUNT_EMAIL_VERIFICATION = 'optional' +SITE_ID = 1 + +# CloudFlare Images +CLOUDFLARE_ACCOUNT_ID = env('CLOUDFLARE_ACCOUNT_ID', default='') +CLOUDFLARE_IMAGE_TOKEN = env('CLOUDFLARE_IMAGE_TOKEN', default='') +CLOUDFLARE_IMAGE_HASH = env('CLOUDFLARE_IMAGE_HASH', default='') + +# Novu +NOVU_API_KEY = env('NOVU_API_KEY', default='') +NOVU_API_URL = env('NOVU_API_URL', default='https://api.novu.co') + +# Sentry +SENTRY_DSN = env('SENTRY_DSN', default='') +if SENTRY_DSN: + import sentry_sdk + from sentry_sdk.integrations.django import DjangoIntegration + from sentry_sdk.integrations.celery import CeleryIntegration + + sentry_sdk.init( + dsn=SENTRY_DSN, + integrations=[ + DjangoIntegration(), + CeleryIntegration(), + ], + traces_sample_rate=0.1, + send_default_pii=False, + ) + +# Logging Configuration +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', + 'style': '{', + }, + 'simple': { + 'format': '{levelname} {message}', + 'style': '{', + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + }, + 'file': { + 'class': 'logging.FileHandler', + 'filename': BASE_DIR / 'logs' / 'django.log', + 'formatter': 'verbose', + }, + }, + 'root': { + 'handlers': ['console'], + 'level': 'INFO', + }, + 'loggers': { + 'django': { + 'handlers': ['console'], + 'level': 'INFO', + 'propagate': False, + }, + 'apps': { + 'handlers': ['console', 'file'], + 'level': 'INFO', + 'propagate': False, + }, + }, +} + +# Rate Limiting +RATELIMIT_ENABLE = True +RATELIMIT_USE_CACHE = 'default' + +# Django Defender +DEFENDER_LOGIN_FAILURE_LIMIT = 5 +DEFENDER_COOLOFF_TIME = 300 # 5 minutes +DEFENDER_LOCKOUT_TEMPLATE = 'defender/lockout.html' diff --git a/django/config/settings/local.py b/django/config/settings/local.py new file mode 100644 index 00000000..8bfd5776 --- /dev/null +++ b/django/config/settings/local.py @@ -0,0 +1,51 @@ +""" +Django development settings for ThrillWiki project. +These settings are used during local development. +""" + +from .base import * + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = env.bool('DEBUG', default=True) + +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['localhost', '127.0.0.1']) + +# Development-specific apps +# INSTALLED_APPS += [ +# 'silk', # Profiling (optional, install django-silk if needed) +# ] + +# MIDDLEWARE += [ +# 'silk.middleware.SilkyMiddleware', +# ] + +# Database - Use SQLite for quick local development if PostgreSQL not available +DATABASES = { + 'default': env.db( + 'DATABASE_URL', + default='sqlite:///db.sqlite3' + ) +} + +# Disable caching in development +CACHEOPS_ENABLED = False + +# Email backend for development (console) +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# Django Debug Toolbar (optional, install if needed) +# INSTALLED_APPS += ['debug_toolbar'] +# MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] +# INTERNAL_IPS = ['127.0.0.1'] + +# Celery - Use eager mode in development +CELERY_TASK_ALWAYS_EAGER = env.bool('CELERY_TASK_ALWAYS_EAGER', default=True) +CELERY_TASK_EAGER_PROPAGATES = True + +# CORS - Allow all origins in development +CORS_ALLOW_ALL_ORIGINS = True + +# Logging - More verbose in development +LOGGING['root']['level'] = 'DEBUG' +LOGGING['loggers']['django']['level'] = 'DEBUG' +LOGGING['loggers']['apps']['level'] = 'DEBUG' diff --git a/django/config/settings/production.py b/django/config/settings/production.py new file mode 100644 index 00000000..f42e6677 --- /dev/null +++ b/django/config/settings/production.py @@ -0,0 +1,67 @@ +""" +Django production settings for ThrillWiki project. +These settings are used in production environments. +""" + +from .base import * + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') + +# Security Settings +SECURE_SSL_REDIRECT = True +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_HSTS_SECONDS = 31536000 # 1 year +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True +SECURE_CONTENT_TYPE_NOSNIFF = True +SECURE_BROWSER_XSS_FILTER = True +X_FRAME_OPTIONS = 'DENY' + +# Static files (WhiteNoise) +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware') + +# Email Configuration (configure for production email backend) +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = env('EMAIL_HOST', default='smtp.gmail.com') +EMAIL_PORT = env.int('EMAIL_PORT', default=587) +EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True) +EMAIL_HOST_USER = env('EMAIL_HOST_USER', default='') +EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD', default='') +DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='noreply@thrillwiki.com') + +# Database - Require DATABASE_URL in production +if not env('DATABASE_URL', default=None): + raise ImproperlyConfigured('DATABASE_URL environment variable is required in production') + +# Connection pooling +DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default=600) + +# Redis - Require REDIS_URL in production +if not env('REDIS_URL', default=None): + raise ImproperlyConfigured('REDIS_URL environment variable is required in production') + +# Celery - Run tasks asynchronously in production +CELERY_TASK_ALWAYS_EAGER = False + +# Logging - Send errors to file and Sentry +LOGGING['handlers']['file']['filename'] = '/var/log/thrillwiki/django.log' +LOGGING['root']['level'] = 'WARNING' +LOGGING['loggers']['django']['level'] = 'WARNING' +LOGGING['loggers']['apps']['level'] = 'INFO' + +# Admin URL (obfuscate in production) +ADMIN_URL = env('ADMIN_URL', default='admin/') + +# Performance +CACHEOPS_ENABLED = True + +# CORS - Strict in production +CORS_ALLOW_ALL_ORIGINS = False +if not CORS_ALLOWED_ORIGINS: + raise ImproperlyConfigured('CORS_ALLOWED_ORIGINS must be set in production') diff --git a/django/db.sqlite3 b/django/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..577fd2a09d2455887fb79ae2e9b9926ae4a6a448 GIT binary patch literal 507904 zcmeIb3wRq@cHfC2L4q#;dP6U^N|4wTnxcsJqt$9Pn?g%$@gY%UcdL7PstZ7oEC~c4 zpr96eJhq|kp2;Lmvg7gEiL(AcA_DsB=WBc=ZC&}7Lb`yVI z+cQZfer(46vgh6^psIiZN%gdvBhFu6_Z4~XIp?19yXW4zx2gc$x;dw6inv;?ZpfM# zbM12pg6joQbh%s`^#6C!|HdC-AN}BN{ziWbmY=)*xZ%3+7yqP>TKoO~n#t7s-}L{H z|3CVF-~aFR3Ra0fAOHd&00JNY0w4eaAOHd&00JNY0^c%$T;nR)xY0;cd1al7sW~bF zQ&b#2#?Iz~|C?00B>gY;BmRH@2!H?xfB*=900@8p2!H?xfB*>W9ReRudi}rV^7-EC z@&B~{-}&D1|9$`G{NL@5`Cs+@j^F3|72i+#>b}qUZtR_i5eW!@00@8p2!H?xfB*=9 z00@A<;~{Xcw|7F2>)N_hQK}nit)`Ys+R3xMmj`O)yeb#ve7;;SX_Ee%R=%s0o*L_& zacnhYj|Y1%I=5FUM@D;N17)ouY0aXb+*k9;*pc3w1BE+sX{{{fm7-GJl&VUtUesz* ze!W~Ra3_@NnUlTqUBooGc2`#kdV8<83FQ=7V*#q=f|_TNWB!i*xJR=8 zRaqmpwHX@6gKc9O=;@6$CG+HfLcq1niZbeQ4Uy>R$X8tcGyXpR*Zlv?|1bRiqc`O3 z@gDL%>-|OFd%lnP(!PV1!Yw! z6_hF+*XV#IYnrlA(E?-c)S1?K%XZr22RHo~dE{w#YRp;6dL%u3)}1=-EM^`_4?XRU zOtcBH^Jbo6x~mjg$IY|u2>EO~1~ai*xhPkgLI=7CS&z51{b$^fcITNTtI)o2cVyC` zHc)%$X>*`dFi^Y*i{Z`3`0MzGyAu-r88o^X$!a%z<;m622K@o~pC{pQ2)sQbi} zV4S%5ftwD=t1A0MJdOO&QTK^4TdQ##9}KuhPYcca4D5!3ey?HRw0krmtkvaeL6u7^ z8OymlN}jPXZh&}#?$K0Bg6BlLNdF=C=n0`lNyQHJ#@~G>-D4~>`3asK;A@tj-9Go> z0Ke734fY;%A3n{0HT&(^PqSf=-)Nxw40IQuy2;M}THlb%|8+X~|5N`L{eRv6zJJ62 zs{g7#?mz9{=ld_df8+Zl-xqv8>ihHbEyE3=jW@;olhk((vCI{_OBO!^-gDaB_HbxPR#Phkj+~ zi$gy&^j$+AA9`Wv?2vcx8-rgR{JFs&AN)&$wZYd1UmP4C92xk=z*h&pH1MMX?+ug( zULCkR5E?k(`4i7?d49q34?RER`EJjeXVDY)9P_yPzt;cN{(sW{h5o8Y_h0+|L*H-p{c_)z`hL9cbA6xeQ~Pf9J==Gh0>vK?009u_AaKMjObVm-Rpo8T zvVqW!x`l`^TA-`ArdS(ldP9Dl?kU?Es)yaegfL1M39IIi(ytLSEQH+Z@Uf-i>7o_!{s1h|!6q;Z?oC=)%(Q3WsupbzrGo z;oTnAp;#VgEFW_VG2w8NjHTt4-qOTlX<6c~9_EZJ)i-&^hjj?oS-+^) z8|KaW1wJZ=m~pdmp7T7YdrxU(*|5%?bPE~bU^4)F<8{5U8H2s?Wgg%`9)`VTma{#; zyR&V4uW>&P7@gUguIf!jSGJ~?c&G38(aM}uiw|v%?mvKeGa``)>|Apd7cmbzV<#Yop1{&!Oy#B-@89I>J}~vexsZA z){A#n^i!<&4(%eF^mX+G*cqsO+!l-_7`*w&cjf%{m0Z7m5NxR-Zl zIa9>>5PFTSEDbTe!RW@)5aqMP%et`CMtIhbSj^L*B+UC9u^Ff8DZScenyN4Gg?hv^ zOm&moi(#u-{XBD?(;K#%)|)2uCcAmP=^36$!3kd1eieGC* z26z|d@lE6%4;USo^{4cD!@ODl6d&CIX56eCJ>?cI3m$IT=>&N4v|E@FJVqx@5+`&C zqdO;w;~cJsb?nr9Omqv=LO*wbUn|;=m`8(dAuIG7J~)a5bP>Y|N0B2u(thTJWBXx# z!S3U37{}N{oLHaHu~B(YuQa+cDi81rb{{it)a>U&>E`Avhu?jCF1QVomIlAxV3@Tu z`1s(vnQ2R{cb{80E%chuM!5UER!GBol@-bm--`9NAPjQjJ=_c}*T%7bfM-yTVau%d z==Fv{v!3n&(8i{RSv4#B4!8%$1#Y={hIb!w57ONqeT&+x?B$h)&1PlKKKI}lp9jqv zFYSXzN51Ux|AGIj{;&9d!v9zNb^k5@MSsxm_WeI}^8dTOcYSa8QoaM;Kk)v7_w(L& zyvyE<_nh~jR~Y$EBfm|m_yYnU00JNY0w4eaAOHd&00JK^fkUHiSKtgg2wR{3?>|Yk zqpezY+nk;hy8?$>Rr`*+U57`l&;Jh|qnb0U#&X}8&XKOeCs?I%=lEE_?HV0-eExs- zh})GqV}H!9Kl88gBeMSU*kO_ybCj|^|37nx#7;YknYU2K4!T{D3A>QP^ZzpkNNl`Q z%y#4Q%zkGfI)b)dZ?6USkx-lS?a%+8_LIzHXK(E{`^J1E=j6Qo`Tt3;+ckbhzpX|$ z*V(5AEce)sk5KC=hgP~#_S7)7jXSmJW=;;dT_>gtn;kcz0)x~#W^JVdIlB{eWPmc@ zbm#Mb(L?o#t)Bm%?k9;cx;dZ;mC^H?O|9g8WLj&yN0o>q$ z9{Tk(`_;U6uwP)a!S?*0o&N>@+b;SKe?R~PKmY_l00ck)1V8`;KmY_l00cf<0*!v* zh~ygZdc6aK)AQr>7yHjO;5z-vl{EGlF!Bx&&FaGBI!gV8jB?22|1#qlIc_`9#+!1q+Cd(!kKs? zR*0w4@##{zw7F5P*H-9e8G9jK@c*fc{=**-009sH0T2KI5C8!X009sH0T2LzJxHLp z*E`6b|GW0>!Qc=Q2!H?xfB*=900@8p2!H?xfB*=9K#KtX{y*G`akV&_&-745m5H4{=EN9 z`li5}{_Fm%|FSU_xf5G>@?|XckzM5~tx8{@S`vz8gi@sUkjPDYC z^FYKm;T!Xv@*VRX^m%s7px_qO*I1&Ti)00JNY0w4eaAOHd&00JQJ zVG!u+6@*cZmo;8id3leOCo8-x^RmRt4OX5g^71Y(@9k!AftOmoZ*Oc^P5lNSK#Xyu856NmdS@=j8-1pW$VQ zl|$!vInK+cc{#?)!Lz(P!^t?j01|tmtJ$ zPjBxap8tOs^By)p00ck)1V8`;KmY_l00ck)1VG^1oB;m^@$mltxA`c+JrDo^5C8!X z009sH0T2KI5C8!X_|^#E{Qs@-gEk0&00@8p2!H?xfB*=900@8p2s{Y{aQ=T1MhQNG z00@8p2!H?xfB*=900@8p2!O!1Mxc+>1^+jhk?+#~`qu108w5ZA1V8`;KmY_l00ck) z1V8`;K;YYrz}q+Q{Qui+OyCa)fB*=900@8p2!H?xfB*=900?~e1lajs@c%0p{f9pw z00JNY0w4eaAOHd&00JNY0w4eaPauIaz4L>SL_8r!lvFaEO2xxUI+v6SiBvcfPs9rG zR64Go|G)0?fBgvz5H5oN2!H?xfB*=900@8p2!H?xfB*IY7Z#Vrg_XHE@y62Z{Pfao@p|^QIK8sGIJ-bb=CcdS=XC=T z)5@>Q)pN0E$W&X(zLZ_cF3eGkKtT8ld2^6>i^pMDSWp$YR(rc#CBFCNDn}fNrcK7gswmeqsaRf9OF^+9 zYl^0BD16wcO|40_dPS+SE`nmNTrRdo#~8YjyrEcZC&H#}ab;ol=1P_XSJf)@v4v>U zjjctPH=4Y<+UW_k$azh@Pp-5($wHFu&LpGO%|hL?pacnWRwkAEt+9Q6LF_7 z%RHYd)$)C{z<4!@TcMCKU948mnG(@N!Zb)C8>_OUl-M*R=h`EZ@5`E8mFiWiM_~(i zGLTctK~Ym4Xx8z{$t6yq-5}LVRYk6qDFd8!Bw4eswHl+EFVjL=(j;xOq7XhQ zx*AUTwLgoW|6ja8w@pW?e_#WCxnOo9l4n=7btf( z6e%2$bFoxHmg*(-UYlEehST~QA@0z3XVW{5gz~XzAsBi%w$Brom=N9*%!DxEx4G+g zNDWi?1La3M=FaT0&X2+!xwKZc4d%{fi#6ufiWF&EF6_B#Ta7}EXZ@bQT1IHd=IHC! zR}(-@GJj^7nvUla`Ak&d6gz}{4hh>K)WxD@tUFW~5St&Ax)VN{VdT7JhIQ!aTL9*F zE)osrbE_F=zr8#1TQ_epww=pO1@iWecOcN{%?J*nW6c&FyskjGQxW)bN+QW;XDw74zsaC^#e^x-HPKT);b$a zw{|=U?a^;N*|~XBVP}1W8rS+gfpSb}e1a#B!-^rvm5Nl9b4pR#phdbUrINWqtPsob zlyOjSJLZdn;MSJ)EOAg{k+pmZQjT!9Xd=w^c>=MR@G!%3!$H%bs^6X!4iX@9x^w{hMJK8@em+l+bEiNcD%7nD2I_}3qc>e#$2X`od00@8p2!H?x zfB*=900@8p2!Oy7N5DJuORhu0XI&%TJMgPRpBwsk|NOuY4!qI#yL}J)PPzYX@1OM6 zd%o^JGW?G3_VA$hmxa%cOuBw{_}&v2=9A;_!#Acpfyq)F591M+MZq7LNc_BXtsG(A=Ky{_XK9ggvJ=ZXUm_08ZTX#_ur$du|y;#%huL2 z236z1nYF1?QNRCcm0|9+?sbP6uBSbLtK`M$PA`}ido;3|2#0fulCW9X!ijc0XCDkH zy5l=F<_YAG>%yuwm$8a^0C#tlFQ_H_o5RjM{$b_Y?Q1r!H|5$6G%~~LT9JR)zm6|nW~;8 zr?%*>Dd^ZyFr)-Mfs52dUuPGJ5)R7+qW6Hu^wbuen4(>}$(^RBKW&ld^DdrCCsw1a zUPhgIX_sv4gf|C6?+riY35<^mpI+f>7js&o*E3etqQc*4YW;HD=)>|-Q>%>)!j{Vn z-ThZroo{DT!tD6<5~)c&G9{url;Eq@?8>9dXD$-tZBm z9_z=v97gSL5zzxBdIP1<_V$nY>QuA+!+g7krp)6@f!OF)Zw~Q(Ie26I`On8U{ohdExM*c}Lb9Rl3x6uG#h-E_+ed zR4{_rc5^#)-V<1)9FDXy%1BAu!XYV2@Uf)16Lsj&~P}ro){ojIPO=4Nj=Bc8*>Q66nRCQ!SL8 zimkn^OW1p;{fzZXUkF@OLFfB*=900@8p2!H?x zfB*=9z}5tC{@D~wsQGfslfB*=9 z00@8p2!H?xfB*=900`I#^!5%6;{0!Cg5N*@1V8`;KmY_l00ck)1V8`;Kwz&C!1;f# zjVR&)0T2KI5C8!X009sH0T2KI5CDNr0=>O1{`}vyud@v`AOHd&00JNY0w4eaAOHd& z00JNY0(*x5zW;ykj3pug0T2KI5C8!X009sH0T2KI5CDO01aSWEMh5jD00JNY0w4ea zAOHd&00JNY0wAz=2;ls`cg7NtfB*=900@8p2!H?xfB*=900@9UHv%~ScO!#(5C8!X z009sH0T2KI5C8!X009u#I|Ok4-#cT8NI(DtKmY_l00ck)1V8`;KmY_lpc?_4|GSYv zJqUmR2!H?xfB*=900@8p2!H?x>>UC)|L>i#L?j>p0w4eaAOHd&00JNY0w4eaAkd8f z&i~!WpdJK300ck)1V8`;KmY_l00ck)1ojRAod5UESRxV-009sH0T2KI5C8!X009sH z0TAd$z{?u;b!$aE2!H?xfB*=900@8p2!H?xfB*=9z+NDL=l^?Q6cGamfB*=900@8p z2!H?xfB*=900?YN0O$X$`Jfd9KmY_l00ck)1V8`;KmY_l00i~|0i6H$!YCpJ5C8!X z009sH0T2KI5C8!X009u#nt-=|*mX$w4OefhM|S_FKjr%o?>D_`!|KrgIy5nOe&GKb z_=SPbdBXjF-218ipYJ~?{DzQq{o}1IqIK5^GzRB9f%!3^F{zdc%7a=xS5WV(HMLx- zN%?ZUq*XU1wIHpg3#+TCNZi`~{LE5zdO0i3E?mjJCI+odPO3q1altMX6wd|CK7ygf zV$>6;T^1Uf+!r~|8zp5!R*U&^X;rOm$QpU3*GO`qP*rL*=3XL~T21E@>8>gl?7nuD z>uy?izpEmPg*$3-D;T;N?hh!J1((kDUQyK)Nv>;U{=3xGm=x*SbpGMOtS4~#wD8`V zCg+Muv7DF5tg6(^pDr4~E>FKWmlcC%0|#DJWKAgq#RC1MsT+#8u(&KPtjx`w7lRw+ zg1V}9YgI`rzb;qL#iF5>mUv@nc7A&4ws<{zn=F*%4J9bvm#bVbnn;+E)LNwR)9MA1 zC@6V#LoPNuE|yDc&MlU-qLFrTU@7}jb}73slfBiMUZfEcuP}in4r6v(){%^xBN?J)Yk)0Xjq}$% z0hv}0Y0K3kUoI$8A)(~tNM=>4m(+WfQLSAvNUki*-dx#sIq9^u?ZQIQFs)!H_HuvV z>gjIN(VQnz#QgcW)V2P=^|5YB)`Grw5;(C>xXsv>XNjW?JP}0k(c@d*;C!dre#fvwSG)T zvO80Wwp3D)D?3w(v{aIf@0#%hPMs3oy~@YF)zJDOZXfkl{q7FAsL#RXglUbub-lNV z#LN{UW+q3dvF}AsU~y7tMEJT;xFeU=%5-JiP)ky=ye1hdmXuE<Oil`)4)Nq^_n`gvgguqon~kaTXjh?f?v9ez_(eri9%#+Wv{7AEDphmf zqhb4Wm-Bp+wpx_ef?|y>@=2xDD>rUrmy8W38BNS>JdDat1k&E-(MYhSPD^hberSI#VTzbZE z$-et=RNvYYeZv$IU%f`VQOiEA@lwVU*dWK=-kzO)g2<#(`E)9p<~t=PpF@s5J4v=y zN(akZhto`bngvE=c>cfZr*$|00w4eaAOHd&00JNY0w4eaAOHf7odC}Nj~y{O009sH z0T2KI5C8!X009sH0T2LzT_=El|8Lia01kiv2!H?xfB*=900@8p2!H?xfWTuXfam{@ z9Wgop0T2KI5C8!X009sH0T2KI5CDN)CxGYwyFLVP00ck)1V8`;KmY_l00ck)1V8`; z9yWIsu&jcYO%p00@8p2!H?x zfB*=900@8p2!H?xJaz*3{QqM|j1E8m1V8`;KmY_l00ck)1V8`;Kw#Gi;Q9Zq4*?ti z0T2KI5C8!X009sH0T2KI5CDP4P5{sUA3I`n00JNY0w4eaAOHd&00JNY0w4eayG{Vl z|95=|-~b4K00@8p2!H?xfB*=900@8p2t0NIIR8I(#OMG7KmY_l00ck)1V8`;KmY_l z00efO0M7rrJ_K+81V8`;KmY_l00ck)1V8`;KmY_DI{}>kA3I`n00JNY0w4eaAOHd& z00JNY0w4eayG{V-|6LyfH~<1500JNY0w4eaAOHd&00JNY0*{>lzW@KRBSr@x00JNY z0w4eaAOHd&00JNY0wA#K1aSV}^&x-*AOHd&00JNY0w4eaAOHd&00JQJ*a_hL|JV_u z0}ucK5C8!X009sH0T2KI5C8!X*mVNz{O=WVuAZOoiTgh7{eh8R9=SU#56$!y`ug0H zy-U>e8Rq1tpW1a-pHK%Hi|d|1`MS`!rQn9N;yT=q>55i%LO&B z$+f#utz49=l3I}DbSkf;qWNtUW@nbN)5}?LcHv6)H8Hr2bf@{CxVW&bT2MR}Y&sSU zHHH^Ffvcy5#zh`cv7DESs-`M6saDSw)cdNGOeG2lnWA`LHojnY)NF9j(w#Czc>lWX znp!TA;ox~OSdups^2R&w3EU)a5?#D8G^9dWPRHd$*fe{2JD1v3ci>m3@!FavP^aRK%* zYRUtxrAoDT-*&FQS6AvvyD{>TRFntgL}{%{Yqeb0)Y6)CSJ~{QUQw$Gxl)idMN>Bv z9zC;L;zJP>bLDc;>SIx^X;QUbl4Pw@ODk(MQ(0@iTrZJ8lWVlzpfPOMdErcNC^@WuQ0VGo>a37v&*y7b91+i%Ip<##<~zZQin~&NA9t8!FiM%p{ zbaA??6xao>WpqcQs_Q*++{yzsKct$HFP94B&(?voOY?9-4^O;45NMp37ard3c3H3u zilp3EN}5!ZHC3w@6scG)t?57I^57zjQwsu2_gl#sc6-h1>Xp2FMC*Ll`GSseLADBKrqnJ~P zYwNA+nnt=5UxhNJX+9E;+U-|lZJl=Tt7TIr+U9~mr{f}JZX}B3HMJzsA%k63Y#W{s zzW?tDy{!PZK>!3m00ck)1V8`;KmY_l00cnb5eVS?{|K-k3IZSi0w4eaAOHd&00JNY z0w4eaPbdL={{IObIJgZ0AOHd&00JNY0w4eaAOHd&00NId0MGv)0Tx6-00ck)1V8`; zKmY_l00ck)1VG>kC4lq)6FP8k8w5ZA1V8`;KmY_l00ck)1V8`;9)SSf|9=En5Cs7c z009sH0T2KI5C8!X009sHfhUvz&i_y7z`<=0009sH0T2KI5C8!X009sH0T6fu0(k%b z5nw?S1V8`;KmY_l00ck)1V8`;KmY`uPy#srKcNE$w?O~|KmY_l00ck)1V8`;KmY_l z;1LMm{r^XR1yK+H0T2KI5C8!X009sH0T2KI5O_if;Q9X(I&g3s1V8`;KmY_l00ck) z1V8`;KmY_DfdG5{zfbsCSI<5FIq%;bIX(O{L!TKs>oZH)>E*0AyKp7@ni$;LT2NeEu*o>v42tK1hASl{7;3ztdIC3Z z3J(LCoGU7pE=WD6$eNTdmh*R&LM^|p6zV&XSTMX^o_=vI+wPH*N#>GT2#RV+Q`VHK zxUje^F09PWi8q#J=cku$i`TQa#p#vh#aU*0KD)4dUJM#;Nt(K$1Vt7R`>CZHYD5|- zeJEaa9*TTb$`z9NNMT1q@!B>+VYBwALy@l%Xr(}-alVlxr{{&nqO;SKKdXt1EN|ho zCfDxj>GSF~KHH3K<+EwBJ$t4a3EkfnXMYLHYB(+@!rdX8#$VaSUz@Q<^w&f@-iUhw zR|)ae4wv69SMMrSsgQ^l3hAAAywcs{4qH2SSXZTN7;nV7xmwd?tzMHdxnwjP*@>&m z-CgalwR2Z>Ri3!fZi&kVSSra@@&(53YvBsC)af!e&_;yF;e zS5#?nmg`!X|1Q~=!RE?rW~vm~PU6;kD~+%xFh4K+xlxCewbSHw2A2#bj;m^?xev6a zazJb)zAsnv>vHv6G?56kmQ-ZAGGK zM=7P0FBjC(nkku#+XVICf}*B8(D-$Mx-hR`ol8G8YgIX~ZO{xR#FOPhRWhyyGM4RLw}G*Uyz%h68%I2W#YN%0yIYKlMZTxTe_Mh( znnBx4{l~rdgx>8b@`>?Y*C@Yu1G;ZeO<7 z%k`?6;hCgu5iZD^Qh8N+TTy6DZ7zLn3$-D$LD8?9LXoI_Ip@vH)}|s``fIbrHuLY4 z>4ai-7Bd|RHCFC;0=4TxW3zn|*F3f~7gN5LHV+GGL5ip3WM(xTwa*I<*{ZT=3`ytF zYo{GJ?MphkVFPJ87YyO~|3`CBM=u}%0w4eaAOHd&00JNY0w4eaAh4SR@cIAUq>sKq z00ck)1V8`;KmY_l00ck)1VG@UA%N%q9}Q>p0s7#ED009sH0T2KI5C8!X009sH0TB3T2;ltx(QrmDAOHd&00JNY0w4ea zAOHd&00JPen*?zF-%a}H8w5ZA1V8`;KmY_l00ck)1V8`;J{kh}_y0Z`&gcaMKmY_l z00ck)1V8`;KmY_l00efE0G|KvCVlh`0w4eaAOHd&00JNY0w4eaAOHd%4FR10KN`;H z1q46<1V8`;KmY_l00ck)1V8`;c9Q_!|KCme=oe#s%{{h@b;QiH!g_|t=*8oWC2X9GVupbqT! z{G#WRo>>3y_WyVNH~RbgzSO7oopOKKU2z}p{n_4f?_|$cd%mydvhWSz^TLAQq0TLwOTkF&PZ$3a=oHgM8naW%ku zS9eKTR|^HDw)BV_AhS6gvOu4`o}8jfVbiAYik$0Jck$5QD?Bz--+rLkDc7zu}y5)~0g zW65wj5nGxfW3yd7i%N2#z$2AP@`fVG)wP;*_igrGnnn}HIfozU-wHcArlB%h4 z(b+;glZY%#_PHYM36i4$3x^|VN!_SaS$gEF<&q}nq;;iO(Ub?8v%P3Ile~SN?7i9M zkzp?-$wkW6rnOprT`AOyinKw-*QN5Rw5iC>xg1Z$lBwkhGJVV0bh3+SP2Eu5DVG#S zhvU&?H2&%{Wc9YQ)r6!JR86Xti*mK)t#V%}X*D+g)V$(6HnCVLnqCfdcQ@XgcllyD ze^)7(#!GUp*k!K7BC%Nd+Bve?p5F%FRz60<;Uq0I;Z!u`7AtHaH4vNZ1;cvPw= zRkd7D^K4;|lm`{Hsz@~@UoI7#Lyn~5vE;RBz5g{cS~uDB?>``WTK7> zUMi6!T-U_zmT2}GOJ_Qqh(@Gvx{FV-L@b`UcB;E2Yjm+_R0?OPbQnOwl!~Qad5SE( z>I^->7tKeVpQ&&xnTcH+?e3v@nMj4BELQsC=wUb#izKg|>~5(wCnHI^Hc+yL<8%#e zzv@RXBr}GLuL~;+Kw-rS>a>zD|U7 zi)(e0538Kl)N)B$Ey`DPxDj;n5}55k6&qDjYscCdOJO zwhlO%NoS&05B0fDEKbXLz7L}a=z_5!t3~RDHd8f8Ej*Cw6q>WAb^#f$EZZSxC*lAGTnKez`rzL+?RSH|!PQ>G> zW2Es^B$kfuBV~fzLbf&LSSA*Zhy8qD>NQ%Nw@s`V ztxK6}KFUN&th%;NnM*T=$Ic8$t7^5T>07LA{Eft;@#G6$vP5~MTZ+(d@;xnWiymyU z6@Npm)hJ6HFB&vY<*T&?jLtW>MzsMX9kh*zqcn$k0?wyyzhl*PABj zx?sk|0ny5$mZVa>=(rb7kcnjGg?_Tp9P4m%3i7OJu_;$H$FWXil8I<+rjIN%$C}nN zix+$*Z!<*_nRtvY0&X(WOx|!adCgqgN!HNmfELB6Ua~}!jVJHcD|jNE38ypHdiq>r zS7`+|xijpo-Kt(Mu>s*;IL-4!nlAszlt7l)wZLX+%gLTdWzyjoJO6t}zSrgd3IB>; z^nKO$-}?%_DeoV8zu;Z-o}}~t_kNg%b=U#{5C8!X009sH0T2KI5cp0eusGA_8oxDN zE9dFCiFuo%`GCPzPdCQ3sw~loSMEFs#V$nXu2Lj9`yv^<+F>w3j}=%KTKTS`A57Vu ze@U)X98Ja3;dFX-+R+r9R4WyUJ*iRYULDgJ8EM1`0w4eaAOHd&00JNY0w4eaAn=_`!0TIgd0nr&dXJ854&NLK4n99n z_I$Sg3w^)Y=l4(h-gCX~TlWWDmt65jrS$%@jjNtOEGGQ<6ZBc;qSAbQgTDh(Q0~)v z2~JfrOWEn=thhY=;#^h?I!FY?b3wHb6jl1D!@+|%ysfs7kvJb7!FZyT9~>~>Ekh4OOk2} zRHM~)WUeXNGFYbXJdjIy3UCL`Xr^@2K!HAprdgDmM9D4wVIFGhbeMlmhm8X#8jG4} z#HH*@*`@5lO!k%-WMUK+AF_~mg|#g4p`BfrU7nquo4ajPX0I?d>(#QR5zaMfl|I;4 z=O0R1;{{J(^Q!Q$ZwHaJWxW(t=ySyJ zaAM2Y)*mIdZawTUx;Ax+@AbxIPvGrY;h|uUk58+P6tvNiV#x%32}La33eQn#txJ54 z!j_jMS#mUcXbaC#m1U&OKZ@>p<9SbDV^(O;XVB@z%dH*Nmj~8uJeSL@=91B^Xo z1A)dk1$l2LK{n~MB~I%SCB;@F>C9>>&p!yi^?(+P&{q~_Z?0s;_NCJ?>&&FF<(|~V zft_+`h83i1sm(B}XxS!QXB!s@*BM%%R6DLMFCjLQ7GHDWT&xgXZG+`xD!(-=CzaM^ zW=FlZk#aID{}HXFY+dSA|cl*f&^Cc3WBE)F>N)8%KsMWsPmXqa7a1(QTa| zc5ioO8>+{#2cmIflxbo75cl>WIz683c7kkV8dqqqO`DZ+`;2LRCxH}6W$1gF$SL!Z z;gk!-E^B3-zCp^^%bJ#Un9xl{{xN)_VxRAgi;Wq6P-t|lj;$_bB`GU;oAZuK*WE7O zuhExy8QzFhqf|G!&Sq8OlYmKmY_l z00ck)1V8`;KmY_lVDAvX`G4<>B_aU<5C8!X009sH0T2KI5C8!X0D*1_D(#A1Oz|;1V8`;KmY_l00ck)1V8`;K%kQV z&i|cEPy+%W00JNY0w4eaAOHd&00JNY0(*@B-v8fgBZ_!H00ck)1V8`;KmY_l00ck) z1VEsZ0M7rNOi%*?AOHd&00JNY0w4eaAOHd&00MiB0M7q=ZA1|d2!H?xfB*=900@8p z2!H?xfB*<|67UZHf@{S6eZuh5L&c#BgFon7=}+~|yT8x-H-`WDz)yO<;rb%e5qdf` zQ3C=X00JK#fySlA$320y%R)m|O9kaYt(;foqMXl{>m^Onf78l$m6Ft~Qwvfi5m}8! z!b(@+`!h?~>E*0AyKp7@ni%XV=VUY}E-q}L5){t`O;>`U#%kkDPvCnl3lC3h7eJ*V zk@Kdqw3?UWN?6WI^^$t8tCMvjn3aXun=3nsY3qJ=5+An^JTKZJeK+0sm?v=gvhe=* zXmYODoqpFy9vG1q-G;GR2#RV+Q`VHKxUje^F09PWi8q#J=cku$i`TQa#p#vh z#n}b2IG|MQH#=hCZre$vva=3C zjprIUPe7Rw8gF*Z(q>}TYf6vHv6JREAxck2Q{T$}O)P3|`LIZ^B6H`ba^<7DHSC$JI|8kcyaWvwD< z&7z>(SM!uq##9Nf<|656CfaP~g1XFxeN-GII;`l!;vmTww59}IgZYCzm+Rf@jki33 znHk|zqg@wfBWt&q%kS9fZy{-{Ip$imljXUU@2!E0hAks!%z{>9Boi@*l^Tn3O_Pe{ zHMQh0R@Bbt#U08?g{~XsN^W?@tm{J+6nWCbqNa7*GRwqjMu6L|+H|d{$}EH0(@UJW zmYumSo;&yQt;GfDmFcZWI zhxVCKQ#4I2tx;aub~lbm){@mzto5rU!SMY5!#lsg5(t0*2!H?xfB*=900@8p2!H?x ze7h3B`TyH>WZ)MFfB*=900@8p2!H?xfB*=900?||1aSWU@L0kU2!H?xfB*=900@8p z2!H?xfB*=5yAtq{V>evCmTv`mhT_=e$ZF-E%+|@dcD8q{k&K6 zE_t8v_Kf`2$d8P?HgaJ2pAUck@WSxW&{u{&KU5u>89F-ndxJkU`2OH4gQo`m?ZDp} zC=Og281VeM=f^xX&-0$a{{OT8^ZkYX@xHJ1{gb{Q=qvQ4`$pXV!u@yL@3`mP$9sRL z_osS4)4SYzs^@DxKi~7;^nAQ0+|w`oAHx42yd&Hcg063ngWLQG-RN@(6Ny9jRpo6- zlWTYBlbrRUHY1lrRwc43QLmd^B+;qNp@LFW=;Ld4VZG8OzMzZKXN5{@?c#c+ReXG& z#6z(|a-q;>nrf_q&&-ivV)7vOIA1L1?<$2_eqAZli%QFFqyEBmlFdX88ZJ9X8-6$C zU)JRfpB>~4$BmYAvm_szILMrK5N4hm)#tB~WF&EbdtECRvg5cX%^F~1Q&v)U?e1kr2?KdWmP{x_?w8b%Dwre`vwk(l%| z_nUQAaTY?e{+TqF^>Od>)pChG=w_8=^`cRKAw{y8h|lodLE3P=DWBBk4bL6q4abd^ za|x19slrND{1sPkv@f2RP(Y%A2t@bz{MCTMs9hH z5ZY7?_q!@bb!8)-Hf1B0Zpw)WDNoFMS#WKdEU<2BQ(;mI&5T$grJ%NmGd4vElUGM< z5gIC8Rj%f7i*aRsgE)Uxy zF;&{4F_fOsmF$t2N^Q~T4U-{Kn#v4YA~7|r(df0Qb0nUb8RC&B8?su=mrJW^b%Q5b zD;nM+TAMD8lj5bTLqh%x8<>^CwC0@^ugQn0<7+oV%msHM(qWsQhaaTE4SJQEz2eRipXE z6Qn=0=rO|RX2A%ki;d&D4I{R0HjF5pZ9I33Y+Rc4uz#o;iy&4U}sA9 za7n{StEAy#OEM^s-fP~oBmZUOD&Gj`u@LvMBe~d z8D1Km9PSzV&7prd^#0KD&>1p>KOg`CAOHd&00JNY0^f!NCN%o)s>#$S|57UUMRJvD zc~!N1?=b&zIiqr_M#7PcqsDhrwTT;FPi0f6>I%kJRJAD>-%@2&2;C!viNq-Trm8k+ z_GML8(Xk4Njz>M$2A>7o{Io1#`%S>2fp5*?d5+4Qq1 z*K(3ooGg;isqqsn4_jaM#pAe3t!E}qSR7;RR;TXh5*B}$gvFQRDoKovooKqjnwu`1 zU8lD3spH1ijpgN{s#(8oOs_b%Mp6^8X834nVbX@1mf8zB zUEc82(qeJeQhQD&`H9#u=C7r?>9VCN_!dcyO&;Z5=gW25H@F$UKP{0=D00;B)*@wi z+LZgaE@yaYkuwI^s1e^Jxib?-nRgZ;Hq6GaQy(LV$i)EnO3vr$+eKwfQ#LB*m!9jj zqEQ`tgA@{%1BQzoDu$zNO0VlmhQA$3hSzqb$=jqfl?gECJ2Y4bcJYbVNPIGNgh#@^ z;jit>+j*Vw?d?;ql5phW5hIE=aU%ws!YjIh5kZ@R;lEWOv_c9Ki6hMWHfiR&RdjNh zM5i)`^~_i=R||Y_lxi!X^Q=-IGNl^1MdGnbhYjC(n?=EJ-mbEws~Fz5s~GOvR3>hc R%4F&=^S@o51z;2Y{{b#rkTC!N literal 0 HcmV?d00001