السلام عليكم.
كيف بدأت القصة؟
منذ فترة تواصلت مع أحد الأشخاص و أخبرني بأنه يريد موقعاً ليعرض عليه منتجاته
تناقشنا بخصوص الموقع وأتفقنا على المميزات و المدة مع التكلفة
جمع المتطلبات
أخبرنا العميل بأنه يريد
- أضافة فئة (مع أمكانية تعديل - حذف)
- أضافة منتج (مع أمكانية تعديل - حذف)
- أضافة طلبية (بواسطة الزبون مرفقة مع المنتج المطلوب و معلومات الزبون)
التفكير وتصميم ال 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
قمت بالتجربة وجدت كل شيء يعمل
تخصيص لوحة تحكم سهلة للعميل
العميل لا يملك اي خبرة في مجال الحاسوب ولغته الإنجليزية ضعيفة ويريد لوحة تحكم عربية بسيطة تقوم بتنفيذ ما يحتاجه من اضافة وتعديل منتجات
فالحل بتخصيص 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 خاصة و معربة وبتنسيقات خاصة بنا
خيارات اضافية
أخبرنا العميل لاحقاَ بأنه يريد اضافة خصائص معينة للمنتجات مثلاَ الالوان المتوفرة والقياسات.
أفضل طريقة لأضافة هذه الخيارات هي ان تكون منفصلة عن 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
نقوم بتجهيز المشروع للرفع:
- أنشاء ملف Procfile لأخبار السيرفر من أين يبدأبتشغيل التطبيق ونكتب فيه السطر التالي
web: gunicorn YourProjectName.wsgi --log-file -
- أضافة ملف 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 الخاصة بك وتفتحها ستجد مجلدأ جديدا يحتوي على الصور التي قمت برفعها
طبعاَ الموقع احتاج للمزيد من التطوير كصفحة تسجيل الدخول و صفحة لوحة التحكم لم اقم بذكر هذه التفاصيل كي لا اطيل الموضوع اكثر ربما سيتم ذكرها بمواضيع أخرى
الشكر للأستاذ @YaserAlnajjar ساعدني في كل شيء جزاه الله خيراً