تجربة انشاء متجر الكتروني

السلام عليكم.

كيف بدأت القصة؟

منذ فترة تواصلت مع أحد الأشخاص و أخبرني بأنه يريد موقعاً ليعرض عليه منتجاته

تناقشنا بخصوص الموقع وأتفقنا على المميزات و المدة مع التكلفة :handshake:

جمع المتطلبات

أخبرنا العميل بأنه يريد

  • أضافة فئة (مع أمكانية تعديل - حذف)
  • أضافة منتج (مع أمكانية تعديل - حذف)
  • أضافة طلبية (بواسطة الزبون مرفقة مع المنتج المطلوب و معلومات الزبون)

التفكير وتصميم ال models المطلوبة

  • مصدر الصورة: maxdemarzidotcom

قبل أن نبدأ، أستعملت Python مع اطار عمل جانقو لإنجاز المشروع…

قمت بأضافة ال models اللازمة في ملف models.py داخل تطبيق store

class Category(models.Model):
    title = models.CharField(max_length=150)
    slug = models.SlugField(max_length=100, blank=True, null=True)

    def __str__(self):
        return self.title




class Product(models.Model):

    category = models.ForeignKey('Category', on_delete=models.CASCADE)
    title = models.CharField(max_length=150)
    price = models.IntegerField(default=170)
    img = models.ImageField(upload_to = 'images', blank=True, null=True)

    def __str__(self):
        return self.title



class Order(models.Model):

    product = models.ForeignKey('Product', on_delete=models.CASCADE)
    quantity = models.IntegerField(default=1)
    full_name = models.CharField(max_length=150)
    phonenumber = models.CharField(max_length=10)
    accept = models.BooleanField(default=False)
    color = models.CharField(max_length=150, blank=True, null=True)
    size = models.CharField(max_length=150, blank=True, null=True)
    date = models.DateTimeField(auto_now_add=True)
    

    def __str__(self):
        return self.full_name + ' ' + self.product.title

بعد ذلك قمنا بتسجيل ال model في ملف admin.py بنفس مسار models.py
هكذا:

from store.models import Category, Product, Order

admin.site.register(Category)
admin.site.register(Product)
admin.site.register(Order)

قمنا بعمل حساب SuperUser لنجرب حذف و أضافة المنتجات عن طريق الامر:

python manage.py createsuperuser

ثم Run Server

قمت بالتجربة وجدت كل شيء يعمل :smile:

تخصيص لوحة تحكم سهلة للعميل

العميل لا يملك اي خبرة في مجال الحاسوب ولغته الإنجليزية ضعيفة ويريد لوحة تحكم عربية بسيطة تقوم بتنفيذ ما يحتاجه من اضافة وتعديل منتجات

فالحل بتخصيص forms خاصة باضافة منتجات وفئات

قمنا بإنساء ملف أسميناه forms.py بنفس مسار models.py في التطبيق
و قمنا بإنشاء form خاص لكل من الفئة والمنتج بهذا الشكل:


class CategoryForm(forms.ModelForm):

    class Meta:
        model = Category
        fields = ('title', )

        labels = {
            'title': _('أسم الفئة'),        
        }

class ProductForm(forms.ModelForm):

    class Meta:
        model = Product
        fields = ('category', 'title', 'price', 'img', )

        labels = {
            'category': _('النوع '),
            'title': _('أسم المنتج'),  
            'price': _(' سعر المنتج'),
            'img': _('صورة المنتج'),
       }

ثم في ملف views.py أستدعينا ال forms التي قمنا بأنشائها
هكذا:

from .forms import ProductForm, CategoryForm

و إنشأنا ال view الخاصة بـ إنشاء فئة ومنتج
هكذا:

def home_view(request):

    title = 'الرئيسية'
    categories = Category.objects.all()
    query_list = Product.objects.all()
    return render(request, 'home.html', {'products': query_list, 'categories': categories }) 


def control_add_category(request):
    title = 'أصافة فئة'
    if request.method == 'POST':
        categoryForm = CategoryForm(request.POST or None)
        if categoryForm.is_valid():
            categoryForm.save()
            return redirect('home')
    else:
        categoryForm = CategoryForm()
    context = {
        'title': title,
        'form': categoryForm,
    }
    return render(request, 'control-add-category.html', context)


