diff --git a/Planteer/Planteer/__init__.py b/Planteer/Planteer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/Planteer/asgi.py b/Planteer/Planteer/asgi.py
new file mode 100644
index 0000000..7693f2d
--- /dev/null
+++ b/Planteer/Planteer/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for Planteer project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Planteer.settings')
+
+application = get_asgi_application()
diff --git a/Planteer/Planteer/settings.py b/Planteer/Planteer/settings.py
new file mode 100644
index 0000000..16059fa
--- /dev/null
+++ b/Planteer/Planteer/settings.py
@@ -0,0 +1,127 @@
+"""
+Django settings for Planteer project.
+
+Generated by 'django-admin startproject' using Django 6.0.4.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/6.0/ref/settings/
+"""
+
+from pathlib import Path
+import os
+
+# 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/6.0/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-=bq-yu6kds01c+*nn4^kcixwmy%(0v1r1rd7zk1vjdv3gu@3=r'
+
+# 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',
+ 'main',
+ 'plants',
+ 'accounts',
+]
+
+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 = 'Planteer.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'Planteer.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/6.0/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/6.0/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+TIME_ZONE = 'Asia/Riyadh'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/6.0/howto/static-files/
+
+STATIC_URL = 'static/'
+
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+
diff --git a/Planteer/Planteer/urls.py b/Planteer/Planteer/urls.py
new file mode 100644
index 0000000..5817a04
--- /dev/null
+++ b/Planteer/Planteer/urls.py
@@ -0,0 +1,27 @@
+"""
+URL configuration for Planteer project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/6.0/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 path , include
+from django.conf.urls.static import static
+from . import settings
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('', include('main.urls')),
+ path('plants/', include('plants.urls')),
+ path('accounts/', include('accounts.urls'))
+] + static(settings.MEDIA_URL,document_root =settings.MEDIA_ROOT)
diff --git a/Planteer/Planteer/wsgi.py b/Planteer/Planteer/wsgi.py
new file mode 100644
index 0000000..f14ac6f
--- /dev/null
+++ b/Planteer/Planteer/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for Planteer project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Planteer.settings')
+
+application = get_wsgi_application()
diff --git a/Planteer/accounts/__init__.py b/Planteer/accounts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/accounts/admin.py b/Planteer/accounts/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/Planteer/accounts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/Planteer/accounts/apps.py b/Planteer/accounts/apps.py
new file mode 100644
index 0000000..9b3fc5a
--- /dev/null
+++ b/Planteer/accounts/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+ name = 'accounts'
diff --git a/Planteer/accounts/migrations/__init__.py b/Planteer/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/accounts/models.py b/Planteer/accounts/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/Planteer/accounts/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/Planteer/accounts/templates/accounts/signin.html b/Planteer/accounts/templates/accounts/signin.html
new file mode 100644
index 0000000..c8ba3e2
--- /dev/null
+++ b/Planteer/accounts/templates/accounts/signin.html
@@ -0,0 +1,30 @@
+{% extends 'main/base.html' %}
+
+{% block title %}Sign In{% endblock %}
+
+{% block content %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/accounts/templates/accounts/signup.html b/Planteer/accounts/templates/accounts/signup.html
new file mode 100644
index 0000000..6e559a1
--- /dev/null
+++ b/Planteer/accounts/templates/accounts/signup.html
@@ -0,0 +1,43 @@
+{% extends 'main/base.html' %}
+{% block title %}Sign Up{% endblock %}
+{% block content %}
+
+
+
+
Sign Up
+
+ {% if messages %}
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+ {% endif %}
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/accounts/tests.py b/Planteer/accounts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/Planteer/accounts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/Planteer/accounts/urls.py b/Planteer/accounts/urls.py
new file mode 100644
index 0000000..49447cc
--- /dev/null
+++ b/Planteer/accounts/urls.py
@@ -0,0 +1,11 @@
+from django.urls import path
+from . import views
+
+app_name="accounts"
+
+urlpatterns=[
+
+ path('signup/', views.sign_up, name="sign_up"),
+ path('signin/', views.sign_in, name="sign_in"),
+ path('logout/', views.log_out, name="log_out"),
+]
\ No newline at end of file
diff --git a/Planteer/accounts/views.py b/Planteer/accounts/views.py
new file mode 100644
index 0000000..6929287
--- /dev/null
+++ b/Planteer/accounts/views.py
@@ -0,0 +1,54 @@
+from django.shortcuts import render,redirect
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate, login, logout
+from django.contrib import messages
+from django.http import HttpRequest,HttpResponse
+
+# Create your views here.
+
+def sign_up(request: HttpRequest):
+
+ if request.method == "POST":
+ try:
+ new_user = User.objects.create_user(
+ username=request.POST["username"],
+ password=request.POST["password"],
+ email=request.POST["email"],
+ first_name=request.POST["first_name"],
+ last_name=request.POST["last_name"]
+ )
+ new_user.save()
+ messages.success(request, "Registered Successfully!", "alert-success")
+ return redirect("accounts:sign_in")
+ except Exception as e:
+ print(e)
+ messages.error(request, "Username already exists!", "alert-danger")
+
+ return render(request, "accounts/signup.html")
+
+def sign_in(request:HttpRequest):
+
+ if request.method == "POST":
+
+ #checking user credentials
+ user = authenticate(request, username=request.POST["username"], password=request.POST["password"])
+ print(user)
+ if user:
+ #login the user
+ login(request, user)
+ messages.success(request, "Logged in successfully", "alert-success")
+ return redirect(request.GET.get("next", "/"))
+ else:
+ messages.error(request, "Please try again. You credentials are wrong", "alert-danger")
+
+
+ return render(request, "accounts/signin.html")
+
+
+
+def log_out(request: HttpRequest):
+
+ logout(request)
+ messages.warning(request, "logged out successfully", "alert-warning")
+
+ return redirect(request.GET.get("next", "/"))
\ No newline at end of file
diff --git a/Planteer/db.sqlite3 b/Planteer/db.sqlite3
new file mode 100644
index 0000000..0cf2b1b
Binary files /dev/null and b/Planteer/db.sqlite3 differ
diff --git a/Planteer/main/__init__.py b/Planteer/main/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/main/admin.py b/Planteer/main/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/Planteer/main/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/Planteer/main/apps.py b/Planteer/main/apps.py
new file mode 100644
index 0000000..833bff6
--- /dev/null
+++ b/Planteer/main/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class MainConfig(AppConfig):
+ name = 'main'
diff --git a/Planteer/main/migrations/0001_initial.py b/Planteer/main/migrations/0001_initial.py
new file mode 100644
index 0000000..dab0471
--- /dev/null
+++ b/Planteer/main/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# Generated by Django 6.0.4 on 2026-04-19 18:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Contact',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('first_name', models.CharField(max_length=1024)),
+ ('last_name', models.CharField(max_length=1024)),
+ ('email', models.EmailField(max_length=254)),
+ ('message', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ ]
diff --git a/Planteer/main/migrations/__init__.py b/Planteer/main/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/main/models.py b/Planteer/main/models.py
new file mode 100644
index 0000000..db34519
--- /dev/null
+++ b/Planteer/main/models.py
@@ -0,0 +1,10 @@
+from django.db import models
+
+# Create your models here.
+
+class Contact (models.Model):
+ first_name = models.CharField(max_length=1024)
+ last_name = models.CharField(max_length=1024)
+ email = models.EmailField()
+ message = models.TextField()
+ created_at = models.DateTimeField(auto_now_add=True)
diff --git a/Planteer/main/static/css/style.css b/Planteer/main/static/css/style.css
new file mode 100644
index 0000000..33ce4e2
--- /dev/null
+++ b/Planteer/main/static/css/style.css
@@ -0,0 +1,145 @@
+
+
+/* ===== General ===== */
+* {
+ font-family: "Ubuntu", sans-serif;
+}
+
+main {
+ min-height: 70vh;
+}
+
+a {
+ text-decoration: none;
+ transition: all 0.2s ease-in;
+}
+
+/* ===== Navbar ===== */
+.navbar-brand h2 {
+ color: #4CAF50;
+ font-size: 1.8rem;
+}
+
+/* ===== Hero Section ===== */
+.hero-section {
+ background-color: #f0f7f0;
+ border-radius: 16px;
+ padding: 3rem 2rem;
+ margin-bottom: 2rem;
+}
+
+.hero-section h1 {
+ font-size: 3rem;
+ color: #2e7d32;
+ font-weight: 700;
+}
+
+.hero-section h4 {
+ color: #555;
+}
+
+/* ===== Cards ===== */
+.card {
+ border: none;
+ border-radius: 12px;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.08);
+ transition: transform 0.2s ease-in;
+}
+
+.card:hover {
+ transform: translateY(-5px);
+}
+
+.card-img-top {
+ border-radius: 12px 12px 0 0;
+}
+
+/* ===== Badges ===== */
+.badge.bg-success {
+ background-color: #4CAF50 !important;
+ font-size: 0.8rem;
+ padding: 0.4rem 0.8rem;
+ border-radius: 20px;
+}
+
+/* ===== Buttons ===== */
+.btn-success {
+ background-color: #4CAF50;
+ border: none;
+ border-radius: 8px;
+}
+
+.btn-success:hover {
+ background-color: #388e3c;
+}
+
+.btn-outline-success {
+ border-color: #4CAF50;
+ color: #4CAF50;
+ border-radius: 8px;
+}
+
+.btn-outline-success:hover {
+ background-color: #4CAF50;
+ color: white;
+}
+
+/* ===== Forms ===== */
+.form-control, .form-select {
+ border-radius: 8px;
+ border: 1px solid #ddd;
+ padding: 0.6rem 1rem;
+}
+
+.form-control:focus, .form-select:focus {
+ border-color: #4CAF50;
+ box-shadow: 0 0 0 0.2rem rgba(76,175,80,0.2);
+}
+
+/* ===== HR ===== */
+hr {
+ border-color: #e0e0e0;
+ margin: 2rem 0;
+}
+
+/* ===== Footer ===== */
+footer {
+ background-color: #f9f9f9;
+ color: #555;
+}
+
+footer h5 {
+ color: #2e7d32;
+}
+
+.form-check{
+ width: 150px;
+}
+
+.border-color{
+ background-color: #e1dcdc;
+
+}
+/* footer {
+ background-color: #212529;
+ color: #adb5bd;
+}
+
+footer h5 {
+ color: #4CAF50;
+}
+
+footer a {
+ color: #adb5bd;
+}
+
+footer a:hover {
+ color: #4CAF50;
+} */
+
+/* ===== Responsive ===== */
+@media (max-width: 768px) {
+ .hero-section h1 {
+ font-size: 2rem;
+ }
+}
\ No newline at end of file
diff --git a/Planteer/main/static/images/Carrot.jpg b/Planteer/main/static/images/Carrot.jpg
new file mode 100644
index 0000000..7a9bcfc
Binary files /dev/null and b/Planteer/main/static/images/Carrot.jpg differ
diff --git a/Planteer/main/static/images/Cedar-Tree.jpg b/Planteer/main/static/images/Cedar-Tree.jpg
new file mode 100644
index 0000000..64287d9
Binary files /dev/null and b/Planteer/main/static/images/Cedar-Tree.jpg differ
diff --git a/Planteer/main/static/images/Lavendar.jpg b/Planteer/main/static/images/Lavendar.jpg
new file mode 100644
index 0000000..341cd95
Binary files /dev/null and b/Planteer/main/static/images/Lavendar.jpg differ
diff --git a/Planteer/main/static/images/Maple-Tree.jpg b/Planteer/main/static/images/Maple-Tree.jpg
new file mode 100644
index 0000000..f444356
Binary files /dev/null and b/Planteer/main/static/images/Maple-Tree.jpg differ
diff --git a/Planteer/main/static/images/Mint.jpg b/Planteer/main/static/images/Mint.jpg
new file mode 100644
index 0000000..2620a51
Binary files /dev/null and b/Planteer/main/static/images/Mint.jpg differ
diff --git a/Planteer/main/static/images/Oak-Tree.jpg b/Planteer/main/static/images/Oak-Tree.jpg
new file mode 100644
index 0000000..c20f46c
Binary files /dev/null and b/Planteer/main/static/images/Oak-Tree.jpg differ
diff --git a/Planteer/main/static/images/Sun-Flower.jpg b/Planteer/main/static/images/Sun-Flower.jpg
new file mode 100644
index 0000000..82547d6
Binary files /dev/null and b/Planteer/main/static/images/Sun-Flower.jpg differ
diff --git a/Planteer/main/static/images/Tomato.jpg b/Planteer/main/static/images/Tomato.jpg
new file mode 100644
index 0000000..b45c8ce
Binary files /dev/null and b/Planteer/main/static/images/Tomato.jpg differ
diff --git a/Planteer/main/static/images/apple_tree.jpg b/Planteer/main/static/images/apple_tree.jpg
new file mode 100644
index 0000000..9dc600a
Binary files /dev/null and b/Planteer/main/static/images/apple_tree.jpg differ
diff --git a/Planteer/main/static/images/pine-tree.jpg b/Planteer/main/static/images/pine-tree.jpg
new file mode 100644
index 0000000..675cc4f
Binary files /dev/null and b/Planteer/main/static/images/pine-tree.jpg differ
diff --git a/Planteer/main/templates/main/base.html b/Planteer/main/templates/main/base.html
new file mode 100644
index 0000000..d366da8
--- /dev/null
+++ b/Planteer/main/templates/main/base.html
@@ -0,0 +1,95 @@
+{% load static %}
+
+
+
+
+
+ {% block title %}Planteer{% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Planteer/main/templates/main/contact.html b/Planteer/main/templates/main/contact.html
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/main/templates/main/home.html b/Planteer/main/templates/main/home.html
new file mode 100644
index 0000000..7c96778
--- /dev/null
+++ b/Planteer/main/templates/main/home.html
@@ -0,0 +1,45 @@
+{% extends 'main/base.html' %}
+
+{% block title %}Home Page{% endblock %}
+
+{% block content %}
+
+
+
+
+
Planteer
+ Plant Database For Plant Lovers
+
+
+
+
+
+
+
+
+
Latest Plants
+
More →
+
+
+
+ {% for plant in plants %}
+
+
+

+
+
{{ plant.category }}
+
{{ plant.name }}
+
{{ plant.about|truncatechars:80 }}
+
View Details
+
+
+
+ {% empty %}
+
No plants yet. Add one!
+ {% endfor %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/main/templates/main/message.html b/Planteer/main/templates/main/message.html
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/main/tests.py b/Planteer/main/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/Planteer/main/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/Planteer/main/urls.py b/Planteer/main/urls.py
new file mode 100644
index 0000000..076d8ed
--- /dev/null
+++ b/Planteer/main/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+from . import views
+
+app_name="main"
+
+urlpatterns=[
+
+ path('', views.home_view, name="home_view"),
+ path('contact/', views.contact_view, name="contact_view"),
+ path('message/', views.message_view, name="message_view")
+
+]
\ No newline at end of file
diff --git a/Planteer/main/views.py b/Planteer/main/views.py
new file mode 100644
index 0000000..34bf289
--- /dev/null
+++ b/Planteer/main/views.py
@@ -0,0 +1,21 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest,HttpResponse
+from .models import Contact
+from plants.models import Plant
+
+# Create your views here.
+
+
+def home_view(request:HttpRequest):
+ plants = Plant.objects.all().order_by("-created_at")[0:3]
+ return render(request, "main/home.html" , {"plants": plants})
+
+def contact_view(request:HttpRequest):
+
+ return render(request, "main/contact.html")
+
+def message_view(request:HttpRequest):
+
+ return render(request, "main/message.html")
+
+
diff --git a/Planteer/manage.py b/Planteer/manage.py
new file mode 100644
index 0000000..bd0a64a
--- /dev/null
+++ b/Planteer/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Planteer.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Planteer/media/images/Carrot.jpg b/Planteer/media/images/Carrot.jpg
new file mode 100644
index 0000000..7a9bcfc
Binary files /dev/null and b/Planteer/media/images/Carrot.jpg differ
diff --git a/Planteer/media/images/Cedar-Tree.jpg b/Planteer/media/images/Cedar-Tree.jpg
new file mode 100644
index 0000000..64287d9
Binary files /dev/null and b/Planteer/media/images/Cedar-Tree.jpg differ
diff --git a/Planteer/media/images/Lavendar.jpg b/Planteer/media/images/Lavendar.jpg
new file mode 100644
index 0000000..341cd95
Binary files /dev/null and b/Planteer/media/images/Lavendar.jpg differ
diff --git a/Planteer/media/images/Maple-Tree.jpg b/Planteer/media/images/Maple-Tree.jpg
new file mode 100644
index 0000000..f444356
Binary files /dev/null and b/Planteer/media/images/Maple-Tree.jpg differ
diff --git a/Planteer/media/images/Mint.jpg b/Planteer/media/images/Mint.jpg
new file mode 100644
index 0000000..2620a51
Binary files /dev/null and b/Planteer/media/images/Mint.jpg differ
diff --git a/Planteer/media/images/Oak-Tree.jpg b/Planteer/media/images/Oak-Tree.jpg
new file mode 100644
index 0000000..c20f46c
Binary files /dev/null and b/Planteer/media/images/Oak-Tree.jpg differ
diff --git a/Planteer/media/images/Sun-Flower.jpg b/Planteer/media/images/Sun-Flower.jpg
new file mode 100644
index 0000000..82547d6
Binary files /dev/null and b/Planteer/media/images/Sun-Flower.jpg differ
diff --git a/Planteer/media/images/Tomato.jpg b/Planteer/media/images/Tomato.jpg
new file mode 100644
index 0000000..b45c8ce
Binary files /dev/null and b/Planteer/media/images/Tomato.jpg differ
diff --git a/Planteer/media/images/apple_tree.jpg b/Planteer/media/images/apple_tree.jpg
new file mode 100644
index 0000000..9dc600a
Binary files /dev/null and b/Planteer/media/images/apple_tree.jpg differ
diff --git a/Planteer/media/images/pine-tree.jpg b/Planteer/media/images/pine-tree.jpg
new file mode 100644
index 0000000..e832480
Binary files /dev/null and b/Planteer/media/images/pine-tree.jpg differ
diff --git a/Planteer/media/images/pine-tree_ADdiO9n.jpg b/Planteer/media/images/pine-tree_ADdiO9n.jpg
new file mode 100644
index 0000000..ad48726
Binary files /dev/null and b/Planteer/media/images/pine-tree_ADdiO9n.jpg differ
diff --git a/Planteer/media/images/pine-tree_IJZnJqV.jpg b/Planteer/media/images/pine-tree_IJZnJqV.jpg
new file mode 100644
index 0000000..675cc4f
Binary files /dev/null and b/Planteer/media/images/pine-tree_IJZnJqV.jpg differ
diff --git a/Planteer/plants/__init__.py b/Planteer/plants/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/plants/admin.py b/Planteer/plants/admin.py
new file mode 100644
index 0000000..2817e0e
--- /dev/null
+++ b/Planteer/plants/admin.py
@@ -0,0 +1,8 @@
+from django.contrib import admin
+from .models import Plant, Comment, Country
+
+# Register your models here.
+
+admin.site.register(Plant)
+admin.site.register(Comment)
+admin.site.register(Country)
diff --git a/Planteer/plants/apps.py b/Planteer/plants/apps.py
new file mode 100644
index 0000000..478c3ce
--- /dev/null
+++ b/Planteer/plants/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class PlantsConfig(AppConfig):
+ name = 'plants'
diff --git a/Planteer/plants/forms.py b/Planteer/plants/forms.py
new file mode 100644
index 0000000..878f4ea
--- /dev/null
+++ b/Planteer/plants/forms.py
@@ -0,0 +1,8 @@
+from django import forms
+from .models import Plant
+
+# Create the form class.
+class PlantForm(forms.ModelForm):
+ class Meta:
+ model = Plant
+ fields = "__all__"
\ No newline at end of file
diff --git a/Planteer/plants/migrations/0001_initial.py b/Planteer/plants/migrations/0001_initial.py
new file mode 100644
index 0000000..dd345c4
--- /dev/null
+++ b/Planteer/plants/migrations/0001_initial.py
@@ -0,0 +1,27 @@
+# Generated by Django 6.0.4 on 2026-04-19 18:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Plant',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=1024)),
+ ('about', models.TextField()),
+ ('used_for', models.TextField()),
+ ('image', models.ImageField(upload_to='images/')),
+ ('category', models.CharField(choices=[('tree', 'Tree'), ('fruit', 'Fruit'), ('vegetable', 'Vegetable'), ('flower', 'Flower'), ('herb', 'Herb')], max_length=500)),
+ ('is_edible', models.BooleanField(default=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0002_comment.py b/Planteer/plants/migrations/0002_comment.py
new file mode 100644
index 0000000..04a6b23
--- /dev/null
+++ b/Planteer/plants/migrations/0002_comment.py
@@ -0,0 +1,24 @@
+# Generated by Django 6.0.4 on 2026-04-20 22:41
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Comment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=512)),
+ ('content', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('plant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='plants.plant')),
+ ],
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0003_country_plant_countries.py b/Planteer/plants/migrations/0003_country_plant_countries.py
new file mode 100644
index 0000000..69e6aac
--- /dev/null
+++ b/Planteer/plants/migrations/0003_country_plant_countries.py
@@ -0,0 +1,26 @@
+# Generated by Django 6.0.4 on 2026-04-22 08:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0002_comment'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Country',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=512, unique=True)),
+ ('flag', models.CharField(max_length=128)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='plant',
+ name='countries',
+ field=models.ManyToManyField(to='plants.country'),
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0004_remove_comment_name_comment_user.py b/Planteer/plants/migrations/0004_remove_comment_name_comment_user.py
new file mode 100644
index 0000000..2b5f6af
--- /dev/null
+++ b/Planteer/plants/migrations/0004_remove_comment_name_comment_user.py
@@ -0,0 +1,26 @@
+# Generated by Django 6.0.4 on 2026-04-27 21:51
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0003_country_plant_countries'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='comment',
+ name='name',
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='user',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ preserve_default=False,
+ ),
+ ]
diff --git a/Planteer/plants/migrations/__init__.py b/Planteer/plants/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/plants/models.py b/Planteer/plants/models.py
new file mode 100644
index 0000000..f9f3f8c
--- /dev/null
+++ b/Planteer/plants/models.py
@@ -0,0 +1,42 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+# Create your models here.
+
+#main app
+
+class Country (models.Model):
+
+ name= models.CharField(max_length=512 ,unique=True)
+ flag= models.CharField(max_length=128)
+
+ def __str__(self):
+ return self.name
+
+class Plant (models.Model):
+
+ class Category (models.TextChoices):
+ TREE = 'tree' , 'Tree'
+ FRUIT = 'fruit' , 'Fruit'
+ VEGETABLE = 'vegetable' , 'Vegetable'
+ FLOWER = 'flower' , 'Flower'
+ HERB = 'herb' , 'Herb'
+
+
+
+
+ name = models.CharField(max_length=1024)
+ about = models.TextField()
+ used_for = models.TextField()
+ image = models.ImageField(upload_to="images/")
+ category= models.CharField(max_length=500,choices=Category.choices)
+ is_edible = models.BooleanField(default=True)
+ countries = models.ManyToManyField(Country)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+
+class Comment (models.Model):
+ plant= models.ForeignKey(Plant,on_delete=models.CASCADE)
+ user=models.ForeignKey(User, on_delete=models.CASCADE)
+ content=models.TextField()
+ created_at=models.DateTimeField(auto_now_add=True)
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/all_plants.html b/Planteer/plants/templates/plants/all_plants.html
new file mode 100644
index 0000000..74615c1
--- /dev/null
+++ b/Planteer/plants/templates/plants/all_plants.html
@@ -0,0 +1,60 @@
+{% extends 'main/base.html' %}
+
+{% block title %}All Plants{% endblock %}
+
+{% block content %}
+
+
+
+
All Plants
+
+
+
+
+
+ {% for plant in plants %}
+
+
+

+
+
{{ plant.category }}
+
{{ plant.name }}
+
{{ plant.about|truncatechars:80 }}
+
View Details
+
+
+
+ {% empty %}
+
No plants found.
+ {% endfor %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/new_plant.html b/Planteer/plants/templates/plants/new_plant.html
new file mode 100644
index 0000000..295c5f9
--- /dev/null
+++ b/Planteer/plants/templates/plants/new_plant.html
@@ -0,0 +1,68 @@
+{% extends 'main/base.html' %}
+
+{% block title %}Add New Plant{% endblock %}
+
+{% block content %}
+
+Add New Plant
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/plant_detail.html b/Planteer/plants/templates/plants/plant_detail.html
new file mode 100644
index 0000000..7551ba8
--- /dev/null
+++ b/Planteer/plants/templates/plants/plant_detail.html
@@ -0,0 +1,109 @@
+{% extends 'main/base.html' %}
+
+{% block title %}{{ plant.name }}{% endblock %}
+
+{% block content %}
+
+
+
+
+

+
+
+
+
+
{{ plant.category }}
+
{{ plant.name }}
+
{{ plant.about }}
+
+
Used For:
+
{{ plant.used_for }}
+
Edible: {% if plant.is_edible %} Yes {% else %} No {% endif %}
+
+
Native to:
+
+ {% for country in plant.countries.all %}
+
{{ country.name }}
+ {% empty %}
+
No countries added yet.
+ {% endfor %}
+
+
+
+ {% if request.user.is_staff %}
+
Update
+
+ {% endif %}
+
+
+
+
+
+
+Comments
+
+{% if request.user.is_authenticated %}
+
+{% else %}
+
+{% endif %}
+
+
+ {% for comment in comments %}
+
+
{{ comment.user.username }}
+
{{ comment.content }}
+
{{ comment.created_at|date:"Y-m-d" }}
+
+ {% empty %}
+
No comments yet. Be the first!
+ {% endfor %}
+
+
+
+
+Related Plants
+
+ {% for related in related_plants %}
+
+
+

+
+
{{ related.category }}
+
{{ related.name }}
+
{{ related.about|truncatechars:80 }}
+
View Details
+
+
+
+ {% empty %}
+
No related plants found.
+ {% endfor %}
+
+
+
+
+
+
+
+
+ Are you sure you want to delete {{ plant.name }}?
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/search.html b/Planteer/plants/templates/plants/search.html
new file mode 100644
index 0000000..4f3f72c
--- /dev/null
+++ b/Planteer/plants/templates/plants/search.html
@@ -0,0 +1,39 @@
+{% extends 'main/base.html' %}
+
+{% block title %}Search Plants{% endblock %}
+
+{% block content %}
+
+Search Plants
+
+
+
+
+
+{% if plants %}
+Results for: "{{ request.GET.search }}"
+
+ {% for plant in plants %}
+
+
+

+
+
{{ plant.category }}
+
{{ plant.name }}
+
{{ plant.about|truncatechars:80 }}
+
View Details
+
+
+
+ {% endfor %}
+
+
+{% elif request.GET.search %}
+No results found for "{{ request.GET.search }}"
+
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/update_plant.html b/Planteer/plants/templates/plants/update_plant.html
new file mode 100644
index 0000000..452fbe4
--- /dev/null
+++ b/Planteer/plants/templates/plants/update_plant.html
@@ -0,0 +1,74 @@
+{% extends 'main/base.html' %}
+
+{% block title %}Update {{ plant.name }}{% endblock %}
+
+{% block content %}
+
+Update {{ plant.name }}
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/tests.py b/Planteer/plants/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/Planteer/plants/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/Planteer/plants/urls.py b/Planteer/plants/urls.py
new file mode 100644
index 0000000..faea55d
--- /dev/null
+++ b/Planteer/plants/urls.py
@@ -0,0 +1,15 @@
+from django.urls import path
+from . import views
+
+app_name="plants"
+
+urlpatterns=[
+
+ path("all/", views.all_view , name="all_view"),
+ path("new/", views.new_view , name= "new_view"),
+ path("/detail/", views.detail_view, name= "detail_view"),
+ path("/update/", views.update_view, name= "update_view"),
+ path("/delete/", views.delete_view, name="delete_view"),
+ path("search/", views.search_view, name="search_view"),
+ path("comment/add/",views.add_comment_view,name="add_comment_view"),
+]
\ No newline at end of file
diff --git a/Planteer/plants/views.py b/Planteer/plants/views.py
new file mode 100644
index 0000000..e6a8d83
--- /dev/null
+++ b/Planteer/plants/views.py
@@ -0,0 +1,118 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest,HttpResponse
+from .models import Plant , Comment , Country
+from .forms import PlantForm
+
+# Create your views here.
+
+def all_view(request: HttpRequest):
+ plants = Plant.objects.all()
+
+ if "category" in request.GET and request.GET["category"] != "":
+ plants = plants.filter(category=request.GET["category"])
+
+ if "is_edible" in request.GET and request.GET["is_edible"] != "":
+ plants = plants.filter(is_edible=request.GET["is_edible"])
+
+ if "country" in request.GET and request.GET["country"] != "":
+ plants = plants.filter(countries__id=request.GET["country"])
+
+ return render(request, "plants/all_plants.html", {
+ "plants": plants,
+ "categories": Plant.Category.choices,
+ "countries": Country.objects.all()
+
+ })
+
+# def new_view(request: HttpRequest):
+# form = PlantForm()
+
+# if request.method == "POST":
+# form = PlantForm(request.POST, request.FILES)
+# if form.is_valid():
+# form.save()
+# return redirect("plants:all_view")
+
+# return render(request, "plants/new_plant.html", {
+# "form": form,
+# "countries":Country.objects.all()
+# })
+def new_view(request: HttpRequest):
+ if not request.user.is_staff:
+ return redirect("main:home_view")
+
+ form = PlantForm()
+ if request.method == "POST":
+ form = PlantForm(request.POST, request.FILES)
+ if form.is_valid():
+ plant = form.save()
+ plant.countries.set(request.POST.getlist("countries"))
+ return redirect("plants:all_view")
+
+ return render(request, "plants/new_plant.html", {
+ "form": form,
+ "countries": Country.objects.all()
+ })
+
+def detail_view(request: HttpRequest, plant_id: int):
+ plant = Plant.objects.get(pk=plant_id)
+ comments=Comment.objects.filter(plant=plant)
+
+ related_plants = Plant.objects.filter(category=plant.category).exclude(pk=plant_id)[:3]
+ return render(request, "plants/plant_detail.html", {
+ "plant": plant,
+ "comments": comments,
+ "related_plants": related_plants
+ })
+
+
+
+def update_view(request: HttpRequest, plant_id: int):
+ if not request.user.is_staff:
+ return redirect("main:home_view")
+
+ plant = Plant.objects.get(pk=plant_id)
+ form = PlantForm(instance=plant)
+
+ if request.method == "POST":
+ form = PlantForm(request.POST, request.FILES, instance=plant)
+ if form.is_valid():
+ plant=form.save()
+ plant.countries.set(request.POST.getlist("countries"))
+ return redirect("plants:detail_view", plant_id=plant.id)
+
+ return render(request, "plants/update_plant.html", {
+ "form": form,
+ "plant": plant,
+ "countries":Country.objects.all()
+ })
+
+
+
+# def delete_view(request: HttpRequest, plant_id: int):
+# return render(request, "plants/plant_detail.html")
+def delete_view(request:HttpRequest, plant_id:int):
+ if not request.user.is_staff:
+ return redirect("main:home_view")
+
+ plant = Plant.objects.get(pk=plant_id)
+ plant.delete()
+ return redirect("main:home_view")
+
+def search_view(request: HttpRequest):
+
+ # the user have to search with more than 2 letters 3 and on...
+ if "search" in request.GET and len(request.GET["search"]) >= 3:
+ plants = Plant.objects.filter(name__contains=request.GET["search"])
+
+ else:
+ plants = []
+ return render(request, "plants/search.html", {"plants": plants})
+
+def add_comment_view(request:HttpRequest,plant_id:int):
+ if request.method== "POST":
+ plant_ob = Plant.objects.get(pk=plant_id)
+ new_comment=Comment(plant=plant_ob,user=request.user,content=request.POST['content'])
+ new_comment.save()
+
+ return redirect ("plants:detail_view", plant_id=plant_id)
\ No newline at end of file