From 81ab8ae93e8978d06059de9b0fa3d6a1cc25e026 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 5 Feb 2026 17:29:10 +0000 Subject: [PATCH] Autosave: 20260205-172908 --- config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 6549 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1220 bytes config/settings.py | 108 ++++----- config/urls.py | 31 +-- core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 2515 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 7569 bytes core/__pycache__/translation.cpython-311.pyc | Bin 0 -> 1273 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 1361 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 8481 bytes core/admin.py | 32 ++- core/migrations/0001_initial.py | 118 +++++++++ ...vendor_logo_remove_vendor_slug_and_more.py | 95 ++++++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 7482 bytes ...emove_vendor_slug_and_more.cpython-311.pyc | Bin 0 -> 3647 bytes core/models.py | 101 +++++++- core/templates/base.html | 154 +++++++++++- core/templates/core/cart_detail.html | 81 +++++++ core/templates/core/category_products.html | 46 ++++ core/templates/core/checkout.html | 73 ++++++ core/templates/core/index.html | 229 +++++++----------- core/templates/core/order_success.html | 31 +++ core/templates/core/product_detail.html | 49 ++++ core/templates/core/product_list.html | 52 ++++ core/templates/core/vendor_dashboard.html | 128 ++++++++++ core/templates/core/vendor_register.html | 44 ++++ core/translation.py | 14 ++ core/urls.py | 15 +- core/views.py | 162 +++++++++++-- populate_db.py | 111 +++++++++ requirements.txt | 3 + 30 files changed, 1430 insertions(+), 247 deletions(-) create mode 100644 core/__pycache__/translation.cpython-311.pyc create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_remove_vendor_logo_remove_vendor_slug_and_more.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_remove_vendor_logo_remove_vendor_slug_and_more.cpython-311.pyc create mode 100644 core/templates/core/cart_detail.html create mode 100644 core/templates/core/category_products.html create mode 100644 core/templates/core/checkout.html create mode 100644 core/templates/core/order_success.html create mode 100644 core/templates/core/product_detail.html create mode 100644 core/templates/core/product_list.html create mode 100644 core/templates/core/vendor_dashboard.html create mode 100644 core/templates/core/vendor_register.html create mode 100644 core/translation.py create mode 100644 populate_db.py diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce5584823cd4ebfccfc40c2b36df8beaee7db..cf8b83e431fed2dba3774e09b7fa258642372b66 100644 GIT binary patch delta 3010 zcmai0TTC0-89p-}kH?qs9dij6ha~|6hCm3gK$4wcLtuR&*ipi$8`t3sFw~dr*a;id z3b)MzT}9fklWLJRkx(AeNZFNEyM3tI)pj5H)Q5H)S&B8PDkAl*FMh0AY1MPaF&mqx zJ3jND|NQ6s@8^vC#lUaQT7S#3CV-wl?Egyqmut`3LgwkczP>(4Kz)i%8+e8Qh}Wfz zyh)pB-b4a{6wFTu0G<(h-wpO{ve*KCzUO!=Lg@wT_Yz3&ec|qg2FO4oG(q!23h@@m zLaWBx9+JFGupQXtKU%wY0^mLIMY_92Uw$wOie}X(dLoe9TWKyr@`VG*{QufdalAHbGB=-DcQQue7e?J7DWwoc!zlkKr2j+Zyexkpb`o zd3{zbkQP8EJaHEYov${qNPGBB*v@yM*9$xNlj!Y+o%~zt0Cqhl_#QNS(R>P?x)=BfeE-xzhn{=g6H55lJU>NV*5 z;n0(v+DXv@fWz<%ihJatK_qw|9EE4mY7~xX^qfY=MPi-e`{4vX04MoDR2M&bhu{P< z&cL^;m#JDg0MRZ>J8N=!A~{d z=NlWvPuCFmCZh*lJ_eboL0%yi-h6=uk1o*p28BiVZhe&18@F+!aG^mV1jF?T{6!c! zcGsf9#d_4iJJks5DO{5ON_91H!Uc(h2L3#}!e4^A81OA`ugZG;s(gm-*zpmnWnIYU zg%9$HRC4>yHZ>$iFBJQXAP^K8HVAUay3|ffClz z$fP{QT=AVi%eYEKvKc{TxKJp14G|xs+S4fLR9;GEa;aoq%x2_$<`H3#$BlPbEqq}p zgtHLjJ>%cxO;e1}%ioyp%io&bqQsHOj2trmm-b;_V_$_WcEYEULT1~^t)XO-!yJTS zF2UVQ-b|{7U}ilf<~CHiYF8H7G5fY*I4wXiIjrin`Kw3lJh9_J_E4VsTwW62KT1)| z3+Xqf_&LJoMiD6h#kE}>2XQ6+(--Nj=jp9t`c5f*2Z`?oK3V*DaWC{BbOe)dVO0BU zG!o}#s`C|MHJe!* zs%EFsRhj%J+v%t+cs;(%B~W*(%~kbUE|L-JQ(|T_hjbtvF&r7Oa zbE}$P^DF<;_IS{CJu$b;g@cLcQhXsAiK*6wNGy(;8Vn@3rKOl^3NHi#q2M)cIVgW= z|M@gNT=Q4C`Cv>v8IA^mArw+17UHmWA`qP8u7=`Th!~?UA8xuJv|kp-b_~jCf1s~wO(#xU1S9yQm|FLz}5Z2+56nt zTXgzLP9M@FCN=Vl#X?}Wr))6aAAc}ia-AxRq32LNZs? zHHErf)|(YJtWXh+i6~T5W1RJMBdJBrrR1@j?gk$>#&^jQGwDHzLa)4rj|cA4qKjX2ZI=)j a*bjAf)BvKb>~1ZD51Ymm;H(bqQ2q}nYQq=+ delta 2153 zcmb7FOKclO7@l3PUu%24Ydf~%=CN(sB+kR`D}B*NOyWcdPMjvLpnI{b%-Y%Dc*E|d zg{a7G6{)wX(G&@!BDFmPiC#eEgt%}+oLs1cKlTWDa!NDL2L{hZv5CA;Hvo-G7(S0_6Zm~>x z)v>F5wG*89l2vrR(m?B`3xIYb1i&oIYSXD<1KOZ{hs191Yz@I>VmP3)opEpbdS4kj z-)&wIJdlRwD{km%zf!OW3}m*<&R^r1Tzk!S^5i7g!EruBzmRm<3)w9odS9q^699r2 zasrDd2YrGMPk#YG{{un@;5C5P4%i_CVNeLc5Y})ojIr|pArUZgmnslK8154~VHfN+ zh11g>P8pc<>1`*xcFzCD`F=>Z*Uc2b0ob?a9Qz0GAUuTQ?!Rl72q6Lo;2@R;V6=%t zO&pep0wwH&BSIG(6}oXN|CFhE=YTL^qMSBSfG}ucoZ0Q>?e|9x zylmpAiS-6N+X@Y{7GJW4OssS8e0u`IFud?ulaGtT?W}tv#)nM81S;8j!=vJmLc%wN zD4Y;t(6R)gleuC1(W3{p)H6jjJf3@n zSh})F#W2>n$=SsGbUM3~oLEd|vTr3Xqp$57gc}`pT(+|KemA=1`2IwoWrAEA4N~Pb%fc|nV5fn>y}HI2{i6K z{%FfPO_bf3fpfE}nj)>gj7rkPwSSN5bIe!IW2gWfPyzB4G#u`l-F0*G#^&{FTi0|u zKCDrWo3l4&ucx+Bx}}Y*7U1&$};=shlqYTO|- z=ev*o_I0t|cd}E9iMeDpy^xtn&o7~f|3mbnf2gb+!?u*;hCz!|(v&enJZZow15O)o z#(=XJaHi`;NmIs+y>Bu(mAI73WD}P%>Fm@@GBv3f1!+dtd>zjnXV+x7Q54TBd3<2} zv#CE@0MIQ2LFgn+P`V8m7)GB2Mv)#kfqv`wCg}#9-Br+0>*%ZkUoFB{L8unOnt;wY ze7I)2P+|0dlfjBzsL*;4_`56KhPMm-9=t#>Xdra8FbJI9Dl6y~(rVKkO<`fLaB;7& z*b+KCU1Sx6^?tyGE1rg%t2i4jZ^hAYdMY;E4VX}cZZN@*yB{OnK%o?ZrU zk=x`QXN~gLs3T9U7LI%dFjPF)GZR9$!(U}}a`$aE*iM67jf_-d z6+>EB3PZpEg_Q#&B00EA9QVm-uU*o4pPcr}Ln)#}h%W3^@FYeRScv&~In=4g9oa&U zuhS#vs|vk7-&yVTpq-E$`QKEXRTVM~nKDr5^Ih?TH#1MT3qD71+{fRdT^;BL>SDSr)g6hpiJVd`gDx;+&Dd#Q%lrG>Ld2R zXd^!6#K#(F+(F|lmg!}xkroeNw6;^*X(FB|FYP7v65soO3>-XfB{1X^SvG(RWiV zz`$kLM7{Ni`o!mJ&cKa>htA}pEi5&JC5J?`R9!fizIpJ>x%<=>(hVW)kciUN1^R1o M)Tix+FO%>2587ip6#xJL literal 1557 zcmb7Ezi-<{6h2a`&9y)mR5TN}FQWWXXv428G3D6*L?bJ<0fDW1Zjz3VjNPzapJ3Kyq?|a{ScjS+a zhJj#=|DxWehS2Zwp%|6icw;CCeUDt!MXutiz7nWiRgredF9ljx3-qp@K5KqCsB|l- zt^3uW)~zW>MMQVYL>;A>B`1#ou`388`7g zU*<*qmTaNZY_LhC)QVZCNgLxap4Ua6pT64h<|4TG!2MmY|X6a7%(M zNY5=}_qiPa7jGN);?PMIr)g8tPfWb+y4Vh}7ethaHpYEM101~Q*-?}`Gnpy=T8@_$ z$O4usgit7UxY2BG=5-BeKsIq`2jo^$+Rc`6*S9&pdIW{JORVoxLB)_TDa1?_7jo_c z*Z)x?@utmtl4PUxFGYKy&UD1oMtq34|o|rLr0PMiA4N-Li?SEIsG{iSDQA%}g4T|C&VOo*;=Po0rs=4pgG= z?B03wSyH{TeX!fx+3O_6!QuAd-rXMTP*TtB&cla?KcP23gx;E#JZ4sW6o(?V$aCVv zf->tFWnc5ic8HaIxao&sMlar0ZwAzjeR7*!gmC%W;eW!iC@G4vP*)Xgf#iabvOYn0 z`JSVXC-1uFXJHUBM^`3i??oBuwd0+W3#Zp#wqJfa)h^Gp%X6&?u8&S0pFBSOYT8(z zHP)xvrI~hVp_f+G?C;%I?i&uYrGP|`uS?f&KIx|?c&hZ0zEp*A0@v#d~_1}Ku$8i7v diff --git a/config/settings.py b/config/settings.py index 291d043..a3a8d0a 100644 --- a/config/settings.py +++ b/config/settings.py @@ -2,21 +2,42 @@ Django settings for config project. Generated by 'django-admin startproject' using Django 5.2.7. - -For more information on this file, see -https://docs.djangoproject.com/en/5.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/5.2/ref/settings/ """ from pathlib import Path import os from dotenv import load_dotenv +from django.utils.translation import gettext_lazy as _ +import django.conf.locale +import django.utils.translation BASE_DIR = Path(__file__).resolve().parent.parent load_dotenv(BASE_DIR.parent / ".env") +# Oromo and Amharic are not in Django's default LANG_INFO +EXTRA_LANG_INFO = { + 'om': { + 'bidi': False, + 'code': 'om', + 'name': 'Oromo', + 'name_local': 'Afaan Oromoo', + }, + 'am': { + 'bidi': False, + 'code': 'am', + 'name': 'Amharic', + 'name_local': 'አማርኛ', + }, +} + +# Add custom languages to LANG_INFO +for code, info in EXTRA_LANG_INFO.items(): + if code not in django.conf.locale.LANG_INFO: + django.conf.locale.LANG_INFO[code] = info + # Also update the reference in django.utils.translation if it exists + if hasattr(django.utils.translation, 'LANG_INFO') and code not in django.utils.translation.LANG_INFO: + django.utils.translation.LANG_INFO[code] = info + SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "change-me") DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true" @@ -37,18 +58,17 @@ CSRF_TRUSTED_ORIGINS = [ for host in CSRF_TRUSTED_ORIGINS ] -# Cookies must always be HTTPS-only; SameSite=Lax keeps CSRF working behind the proxy. SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = "None" CSRF_COOKIE_SAMESITE = "None" -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ +X_FRAME_OPTIONS = 'ALLOWALL' # Application definition INSTALLED_APPS = [ + 'modeltranslation', # Must be before admin 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -61,16 +81,13 @@ INSTALLED_APPS = [ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - # Disable X-Frame-Options middleware to allow Flatlogic preview iframes. - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -X_FRAME_OPTIONS = 'ALLOWALL' - ROOT_URLCONF = 'config.urls' TEMPLATES = [ @@ -83,7 +100,7 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - # IMPORTANT: do not remove – injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp + 'django.template.context_processors.i18n', 'core.context_processors.project_context', ], }, @@ -92,10 +109,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'config.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/5.2/ref/settings/#databases - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', @@ -110,56 +123,46 @@ DATABASES = { }, } - -# Password validation -# https://docs.djangoproject.com/en/5.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', - }, + {'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/5.2/topics/i18n/ +LANGUAGE_CODE = 'en' -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' +TIME_ZONE = 'Africa/Addis_Ababa' USE_I18N = True - USE_TZ = True +LANGUAGES = [ + ('en', _('English')), + ('am', _('Amharic')), + ('om', _('Afaan Oromoo')), +] -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.2/howto/static-files/ +MODELTRANSLATION_DEFAULT_LANGUAGE = 'en' + +LOCALE_PATHS = [ + BASE_DIR / 'locale', +] STATIC_URL = 'static/' -# Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS. STATIC_ROOT = BASE_DIR / 'staticfiles' - STATICFILES_DIRS = [ BASE_DIR / 'static', BASE_DIR / 'assets', BASE_DIR / 'node_modules', ] +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' + # Email -EMAIL_BACKEND = os.getenv( - "EMAIL_BACKEND", - "django.core.mail.backends.smtp.EmailBackend" -) +EMAIL_BACKEND = os.getenv("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") EMAIL_HOST = os.getenv("EMAIL_HOST", "127.0.0.1") EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587")) EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "") @@ -167,16 +170,9 @@ EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "") EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "true").lower() == "true" EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", "false").lower() == "true" DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "no-reply@example.com") -CONTACT_EMAIL_TO = [ - item.strip() - for item in os.getenv("CONTACT_EMAIL_TO", DEFAULT_FROM_EMAIL).split(",") - if item.strip() -] +CONTACT_EMAIL_TO = [item.strip() for item in os.getenv("CONTACT_EMAIL_TO", DEFAULT_FROM_EMAIL).split(",") if item.strip()] -# When both TLS and SSL flags are enabled, prefer SSL explicitly if EMAIL_USE_SSL: EMAIL_USE_TLS = False -# Default primary key field type -# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/config/urls.py b/config/urls.py index bcfc074..6c5d25d 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,29 +1,18 @@ -""" -URL configuration for config project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" from django.contrib import admin -from django.urls import include, path +from django.urls import path, include from django.conf import settings from django.conf.urls.static import static +from django.conf.urls.i18n import i18n_patterns urlpatterns = [ - path("admin/", admin.site.urls), - path("", include("core.urls")), + path('i18n/', include('django.conf.urls.i18n')), ] +urlpatterns += i18n_patterns( + path('admin/', admin.site.py_urls if hasattr(admin.site, 'py_urls') else admin.site.urls), + path('', include('core.urls')), +) + if settings.DEBUG: - urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392d6714413db63120e4233d2e96cbadb5de..ebd4fa1e052a5c074395024484f694771a0dbcbc 100644 GIT binary patch literal 2515 zcmb7F&2Jk;6rb5Ie>-WMuco0*lS;^-#u7!Mh=PPr6)H$TYQ7$V~!pwcbEUq8wFav+-a|q4L><6*SBYtvY{N%t- z{vCcQBYyH@{1m`X@g07uD)JIomLb21EYRT`?8%Yc3S)MwfR8HpxH8_y2oFNS1honCF8Yk;inl1~x7IY>b3AYvbZtR7vFLCTO)}?OA-1j2f@_a7y zU%4^g4#l3de&8(@3N^)&W-QI`1mi*$RP!L_K@;6)kJj&;EkNfP;l!ql*czj@&NQZb z)Hav_L7B3WZJ{ElYtm@B0hdPPC))yfeL+PPnhmhcI}T1djx0J(5VFL_e%Wz;OkBS| zQ}(?mc9<8neRogR+Jd*kb^=Lq=4^S~XVD9ChS9yb-Pj2O-bgl+R-82WQ{GJCP&9rD z#gnM*HhH5N3f@p;t+w|>1=c7qp%X0ubO!IM+Dhl!-nE<0KkY8u?izFHC+4yxL)>KI zAIlB!qT6Pln^H=8%8V{6n@QxgcoZoXq>)5WfRYzEyIgpX8>>_C9&B0H#5HJS6%8B* z5y{!8TLMKUrVvm>Vj9g#iZQ5n4DKoscSUojPnDQOE0xAP_OO}?@Y;xQueUk)3^xZ@Ll}4G}O9$1==}iX$U{nIIC2Ss;>}EQ26Xbtw~;5`?sNQ%UBVnMlpH z@S0qjQ5-g($TBeF?z*1uZu-2gi}ztSaSLGu07ybv$r9sHQ+RDW1WIpW5{5VGxp7>H z8@PN4VFqD@9?VA|po{1OfVb&+i1ge=dOqx||BF~~@eo%7vEWu0TyY(Eq97Cv_|fIW z;1oV+`5+OYf%TYr<9#7bI62&xCVv_WcQgeIS|DK2BP{ww3cd17=YFqT>DY+j0FDE6*+`F zLII(OpemOad>d{zF4D_LokmoQtUPiz;f@pi>Z|06>sV$*uF3_jR1cO8OZH9v9_$SF z=ojL1aD-}$eg{0!DWyHK)E)kMyfX!|L1!}Cpt6x*AMbX^y)FadZKA`^@Lo8 z?)0rw96m=l)rqz+u+9^0Qu(ls=-e@#JJED{_k>)A?$q)r4xb~SkNJUhG4qi-qA)st zq8S*+W$4ycPI34g0ewK!ee1GDs~Gwy9Ps1WItE|G;QO5$N&HU delta 168 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 Vi)9%mYs*M-Uto{{q9Qh+5CAPR9ryqM diff --git a/core/__pycache__/translation.cpython-311.pyc b/core/__pycache__/translation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c385385092a65dfffc214127e2e17904135c2f4 GIT binary patch literal 1273 zcmb7DOK;Oa5Z+xoj*~P#03PmfCS>;i{*Nk64j5ewgtVQNO0iB zvHu__>i^)JNI6tKRf0=zslurzW}G;*Nvf)A?|621=KE%5$3M&EB7yPc$Jf>`O2}_q zWJ8-O<3FJ65l%RDiAPQ9F_Tfk8CP7zQ%%*&nK@50HA)x}RH$7*+J`wAjJzaV{YH3B zu&x3pFejr6pZ#n>%zWydTjH&Ocm4uzJ@wX>ci*HE1bcE5=S)xm^-3lWi zlge9Z`=M(`t-ycPMj0MqYIP;ipV^Vv4&-iX zCgsFroSF(}K&m9`8PFnEk0q*adm<@u5jwJ!M$qi3iEdfAZ&^vvvOHjSQD3pFkFo7e zPqgFE}8lDY2g+VmnDE~$}0$rtP_f>sMRu|qjGI_i2Il1uTAc2 zksf-WycLHnUxZ;=XjumIIcz`_z4-zfx=7@ci?ctA*9 z7&=g?QW+Vlma0|fj{FI>bOZ9*i4BRZQ{N@FRVrVc%iZ^V-uIllcVDyFW`ZsE;a%x% zf{@>G61H40xc;dS@`+GFl`^R)Yl=c>15K10Dv7m(LK+BDD6qsPgo!lyqwx#6eaCOT z)tDw^lA%IVu}m77#)xT-WirS#MNCU9lSL*KF|Dyo4w-bsw8b**$kcX7a{=OiwJ+i%d3RZpAWv$h1aG|Nl%evzDU+%VccuQbJ>j58Ilxaft^gC&`@> z+ckaSCreez_GGxKRhX0B<_5KO%T)cAU|1-ZOv~vCX6me2wu{rU%d38C$WUf!rLxnl zYZfaSeE%la9?FH#-bv~jw^TnPe+of%e@)XURcA|8YdZ8UQCuli12N_d)hNy?#tw@C z>yGuXlh!wwzGc|BQYsueLk6detER2%%rxs3HK|ZrDO(>0VW(|}RjI*M&WgAXnCIu} zN+DF7_5h)pxv_3&oT`41Rfwd;u{J+LH??Zf7`J)ZRmW1Rpkx3gfZ}xvVlS z%ZST5;j$38JZUbkA#amiyX-o!^D}6M2a7LOHNTN-l*pe)sx(vWb-QZWD%)eaZQ(!I zHTagft?5kF4US=ai}CIKKdIRY9(kGNc^@Xr`Y>m(ze*^IazO^&o6iO5bZKz%dg&qh|*$$k${yedn}W+YtoUDqWC+-y5&wDU0U_M9~ fJYnMYQ+H;;g+&h*1uS0mw&WTPGxEcGaTEUnms)Oj delta 253 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 literal 1364 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 literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0002_remove_vendor_logo_remove_vendor_slug_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_remove_vendor_logo_remove_vendor_slug_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0308f9af3f6ce9af7cfa62012d415ee663dbc48 GIT binary patch literal 3647 zcmcInO-vg{6y9Ci>$QOd77`!zb@s=d-6bK) zAyRv&ddi_v?>SJa_|RjeUV7-MhssEMu;$tmH%H-=L)$mAUMG+gMB0qkGjHC^``$Ni zW}eqSbabQzD38AT%J{uq5dLJHs3e-g(@#P8UO)m8O`#z2R^qK>#;kZDF0!$hnXu%7 zY$XdxQILd>1Qh>TKnX0Dl?ZSvCJ2w=8s5H_UkzBqT}#j+H?d7Mv){k-Q~>cld^-Bo&f;Zi^j~ zP^=o?l3*5m!}8LM#4Bf{qWOR!gH5{J7HM#Mxg(OxCtA|UNUA3H^QtQQqr@3>Mzc}s zjpL^||E`wDHq>72*dLW6IiBDcY766Jwey&(?Anh|5|H{xMBS*TdTM`sj^uKhBXzg9 zw4pZGlb)zw=I5XF%BNbo(~vsbE9~!hUKNo5ltvpc!mxuIP3VnxV;4SqR&6NB8YX>sNQ?7-qj20RQibk)gG%F%8|nZ%=#?19;~b5561&_e0!sMKgiAo*{d~m zen*`T)cHm-&VZrXV^t$9q=uN~V7+&!-u3#HeRXw=`5fCm|MPn_b$Umg4%F$xy^Yk> zQD!r`y;@Tzcht#1ojly4KeA{D!n;H|P~VqwF;Lx%vTbIe=!oWwBaB!)RB9fCRh+Q9_N7lH|FKCh`uJL*!PE* - + - {% block title %}Knowledge Base{% endblock %} + + {% block title %}{% trans "Ethio-Marketplace" %}{% endblock %} + {% if project_description %} {% endif %} + {% if project_image_url %} {% endif %} - {% load static %} + + + + + + + + {% block head %}{% endblock %} - {% block content %}{% endblock %} + + + {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + +
+ {% block content %}{% endblock %} +
+ +
+
+
+
+
{% trans "Ethio-Market" %}
+

{% trans "Connecting Ethiopian local vendors with customers everywhere." %}

+
+
+

© 2026 {% trans "Ethio-Marketplace" %}. {% trans "All rights reserved." %}

+
+
+
+
+ + + + {% block scripts %}{% endblock %} - + \ No newline at end of file diff --git a/core/templates/core/cart_detail.html b/core/templates/core/cart_detail.html new file mode 100644 index 0000000..a24a543 --- /dev/null +++ b/core/templates/core/cart_detail.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "Shopping Cart" %}{% endblock %} + +{% block content %} +
+

{% trans "Your Shopping Cart" %}

+ + {% if cart_items %} +
+
+
+ + + + + + + + + + + + {% for item in cart_items %} + + + + + + + + {% endfor %} + +
{% trans "Product" %}{% trans "Price" %}{% trans "Quantity" %}{% trans "Subtotal" %}
+
+ {% if item.product.image %} + {{ item.product.name }} + {% endif %} +
+
{{ item.product.name }}
+ {{ item.product.vendor.business_name }} +
+
+
{{ item.product.price }} ETB{{ item.quantity }}{{ item.subtotal }} ETB + + {% trans "Remove" %} + +
+
+
+
+
+

{% trans "Order Summary" %}

+
+ {% trans "Subtotal" %} + {{ total }} ETB +
+
+ {% trans "Shipping" %} + {% trans "Free" %} +
+
+
+ {% trans "Total" %} + {{ total }} ETB +
+ + {% trans "Proceed to Checkout" %} + +
+
+
+ {% else %} +
+

{% trans "Your cart is empty." %}

+ {% trans "Start Shopping" %} +
+ {% endif %} +
+{% endblock %} diff --git a/core/templates/core/category_products.html b/core/templates/core/category_products.html new file mode 100644 index 0000000..e5bf750 --- /dev/null +++ b/core/templates/core/category_products.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{{ category.name }}{% endblock %} + +{% block content %} +
+
+

{{ category.name }}

+ {% if category.description %} +

{{ category.description }}

+ {% endif %} +
+ +
+ {% for product in products %} +
+
+ {% if product.image %} + {{ product.name }} + {% else %} +
+ {% trans "No Image" %} +
+ {% endif %} + +
+
+ {% empty %} +
+

{% trans "No products available in this category yet." %}

+
+ {% endfor %} +
+
+{% endblock %} diff --git a/core/templates/core/checkout.html b/core/templates/core/checkout.html new file mode 100644 index 0000000..eb6fe49 --- /dev/null +++ b/core/templates/core/checkout.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "Checkout" %}{% endblock %} + +{% block content %} +
+

{% trans "Checkout" %}

+ +
+ {% csrf_token %} +
+
+

{% trans "Shipping Address" %}

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +

{% trans "Payment Method" %}

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+

+ {% trans "Your cart" %} +

+
    +
  • + {% trans "Total (ETB)" %} + {{ total }} ETB +
  • +
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..50f3099 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,98 @@ {% extends "base.html" %} +{% load i18n static %} -{% block title %}{{ project_name }}{% endblock %} +{% block title %}{% trans "Ethio-Marketplace | Home" %}{% endblock %} + +{% block content %} + +
+
+

{% trans "Welcome to Ethio-Marketplace" %}

+

{% trans "Discover amazing products from local vendors across Ethiopia." %}

+ +
+
+ + +
+
+
+

{% trans "Shop by Category" %}

+ {% trans "View All" %} +
+
+ {% for category in categories %} + + {% empty %} +

{% trans "No categories found." %}

+ {% endfor %} +
+
+
+ + +
+
+
+

{% trans "Featured Products" %}

+ {% trans "View All" %} +
+
+ {% for product in featured_products %} +
+
+ {% if product.image %} + {{ product.name }} + {% else %} +
+ {% trans "No Image" %} +
+ {% endif %} +
+

{{ product.category.name }}

+
+ + {{ product.name }} + +
+

{{ product.price }} ETB

+ + {% trans "Add to Cart" %} + +
+
+
+ {% empty %} +
+

{% trans "No featured products available." %}

+
+ {% endfor %} +
+
+
-{% block head %} - - - {% endblock %} - -{% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… -
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file diff --git a/core/templates/core/order_success.html b/core/templates/core/order_success.html new file mode 100644 index 0000000..905cd0a --- /dev/null +++ b/core/templates/core/order_success.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "Order Successful" %}{% endblock %} + +{% block content %} +
+
+ +
+

{% trans "Thank you for your order!" %}

+

{% trans "Your order ID is" %} #{{ order.id }}. {% trans "We have received your request and will contact you shortly for delivery." %}

+ +
+
+
+
{% trans "Order Details" %}
+

{% trans "Name" %}: {{ order.full_name }}

+

{% trans "Phone" %}: {{ order.phone }}

+

{% trans "Address" %}: {{ order.address }}

+

{% trans "Total" %}: {{ order.total_price }} ETB

+

{% trans "Payment" %}: {{ order.get_payment_method_display }}

+
+
+
+ + +
+{% endblock %} diff --git a/core/templates/core/product_detail.html b/core/templates/core/product_detail.html new file mode 100644 index 0000000..8e99307 --- /dev/null +++ b/core/templates/core/product_detail.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{{ product.name }}{% endblock %} + +{% block content %} +
+ + +
+
+ {% if product.image %} + {{ product.name }} + {% else %} +
+ {% trans "No Image Available" %} +
+ {% endif %} +
+
+

{{ product.name }}

+

{{ product.category.name }}

+

{{ product.price }} ETB

+ +
+
{% trans "Description" %}
+

{{ product.description }}

+
+ +
+
{% trans "Sold by" %}: {{ product.vendor.business_name }}
+

{% trans "Located in" %}: {{ product.vendor.address }}

+
+ + +
+
+
+{% endblock %} diff --git a/core/templates/core/product_list.html b/core/templates/core/product_list.html new file mode 100644 index 0000000..2b5ce48 --- /dev/null +++ b/core/templates/core/product_list.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "All Products" %}{% endblock %} + +{% block content %} +
+
+
+

{% trans "Our Products" %}

+
+
+
+ + +
+
+
+ +
+ {% for product in products %} +
+
+ {% if product.image %} + {{ product.name }} + {% else %} +
+ {% trans "No Image" %} +
+ {% endif %} +
+

{{ product.category.name }}

+
+ + {{ product.name }} + +
+

{{ product.price }} ETB

+ + {% trans "Add to Cart" %} + +
+
+
+ {% empty %} +
+

{% trans "No products found matching your search." %}

+
+ {% endfor %} +
+
+{% endblock %} diff --git a/core/templates/core/vendor_dashboard.html b/core/templates/core/vendor_dashboard.html new file mode 100644 index 0000000..07c94cc --- /dev/null +++ b/core/templates/core/vendor_dashboard.html @@ -0,0 +1,128 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "Vendor Dashboard" %}{% endblock %} + +{% block content %} +
+
+
+

{{ vendor.business_name }}

+

{% trans "Manage your products and orders" %}

+
+ + {% trans "Add New Product" %} + +
+ +
+ +
+
+
{% trans "Total Products" %}
+

{{ products.count }}

+
+
+ +
+
+
{% trans "Total Orders" %}
+

{{ orders.count }}

+
+
+ +
+
+
{% trans "Status" %}
+ {% if vendor.is_verified %} + {% trans "Verified" %} + {% else %} + {% trans "Pending Verification" %} + {% endif %} +
+
+
+ +
+ + +
+
+
+ + + + + + + + + + + + {% for product in products %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Product" %}{% trans "Category" %}{% trans "Price" %}{% trans "Status" %}{% trans "Actions" %}
{{ product.name }}{{ product.category.name }}{{ product.price }} ETB + {% if product.is_available %} + {% trans "Available" %} + {% else %} + {% trans "Out of Stock" %} + {% endif %} + + {% trans "Edit" %} +
{% trans "No products found." %}
+
+
+
+
+ + + + + + + + + + + + {% for item in orders %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Order ID" %}{% trans "Product" %}{% trans "Customer" %}{% trans "Total" %}{% trans "Status" %}
#{{ item.order.id }}{{ item.product.name }}{{ item.order.full_name }}{{ item.total_price }} ETB{{ item.order.status }}
{% trans "No orders found." %}
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/vendor_register.html b/core/templates/core/vendor_register.html new file mode 100644 index 0000000..44ab179 --- /dev/null +++ b/core/templates/core/vendor_register.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "Become a Seller" %}{% endblock %} + +{% block content %} +
+
+
+
+

{% trans "Register as a Vendor" %}

+

{% trans "Join our community and start selling your products to thousands of customers." %}

+ +
+ {% csrf_token %} +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+
+{% endblock %} diff --git a/core/translation.py b/core/translation.py new file mode 100644 index 0000000..f1455d0 --- /dev/null +++ b/core/translation.py @@ -0,0 +1,14 @@ +from modeltranslation.translator import register, TranslationOptions +from .models import Category, Product, Vendor + +@register(Category) +class CategoryTranslationOptions(TranslationOptions): + fields = ('name', 'description') + +@register(Product) +class ProductTranslationOptions(TranslationOptions): + fields = ('name', 'description') + +@register(Vendor) +class VendorTranslationOptions(TranslationOptions): + fields = ('business_name', 'description') diff --git a/core/urls.py b/core/urls.py index 6299e3d..0cfd2ca 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,16 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), + path('', views.home, name='index'), + path('products/', views.product_list, name='product_list'), + path('product//', views.product_detail, name='product_detail'), + path('category//', views.category_products, name='category_products'), + path('cart/', views.cart_detail, name='cart_detail'), + path('cart/add//', views.cart_add, name='cart_add'), + path('cart/remove//', views.cart_remove, name='cart_remove'), + path('checkout/', views.checkout, name='checkout'), + path('order/success//', views.order_success, name='order_success'), + path('vendor/register/', views.vendor_register, name='vendor_register'), + path('vendor/dashboard/', views.vendor_dashboard, name='vendor_dashboard'), ] diff --git a/core/views.py b/core/views.py index c9aed12..9485a1e 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,143 @@ -import os -import platform - -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone - +from django.shortcuts import render, get_object_or_404, redirect +from django.contrib.auth.decorators import login_required +from .models import Category, Product, Vendor, Order, OrderItem, Profile +from django.contrib import messages +from django.db.models import Q def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + categories = Category.objects.all()[:6] + featured_products = Product.objects.filter(is_available=True)[:8] + return render(request, 'core/index.html', { + 'categories': categories, + 'featured_products': featured_products, + }) - context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), - } - return render(request, "core/index.html", context) +def product_list(request): + query = request.GET.get('q') + products = Product.objects.filter(is_available=True) + if query: + products = products.filter( + Q(name__icontains=query) | + Q(description__icontains=query) + ) + return render(request, 'core/product_list.html', {'products': products}) + +def product_detail(request, slug): + product = get_object_or_404(Product, slug=slug, is_available=True) + return render(request, 'core/product_detail.html', {'product': product}) + +def category_products(request, slug): + category = get_object_or_404(Category, slug=slug) + products = category.products.filter(is_available=True) + return render(request, 'core/category_products.html', {'category': category, 'products': products}) + +# Basic Cart System using Session +def get_cart(request): + cart = request.session.get('cart', {}) + return cart + +def cart_add(request, product_id): + cart = get_cart(request) + product_id_str = str(product_id) + if product_id_str in cart: + cart[product_id_str] += 1 + else: + cart[product_id_str] = 1 + request.session['cart'] = cart + messages.success(request, "Product added to cart") + return redirect('cart_detail') + +def cart_remove(request, product_id): + cart = get_cart(request) + product_id_str = str(product_id) + if product_id_str in cart: + del cart[product_id_str] + request.session['cart'] = cart + return redirect('cart_detail') + +def cart_detail(request): + cart = get_cart(request) + cart_items = [] + total = 0 + for product_id, quantity in cart.items(): + product = get_object_or_404(Product, id=product_id) + subtotal = product.price * quantity + total += subtotal + cart_items.append({'product': product, 'quantity': quantity, 'subtotal': subtotal}) + return render(request, 'core/cart_detail.html', {'cart_items': cart_items, 'total': total}) + +def checkout(request): + cart = get_cart(request) + if not cart: + return redirect('product_list') + + if request.method == 'POST': + full_name = request.POST.get('full_name') + email = request.POST.get('email') + phone = request.POST.get('phone') + address = request.POST.get('address') + payment_method = request.POST.get('payment_method') + + total = 0 + order_items = [] + for product_id, quantity in cart.items(): + product = get_object_or_404(Product, id=product_id) + total += product.price * quantity + order_items.append((product, quantity, product.price)) + + order = Order.objects.create( + user=request.user if request.user.is_authenticated else None, + full_name=full_name, + email=email, + phone=phone, + address=address, + total_price=total, + payment_method=payment_method + ) + + for product, quantity, price in order_items: + OrderItem.objects.create(order=order, product=product, quantity=quantity, price=price) + + request.session['cart'] = {} + return redirect('order_success', order_id=order.id) + + return render(request, 'core/checkout.html') + +def order_success(request, order_id): + order = get_object_or_404(Order, id=order_id) + return render(request, 'core/order_success.html', {'order': order}) + +@login_required +def vendor_register(request): + if hasattr(request.user, 'vendor'): + return redirect('vendor_dashboard') + + if request.method == 'POST': + business_name = request.POST.get('business_name') + description = request.POST.get('description') + address = request.POST.get('address') + phone = request.POST.get('phone') + + Vendor.objects.create( + user=request.user, + business_name=business_name, + description=description, + address=address, + phone=phone + ) + # Update user role + profile, created = Profile.objects.get_or_create(user=request.user) + profile.role = 'seller' + profile.save() + + messages.success(request, "Vendor registration successful. Wait for admin verification.") + return redirect('vendor_dashboard') + + return render(request, 'core/vendor_register.html') + +@login_required +def vendor_dashboard(request): + vendor = get_object_or_404(Vendor, user=request.user) + products = vendor.products.all() + orders = OrderItem.objects.filter(product__vendor=vendor).select_related('order') + return render(request, 'core/vendor_dashboard.html', {'vendor': vendor, 'products': products, 'orders': orders}) \ No newline at end of file diff --git a/populate_db.py b/populate_db.py new file mode 100644 index 0000000..654653d --- /dev/null +++ b/populate_db.py @@ -0,0 +1,111 @@ +import os +import django + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +django.setup() + +from core.models import Category, Product, Vendor, Profile +from django.contrib.auth.models import User +from django.utils.text import slugify + +def populate(): + # Create Superuser if not exists + if not User.objects.filter(username='admin').exists(): + User.objects.create_superuser('admin', 'admin@example.com', 'adminpass') + print("Superuser created: admin / adminpass") + + # Categories + categories_data = [ + {'name': 'Electronics', 'name_om': 'Meeshaalee Elektirooniksii', 'name_am': 'የኤሌክትሮኒክስ ዕቃዎች'}, + {'name': 'Clothing', 'name_om': 'Uffata', 'name_am': 'ልብስ'}, + {'name': 'Home & Garden', 'name_om': 'Mana fi Muka', 'name_am': 'ቤት እና የአትክልት ቦታ'}, + {'name': 'Food & Groceries', 'name_om': 'Nyaataa fi Meeshaalee Nyaataa', 'name_am': 'ምግብ እና ግሮሰሪ'}, + {'name': 'Handmade Crafts', 'name_om': 'Hojii Harkaa', 'name_am': 'በእጅ የተሰሩ ስራዎች'}, + {'name': 'Books', 'name_om': 'Kitaabota', 'name_am': 'መጽሐፍት'}, + ] + + categories = [] + for cat in categories_data: + category, created = Category.objects.get_or_create( + name=cat['name'], + defaults={ + 'name_om': cat['name_om'], + 'name_am': cat['name_am'], + 'slug': slugify(cat['name']) + } + ) + categories.append(category) + if created: + print(f"Category created: {cat['name']}") + + # Create a Vendor + vendor_user, created = User.objects.get_or_create(username='vendor1', email='vendor1@example.com') + if created: + vendor_user.set_password('vendorpass') + vendor_user.save() + Profile.objects.get_or_create(user=vendor_user, role='seller') + print("Vendor user created: vendor1") + + vendor, created = Vendor.objects.get_or_create( + user=vendor_user, + defaults={ + 'business_name': 'Ethio Tech Solutions', + 'business_name_om': 'Furmaata Teeknoojii Itiyoophiyaa', + 'description': 'Leading provider of tech gadgets in Addis.', + 'address': 'Bole, Addis Ababa', + 'phone': '+251911000000' + } + ) + if created: + print("Vendor created: Ethio Tech Solutions") + + # Products + products_data = [ + { + 'name': 'Smartphone X1', + 'name_om': 'Bilbila Ammayya X1', + 'category': categories[0], + 'price': 25000, + 'description': 'High performance smartphone.' + }, + { + 'name': 'Traditional Coffee Pot (Jebena)', + 'name_om': 'Jabanaa', + 'category': categories[4], + 'price': 500, + 'description': 'Handmade traditional clay pot.' + }, + { + 'name': 'Cotton Scarf', + 'name_om': 'Shaashii', + 'category': categories[1], + 'price': 1200, + 'description': 'Pure Ethiopian cotton.' + }, + { + 'name': 'Organic Honey', + 'name_om': 'Damma', + 'category': categories[3], + 'price': 800, + 'description': 'Pure honey from Gojam.' + }, + ] + + for prod in products_data: + product, created = Product.objects.get_or_create( + name=prod['name'], + defaults={ + 'name_om': prod['name_om'], + 'category': prod['category'], + 'vendor': vendor, + 'price': prod['price'], + 'description': prod['description'], + 'slug': slugify(prod['name']), + 'is_available': True + } + ) + if created: + print(f"Product created: {prod['name']}") + +if __name__ == '__main__': + populate() diff --git a/requirements.txt b/requirements.txt index e22994c..e836360 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 +Pillow==11.1.0 +django-modeltranslation==0.19.11 +httpx==0.28.1 \ No newline at end of file