def control_add_product(request):
    title = 'أضافة منتج'
    if request.method == 'POST':
        productForm = ProductForm(request.POST or None)
        if productForm.is_valid():
            productForm.save()
            return redirect('home')
    else:
        productForm = ProductForm()
    context = {
        'title': title,
        'form': productForm,
    }
    return render(request, 'control/control-add-category.html', context)

ثم نقوم بإنشاء ملفات templates اللازمة:

أضغط هنا لمشاهدة الكود home.htm
    {% extends "base.html" %}

        {% block content %}

        {% for category in categories %}
           <p>{{ category }}</p>
        {% endfor %}

        {% for product in products %}
           <p>{{ product }}</p>
        {% endfor %}

    {% endblock%}
أضغط هنا لمشاهدة الكود control-add-category.html
    {% extends "base.html" %}

        {% block content %}

         <form method="post" enctype="multipart/form-data ">

              {% csrf_token %}              
              {{ form }}
              <input type="submit" name="submit" value="شراء" />
           </form>
        {% endblock%}
أضغط هنا لمشاهدة الكود control-add-product.html
    {% extends "base.html" %}

        {% block content %}

         <form method="post" enctype="multipart/form-data ">

              {% csrf_token %}              
              {{ form }}
              <input type="submit" name="submit" value="شراء" />
           </form>
        {% endblock%}

ثم نذهب لملف urls.py

لنقوم بإضافة روابط أضافة منتج و فئة والصفحة الرئيسية
بهذا الشكل:

from django.urls import path
from . import views


urlpatterns = [
    path('', views.home_view, name='home_view'),
    path('add-category/', views.control_add_category, name='new_category'),
    path('add-product/', views.control_add_product, name='new_product'),

]

أصبحنا الان قادرين على اضافة منتج وفئة بسهولة من خلال form خاصة و معربة وبتنسيقات خاصة بنا :star_struck:

خيارات اضافية

أخبرنا العميل لاحقاَ بأنه يريد اضافة خصائص معينة للمنتجات مثلاَ الالوان المتوفرة والقياسات.
أفضل طريقة لأضافة هذه الخيارات هي ان تكون منفصلة عن model Product
قمنا بأضافة model أسمه Variation يحتوي على الاختلافات لكل منتج على حدى
وكل Variation مرتبط مع المنتج

بهذا الشكل:



VAR_CATEGORIES = [
    ('color', 'اللون'),
    ('size', 'القياس'),

]
class Variation(models.Model):
    product = models.ForeignKey('Product', on_delete=models.CASCADE)
    category = models.CharField(max_length=150, choices=VAR_CATEGORIES) #أختيار لون أو قياس
    value = models.CharField(max_length=150) #قيمة اللون أو القياس مثال:( أخضر - XXL - M)

    objects = VariationManager()

    def __str__(self):
        return self.attr


class VariationManager(models.Manager):
    def colors(self):
        return super(VariationManager, self).filter(category='color')

    def sizes(self):
        return super(VariationManager, self).filter(category='size')

سجلنا الmodel الجديد في ملف admin.py

admin.site.register(Variation)

نقوم بتجربة أضافة Variation جديد لاحد المنتجات
ثم نجري تعديل بسيط لاظهار Variations داخل ملف home.htm
بهذا الشكل

...
{% for product in products %}
    <p>{{ product }}</p>
    {% if product.variation_set.all  %}
        {% if product.variation_set.colors %} #هنا يأتي دور VariationManager لفلترة الالوان
              <p>ألوان المنتج</p>
             {% for color in product.variation_set.colors %}  
                  <p>{{ color.value }}</p>
             {% endfor %}
        {% endif %}

        {% if product.variation_set.sizes %} #هنا يأتي دور VariationManager لفلترة القياسات
              <p>قياسات المنتج</p>
             {% for size in product.variation_set.sizes %}
                 <p>{{ size.value }}</p>
             {% endfor %}
        {% endif %}
    {% endif %}
{% endfor %}

لكن هناك شيئ مهم يحتاجه العميل يريد أضافة البوم للصور لكل منتج على حدى وكل منتج يحتوي على عدد غير معروف من الصور
بنفس طريقة أضافة Variations متعددة نقوم بأضافة model جديد أسمه PhotoِِِAlbum مرتبط بـ Product

بهذا الشكل:


class PhotoِِِAlbum(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.FileField(upload_to='product/images')

    def __str__(self):
        return self.product.title + ' صورة '

ونسجل ال model الجديد في admin

الان نستطيع أضافة صور لكل منتج
لكننا نريد أضافة عدد غير معلوم من الصور دفعة واحدة من خلال form أضافة منتج جديد مباشرةَ

لدينا عدة تغييرات سنجريها ومنها أضافة حقل ألبوم الصور لـ form الخاصة بأضافة منتج
سنقوم بأضافة حقل مخصص من داخل control-add-product.html
هكذا:

...
    {% extends "base.html" %}

        {% block content %}

         <form method="post" enctype="multipart/form-data ">

              {% csrf_token %}              
              {{ form }}
              <hr>
              <p>ألبوم الصور*</p>

              <label for="images">حمل صور الألبوم</label>
              <input type="file" name="images" id="images" hidden multiple>
              <br>
             <hr>
             <input type="submit" name="submit" value="شراء" />
           </form>
        {% endblock%}

لأضافة حقل ادخال متعدد الملفات في html نستخدم multiple Attribute

الان نريد استقبال المنتج و البوم الصور ونعمل حلقة تكرارية لحفظ الصور لنفس المنتج

نجري تعديلاتنا على views.py للتعامل مع الملفات بهذا الشكل:

...
def control_add_product(request):
    title = 'أضافة منتج'
    if request.method == 'POST':
        productForm = ProductForm(request.POST or None)
        if productForm.is_valid():
            productForm.save()
            instance_product = productForm.save() #أستنساخ للمنتج القادم من request.POST fu] pt/i

            for file in request.FILES.getlist('images'): #لأجل كل الملفات القادمة من حقل ألبوم الصور 
                instance = Image(product=get_object_or_404(Product,pk=instance_product.id),image=file) #استنساخ الصورة الحالية من قائمة الصور القادمة requset.POST 
                instance.save() #ثم حفطها في قواعد البيانات
            return redirect('home')
    else:
        productForm = ProductForm()
    context = {
        'title': title,
        'form': productForm,
    }
    return render(request, 'control/control-add-category.html', context)

ثم نظهر الصور لكل منتج في home.htm بهذا الشكل:

...
        {% if product.PhotoِِِAlbum_set.all %}
              <p>صور المنتج</p>
             {% for image in product.PhotoِِِAlbum_set.all %}  
                  <p>{{ image }}</p>
             {% endfor %}
        {% endif %}

الان نستطيع أضافة منتج مع البوم الصور و نستطيع اضافة فئة وأضافة متغيرات للمنتج كاللون والقياس

إطلاق الموقع على استضافة Heroku المجانية (Deploy)

*مصدر الصورة: wikimedia

بعد انشاء حساب على heroku

نقوم بتجهيز المشروع للرفع:

  1. أنشاء ملف Procfile لأخبار السيرفر من أين يبدأبتشغيل التطبيق ونكتب فيه السطر التالي
web: gunicorn YourProjectName.wsgi --log-file -
  1. أضافة ملف runtime.txt ونضيف له المكتبات اللازمة في المشروع عن طريق الامر التالي
pip install gunicorn dj-database-url whitenoise psycopg2
#ثم نضيفها لملف  requirements.txt
pip freeze > requirements.txt

المفروض ان يحتوي ملف requirements.txt على التالي:


dj-database-url==0.5.0
Django==2.2.2
gunicorn==19.9.0
Pillow==6.0.0
psycopg2==2.8.3
pytz==2019.1
whitenoise==4.1.2

تضيف الاسطر التالية في اسفل ملف setting.py :

import dj_database_url 
prod_db  =  dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(prod_db)

ثم من CMD نقوم بتسجيل الدخول الى حسابنا على heroku

heroku create YourProjectName

Creating  YourProjectName... done
https://YourProjectName.herokuapp.com/ | https://git.heroku.com/YourProjectName.git

نضيف الdomin الى ALLOWED_HOSTS في settings.py.

ALLOWED_HOSTS = ['YourProjectName.herokuapp.com']

نقوم الان بانشاء مستودع جديد عن طريق الامر

git init

ثم نتحكم في الستودع الخاص بنا على heroku من خلال الأمر التالي

heroku git:remote -a YourProjectName

ثم نضيف التغيرات للمستودع

git add .
git commit -m "Initial commit"

ثم نعمل رفع من خلال

 git push heroku master

ثم نعمل Migrate لقواعد البيانات

heroku run python manage.py migrate

نفحص الرابط https://YourProjectName.herokuapp.com/
كل شيء يعمل

لكن سياسة heroku تقوم بحذف الصور بعد 30 دقيقة لتجاوز ذلك نحتاج لااستضافة خاصة بالصور
هناك خدمة مجانية من AWS اسمها S3
بعد عمل حساب جديد على AWS
نذهب الى قائمة Services نختار S3
ثم نضغط Create bucket
نختار اسم للـ bucket ثم next next
ثم نذهب للقائمة المنسدلة عند أسمك في اعلى الصفحة على اليمين ونختار My Security Credentials
ثم نختار Access keys (access key ID and secret access key)
ونضغط على Create New Access Key
نحفظ كل من AWSAccessKeyId و AWSSecretKey
نبد أبأضافة الاعدادات داخل المشروع
نقوم بتثبيت المكتبات التالية boto3, django-storages من خلال الامر:

pip install boto3 django-storages

لا ننسى ان نضيفم لملف requirements.txt من خلال الامر التالي:

pip freeze > requirements.txt

ثم نذهب لملف setting.py ونضيف التالي:

import boto3, django-storages


INSTALLED_APP = [
	...,
	'storages'
]

#وفي اسفل الملف نضيف الاتي:

AWS_ACCESS_KEY_ID = 'your AWSAccessKeyId'
AWS_SECRET_ACCESS_KEY = 'your AWSSecretKey'
AWS_STORAGE_BUCKET_NAME = 'your bucket name'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
AWS_DEFAULT_ACL = 'public-read'

نقوم بضبط المتغيرات على heroku من خلال الاوامر التالية:

heroku config:set AWS_ACCESS_KEY_ID=yourAWSAccessKeyId AWS_SECRET_ACCESS_KEY=yourAWSSecretKey
heroku config:set S3_BUCKET_NAME=your bucket name

أو من لوحة تحكم heroku :

ثم نحفظ التعديلات في المستودع ونرفعها:

git add .
git commit -m "add S3 AWS Service"
git push heroku master

نجرب القيام برفع صور جديدة لتجربته
و نقوم بمشاهدة الصور المرفوعة من خلال رابط خدمة S3 AWS ثم تذهب الى الـ bucket الخاصة بك وتفتحها ستجد مجلدأ جديدا يحتوي على الصور التي قمت برفعها :sunglasses:

طبعاَ الموقع احتاج للمزيد من التطوير كصفحة تسجيل الدخول و صفحة لوحة التحكم لم اقم بذكر هذه التفاصيل كي لا اطيل الموضوع اكثر ربما سيتم ذكرها بمواضيع أخرى

الشكر للأستاذ @YaserAlnajjar ساعدني في كل شيء جزاه الله خيراً

13 Likes

اصرارك على انجاز المشروع في الوقت المطلوب يستحق كل الاحترام…

اهنئك يا سامر على هذا الانجاز الرائع :muscle:

5 Likes

شكراً استاذي تراكم النصائح منكم له دور كبير
وبصراحة العمل معكم يزيد الثقة بالنفس يكسر حاجز الخوف من المشاريع العملية أو الفعلية :wink:

4 Likes

برافو جداااا ممكن اشوف المشروع

4 Likes

لو سمحت كنت عايز افهم الجزئيه ديCapture

2 Likes

اكيد لكن ننتظر حل مشكلة الدفع لدى AWS عند الانتهاء سأقوم برفعه هنا

1 Like

Manger هو مدير يتعامل مباشرة من كود model مع قواعد البيانات
في حالتي هذه عرفته في المتغيير
object داخل model

ثم انشأته واستخدمت فيه دالة colors and sizes
بحيث عند طلب احد الدالتين تقوم بالتعامل مع قواعد البيانات وجلب المطلوب

**وانتظر احد الخبرات يشرح اكثر مني **
انا شرحت لك الاستخدام

باختصار هو للتعامل مع قواعد البيانت من خلال models

1 Like

انجاز رائع اهنئك … ان شاء الله مزيد من النجاح

2 Likes

جميل جدا اهنئك على هذا الانجاز

2 Likes

الرابط:
www.sadinstore.com

طبعاً لم ينشر منتجاته بعد لكن هناك منتجان للتجريب

2 Likes

اهنئك اخي سامر على هذا الانجاز :star_struck:

2 Likes

ما شاء الله! ثالث مشروع ecommerce من الأكاديمية
استمر!

2 Likes

الله يبارك اهنئك على عملك الرائع :fire::fire:

2 Likes

جزاك الله خيرا على الموضوع فهو مرجعي كلما اردت اطلاق مشروع

3 Likes

مشكلة هيروكو في عدم حفظ الملفات لدي فكرة بس مامتاكد هل بتنجح او لا جرب سوي API واربطه بالموقع مع استخدام قواعد البيانات MongoDB ال API يكون بسيرفر مجاني بمكان اخر فلنفترض ابسط مثال بيانات او صور او ملفات سيتم ارسالها في موقعك والموقع عن طريق السيرفر وباستخدام الرابط سيرسلها الى قواعد البيانات

2 Likes

عمل جبار وشرح مفصل بوركت جهودك

2 Likes

@YaserAlnajjar
هل يمكنك ان توضح ما هذه العملية لم افهمها.

2 Likes

و إياكم يارب الفضل كله للأستاذ @YaserAlnajjar
وسعيد جداً كونه اصبح مرجع بسيط هذا يشجعني لكتابة المزيد كل التوفيق لك @asmaa_salih

2 Likes

شكراً لك :heart:

1 Like

الفكرة ان للمنتج الواحد ربما سيكون هناك ١٠ الوان او اكثر وبالنسبة للقياسات نفس الشيء

فالأفضل ولجعل الامر يبدو بصورة هندسية اكثر هذا المودل يقوم بإنشاء عناصر مرتبطة بالمنتج
هذه العناصر تقدر تعملها الوان وتعطيها قيمة مثال

Color_red =  Variation(Category=“اللون",value=“red”)

Size_XL =  Variation(Category="القياس",value=“XL”)



هنا قمنا بإنشاء عنصرين واحد قياس والثاني لون

عند طلب المنتج تقوم بطلب جميع العناصر المرتبطة به من حقل
Variation في قواعد البيانات وتقوم بفلترتها الالوان بقائمة عناصر
والقياسات بقائمة عناصر

ثم تطبعها للمستخدم في صفحة شراء المنتج ليستطيع اختيار لون وقياس يناسبه

لماذا جعلتها عناصر قابلة للتعديل والإنشاء

احيانا يكون عندك منتج من لون واحد واحياناً اكثر

وبنفس الوقت لنفرض انه لديك t-shirt لون احمر
تقوم باستدعاء المنتج ثم تستدعي Variation الذي يحمل قيمة اللون الاحمر وتستطيع ان تعرف كم قطعة حمراء لديك اذا قمت بتعديل مودل variation واضفت له خاصية جديدة وهي العدد
فيصبح هكذا
-المنتج المرتبط به
-اسم الاختلاف لون او قياس
-قيمته xl او اخضر مثلاً
-عدد القطع المتوفرة ربما ٣

حينها تستطيع التلاعب ومعرفة كم قطعة تيشيرت احمر لديك متبقية
يعني يكون شغلك فيها خيارات اكثر

إن شاء الله يكون وضحت فكرته

2 Likes