اذهب إلى المحتوى

Mustafa Suleiman

الأعضاء
  • المساهمات

    19761
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • عدد الأيام التي تصدر بها

    472

كل منشورات العضو Mustafa Suleiman

  1. آلية الإختبار هي كالتالي: بعد إنهاء 4 مسارات من الدورة على الأقل حيث سيتم إختبارك في المسارات التي أنهيتها فقط، أو الدورة بالكامل عليك رفع المشاريع التي قمت بها بالدورة على حسابك في github، ثم التحدث لمركز المساعدة وإخبارهم أنك تريد التقدم للإختبار وتوفير روابط المشاريع على github. ثم الإنتظار لبعض الوقت لحين مراجعة المشاريع وسيتم الرد عليك، وتحديد موعد لإجراء مقابلة، وبها يتم: إجراء محادثة صوتيّة لمدة 30 دقيقة يطرح المدرّب عليك أسئلة متعلّقة بالدورة والأمور التي نفّذتها خلالها. يحدد لك المدرّب مشروعًا مرتبطًا بما قمت به أثناء الدورة لتنفيذه خلال فترة محددة تتراوح بين أسبوع إلى أسبوعين. إجراء محادثة صوتيّة أخرى لمدّة 30 دقيقة يناقش بها مشروعك وما نفذته وتطرح أسئلة خلالها. إن سارت جميع الخطوات السابقة بشكل صحيح، تحصل على الشهادة أو يرشدك المدرّب لأماكن القصور ويطلب منك تداركها ثم التواصل معنا من جديد. وبالطبع الأفضل دراسة الدورة بشكل كامل أو المسارات التي تريدها في مسارك البرمجي وليس الإكتفاء بـ 4 مسارات فقط، ففي النهاية أنت تريد تحقيق استفادة وتعلم المهارات اللازمة لدخول سوق العمل.
  2. ستجد أسفل فيديو الدرس في نهاية الصفحة صندوق تعليقات كما هنا، أرجو طرح الأسئلة أسفل الدرس وليس هنا في قسم أسئلة البرمجة حيث نطرح الأسئلة العامة الغير متعلقة بمحتوى الدورة أو الدرس، وذلك لمساعدتك بشكل أفضل.
  3. رفع مشاريع على المنصة ليس بتلك الصعوبة، ستحتاج أولاً إلى إنشاء حساب من خلال الرابط: https://github.com/signup بعد تسجيل الدخول عليك تثبيت git على حاسوبك: https://github.com/git-for-windows/git/releases/download/v2.50.0.windows.2/Git-2.50.0.2-64-bit.exe بعد إنتهاء التثبيت، توجه إلى مشروعك عن طريق منفذ الأوامر أو قم بتشغيل التيرمنال من خلال vscode وسيتم فتحه في مسار مجلد المشروع، وعليك تنفيذ الأوامر التالية لرفع المشروع: git init // ثم git add README.md // ثم git commit -m "first commit" // ثم git branch -M main // ثم git remote add origin https://github.com/ اسم المستخدم/repoName.git // ثم git push -u origin main ستجد تفصيل هنا:
  4. قمت بتهيئة تطبيق فلاسك مرتين، فهنا تقوم بإنشاء التطبيق وتحديد المسار الرئيسي له: app = Flask(__name__) @app.route('/') def hello_world(): return '<h1>Hello World!</h1>' ولكن بعد ذلك مباشرةً، تقوم بإنشاء نسخة جديدة تمامًا من التطبيق وتُسندها إلى نفس المتغير app: app = Flask(__name__) #هنا api = Api(app) بالتالي يتم مسح أو استبدال النسخة الأولى من التطبيق، والذي يتم تشغيله في النهاية app.run() هو النسخة الثانية التي لا تحتوي إلا على المسارات /predict و /health، ولكنها لا تعلم أي شيء عن المسار الرئيسي / عليك حذف التالي: app = Flask(__name__) #هنا
  5. المشكلة هي werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'register' وتعني أن دالة url_for() لم تتمكن من العثور على مسار باسم register. الفكرة هو أنه عند استخدام Blueprints، يتم إضافة اسم الـ Blueprint كبادئة لاسم المسار، لذا بدلاً من أن يكون اسم المسار register فقط، يصبح auth.register أي يجب تعديل السطر الموجود في ملف login.html ليصبح كالتالي: <a href="{{ url_for('auth.register' if 'login' in request.path else 'auth.login') }}"> تسجيل</a>
  6. عليك تثبيت بايثون على النظام لديك، من خلال الرابط التالي: https://www.python.org/ftp/python/3.12.8/python-3.12.8-amd64.exe وستجد هنا طريقة التثبيت: ثم قم بإغلاق محرر الأكواد وأعد تشغيله ليتم التعرف على أمر python
  7. لا مشكلة في تدريس الأساسيات النظرية أولاً أي التعلم العميق والشبكات العصبية، ثم الانتقال إلى البنى المتقدمة المبنية عليها كالـ Transformers. لكن الفكرة من إعادة الترتيب هو إتباع منهجية حديثة وهي من الأعلى إلى الأسفل أو التطبيق أولاً، وذلك بدلاً من المنهجية التقليدية من الأسفل إلى الأعلى أو النظرية أولاً. وذلك لتعلم كيفية قيادة السيارة وتحقيق بها نتائج ملموسة تطبيقات المحولات، ثم بعد أن يدرك الطلبة أهميتها وقوتها، سيتعلمون كيف يعمل محركها الداخلي أي التعلم العميق. بمعنى المحولات هي نوع من الشبكات العصبية، لكن استخدامها كأداة لا يتطلب بالضرورة فهم كل تفاصيلها النظرية المعقدة مسبقًا، تمامًا كما أنك تستخدم هاتفك الذكي كل يوم دون الحاجة لمعرفة تصميم أشباه الموصلات في معالجه. لذا الترتيب الجديد غرضه، تعليمك المهارات التطبيقية الأكثر طلبًا أولاً، وإبقائك متحفزة عبر بناء مشاريع حقيقية مبكرًا، وتأهيلك بشكل عملي لفهم النظرية لاحقًا بشكل أسهل وأكثر جدوى.
  8. المشكلة لديك هي ValueError: X has 13 features, but StandardScaler is expecting 15 features as input بسبب عدم تطابق عدد السمات التي يتوقعها الـ scaler حيث تم تدريبه على 5 سمات رقمية محددة فقط. أي عندما تستدعي الدالة scaler.transform(df)، يتوقع scaler أن تكون df لها نفس عدد الأعمدة 15 وبنفس الترتيب الذي تعلمه، لكن تمرر له DataFrame (df) يحتوي على 13 عمودًا فقط، وذلك يؤدي إلى الـ ValueError. بالتالي عليك محاكاة نفس خطوات المعالجة المسبقة التي تمت في الـ Notebook داخل دالة التنبؤ في Flask، من خلال استقبال الـ 13 ميزة من المستخدم. ثم إنشاء DataFrame من المدخلات، وافصله إلى جزأين: df_numerical: DataFrame يحتوي على الميزات الرقمية الخمس. df_categorical: DataFrame يحتوي على الميزات الفئوية الثمانية. ويجب أن تكون قد قمت بحفظ كائن الـ One-Hot Encoder في ملف مثل encoder.joblib وقم بتحميله، ثم استخدم الكائن المحمل لتحويل df_categorical، وسينتج عن ذلك مصفوفة NumPy أو DataFrame جديد يحتوي على 10 أعمدة. ثم قم بدمج بيانات الميزات الرقمية الخمس الأصلية مع بيانات الميزات الفئوية المحولة أي الـ 10 أعمدة، ويجب أن يكون ترتيب الأعمدة مطابق تمام للترتيب الذي تم استخدامه لتدريب الـ scaler. بعد ذلك تمرير المصفوفة الجديدة المكونة من 15 ميزة إلى scaler.transform()، ولن يحدث خطأ لأن الأبعاد متطابقة.
  9. حسب ما فهمت، ما تريده هو إعادة التوجيه بعد تسجيل الدخول أو التسجيل بتغيير الوجهة من لوحة التحكم dashboard إلى صفحة التنبؤ، وحفظ المعلومات في قاعدة البيانات أي نتائج التنبؤ يتم حفظها بشكل صحيح. أولاً في الملف الذي يحتوي على مسارات تسجيل الدخول والتسجيل، أي الدوال التي فوقها app.route('/login')@ و app.route('/register')@)، عليك تغيير الوسيط داخل دالة url_for() من نقطة نهاية لوحة التحكم إلى نقطة النهاية التي تعرض صفحة الـ HTML لنموذج التنبؤ، وذلك على افتراض أن لديك مسار يعرض صفحة التنبؤ، حيث ستقوم بتغيير أسطر دالة redirect والتي بداخلها url_for بالنسبة لقاعدة البيانات، فهل قمت بعمل تحديث في برنامج dbgate لقاعدة البيانات لرؤية الصفوف الجديدة؟ بمعنى عمل refresh، لكن لو حدث أي خطأ قبل تنفيذ db.session.commit()، فلن يتم حفظ البيانات، وستظهر رسالة الخطأ في منفذ الأوامر الخاص بخادم فلاسك. عامًة أنت بحاجة إلى دراسة أساسيات Flask:
  10. أين سلة المشتريات؟ حاول محاكاة ذلك، بمعنى سلة مشتريات عند الضغط عليها يتم عرض قائمة منسدلة توضح مُلخص مع زر للإنتقال لصفحة المشتريات لإتمام عملية الشراء. عند الضغط على زر order now حاول محاكاة عملية الإضافة للسلة، بزيادة عدد المنتجات فوق أيقونة السلة كما في المتاجر الحقيقية، أي عليك إنشاء state لذلك. حاول جعل التصاميم أقرب للواقع قدر الإمكان وليست مجرد تصاميم جامدة بدون وظيفة، عامًة أرى تحسن كبير في مستوى المشاريع، ممتاز، عليك الاستمرار في تنفيذ المشاريع مع زيادة مستوى الصعوبة والتعقيد تدريجيًا لحين الوصول للقدرة على على تنفيذ مشروع متجر إلكتروني يعمل بالكامل، وذلك من خلال رؤية شروحات في البداية ثم محاولة توظيف ما تعلمته في تحويل تصميم من الإنترنت لمشروع حقيقي، وستجد ذلك هنا: بعد ذلك تستطيع تعلم مكتبة خاصة بتأثيرات حركية عند التمرير في الصفحة نزولاً وصعودًا مثل AOS أو GSAP لكن قبل الاستمرار الأفضل دراسة أساسيات تصميم واجهة المستخدم، لتحسين مستوى التصميم في مشاريع وليس مجرد كتابة كود فقط بل عن علم بما تقوم به، فهيكل الصفحة والمساحات والخطوط لا يتم بشكل عشوائي، ستجد تفصيل هنا:
  11. بعد التثبيت ستحتاج إلى استيراد المكتبة ، وأفضل مكان لتهيئة المكتبة هو في المكون الرئيسي لمشروعك App.js أو أيًا يكن اسمه، بحيث يتم تشغيلها مرة واحدة فقط عند تحميل التطبيق. import { useEffect } from 'react'; import AOS from 'aos'; import 'aos/dist/aos.css'; ثم تهيئة المكتبة في App.js من خلال خُطاف useEffect: useEffect(() => { AOS.init({ duration: 1200, }); }, []); بعد ذلك كل ما عليك فعله هو إضافة السمة data-aos إلى أي عنصر JSX تريد تحريكه، وتستطيع اختيار نوع الحركة التي تريدها من موقع AOS الرسمي. <div className="section" data-aos="fade-up"> <h2> العنصر سيظهر بتأثير (fade-up)</h2> <p>أهلاً بالعالم</p> </div>
  12. UX يعني تجربة المستخدم، وهو الشعور والإدراك العام الذي يتكون لدى الشخص أثناء استخدامه لمنتج أو تطبيق، أي هي لا تتعلق فقط بالمظهر الجميل، بل بكل جوانب التفاعل بين المستخدم والتطبيق، فالمظهر والتصميم خاص بجزئية الـ UI. الفكرة هي جعل الموقع أو التطبيق سهل الاستخدام قدر الإمكان بما يتناسب مع نوعية المستخدمين، وكذلك مفيد، مرغوب، سريع وسهل الوصول. والأمر بحاجة إلى تفصيل كثير ستجده هنا: بينما مكتبة Pop-up كالتالي مثلاً: https://www.npmjs.com/package/reactjs-popup فهي لإنشاء مكون النافذة المنبثقة أو ما يسمى أيضًا بـ Modal أو Dialog كالتالي: أي الـ Pop-up نافذة صغيرة تظهر فوق محتوى الصفحة الرئيسي لجذب انتباه المستخدم لأمر معين، لعرض رسالة تنبيه أو تأكيد، كهل أنت متأكد من الحذف؟ أو لعرض نموذج لإدخال بيانات كنموذج تسجيل الدخول، أو عرض معلومات إضافية أو صور بشكل أكبر.
  13. لو كانت كذلك لتم تسجيل الدخول، قم إذن بإعادة تعيينها بالضغط على رابط forgot your password
  14. بسبب أنك قمت بإدخال كلمة المرور الغير صحيحة أكثر من مرة، بالتالي سيتعين عليك الإنتظار لمدة 30 دقيقة أو ساعة، ثم تستطيع تجربة تسجيل الدخول مرة أخرى. الأفضل في حال نسيت كلمة المرور قم بالضغط على رابط forgot your password؟ لإعادة تعيين كلمة المرور.
  15. عليك تعطيل خاصية once فالإعداد الافتراضي لـ once في مكتبة AOS هو true، بمعنى الأنيميشن يحدث مرة واحدة فقط عند أول ظهور للعنصر، وعند التمرير مرة أخرى لا يحدث شيء، قم بتعديل الكود للتالي: useEffect(() => { AOS.init({ duration: 1000, once: false, }); }, []); و duration: 1000 ستجعل الحركة أبطأ وسلسة أكثر.
  16. هل لديك هاتف به Telegram مُسجل دخوله بالفعل؟ سيصلك عليه رقم التأكيد، حاول استخدام إصدار الويندوز وليس الويب ثم اختيار رقم الهاتف ليصل رسالة عليه في حال ليس لديك هاتف به تيليجرام يعمل بالفعل
  17. ستحتاج إلى تحديث نماذج المشروع وتحديد العلاقات المناسبة حيث كل بطاقة مرتبطة بحدث واحد، وكل بطاقة يمكن أن تكون مرتبطة بزائر واحد وكل زائر مرتبط بحدث. ثم عليك إنشاء حدث جديد وبطاقةوحدد عدد الدعوات، ثم إنشاء زوار بعدد الدعوات، واربطهم بالحدث، ثم بطاقة لكل زائر واربطها بالحدث والزائر. بعد ذلك أنشئ رابط فريد لكل زائر وأرسله له، وعند دخول الزائر للرابط، تحقق من الحدث والبطاقة الخاصة به. وذلك حتى يتمكن المشرف من تسجيل الدخول من /admin/login للقيام بالتالي: ينشئ حدث جديد يسجل دخول للحدث ينشئ بطاقة ويحدد عدد الدعوات يعرض الدعوات وينسخ الروابط أو يرسلها بالبريد الزوار يستخدمون الروابط للتسجيل يحصلون على بطاقة PDF مع QR code عند الحضور، يتم مسح QR code لتأكيد الحضور أي عليك تحديث models.py إلى: from flask_sqlalchemy import SQLAlchemy from datetime import datetime import secrets db = SQLAlchemy() class Admin(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) email = db.Column(db.String(100), unique=True, nullable=False) password = db.Column(db.String(200), nullable=False) class EVENT(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) email = db.Column(db.String(100), nullable=False) password = db.Column(db.String(100), nullable=False) event_time = db.Column(db.DateTime, nullable=False) cards = db.relationship('CARD', backref='event', lazy=True) invitations = db.relationship('Invitation', backref='event', lazy=True) class CARD(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) image_filename = db.Column(db.String(100)) message = db.Column(db.Text) card_no = db.Column(db.Integer, nullable=False) # عدد الدعوات event_time = db.Column(db.DateTime) created_at = db.Column(db.DateTime, default=datetime.utcnow) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) invitations = db.relationship('Invitation', backref='card', lazy=True) class Invitation(db.Model): id = db.Column(db.Integer, primary_key=True) unique_token = db.Column(db.String(100), unique=True, nullable=False) is_used = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) card_id = db.Column(db.Integer, db.ForeignKey('card.id'), nullable=False) visitor_id = db.Column(db.Integer, db.ForeignKey('visitor.id')) class Visitor(db.Model): id = db.Column(db.Integer, primary_key=True) full_name = db.Column(db.String(100), nullable=False) email = db.Column(db.String(100), nullable=False) phone = db.Column(db.String(14)) image_filename = db.Column(db.String(100)) qr_filename = db.Column(db.String(100)) qr_token = db.Column(db.String(100), unique=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) confirmed = db.Column(db.Boolean, default=False) attended = db.Column(db.Boolean, default=False) invitation = db.relationship('Invitation', backref='visitor', uselist=False) وتحديث ملف card.py لإضافة وظيفة إنشاء الروابط: from flask import ( session, Blueprint, flash, redirect, url_for, render_template, request, current_app, ) from helper import login_required_event from datetime import datetime import os import secrets from models import * bp = Blueprint("card", __name__, url_prefix="/event") @bp.route("/dashboard") @login_required_event def event_dashboard(): if "event_id" not in session: flash("يجب تسجيل الدخول أولاً", "warning") return redirect(url_for("auth.login")) event_id = session["event_id"] event = EVENT.query.get(event_id) cards = CARD.query.filter_by(event_id=event_id).all() return render_template("card/dashboard.html", event=event, cards=cards) @bp.route("/card_maker", methods=["POST", "GET"]) @login_required_event def card_maker(): if request.method == "POST": name = request.form["event_name"] message = request.form["message"] card_no = int(request.form["card_no"]) event_time = request.form["event_time"] photo = request.files["photo"] try: event_time = datetime.strptime(event_time, "%Y-%m-%d") except ValueError: flash("صيغة التاريخ غير صحيحة", "danger") return redirect(url_for("card.card_maker")) photo_filename = f"{datetime.now().timestamp()}_{photo.filename}" photo_filepath = os.path.join( current_app.config["UPLOAD_FOLDER"], photo_filename ) photo.save(photo_filepath) event_id = session["event_id"] card = CARD( name=name, image_filename=photo_filename, message=message, card_no=card_no, event_time=event_time, event_id=event_id ) db.session.add(card) db.session.commit() for i in range(card_no): invitation = Invitation( unique_token=secrets.token_urlsafe(32), event_id=event_id, card_id=card.id ) db.session.add(invitation) db.session.commit() flash(f"تم إنشاء البطاقة و {card_no} دعوة بنجاح!", "success") return redirect(url_for("card.card_show")) return render_template("card/card_maker.html") @bp.route("/card-show") @login_required_event def card_show(): event_id = session["event_id"] cards = CARD.query.filter_by(event_id=event_id).all() return render_template("card/card_show.html", cards=cards) @bp.route("/invitations/<int:card_id>") @login_required_event def show_invitations(card_id): card = CARD.query.get_or_404(card_id) if card.event_id != session["event_id"]: flash("ليس لديك صلاحية لعرض هذه الدعوات", "danger") return redirect(url_for("card.event_dashboard")) invitations = Invitation.query.filter_by(card_id=card_id).all() return render_template("card/invitations.html", card=card, invitations=invitations) @bp.route("/visitor-info") @login_required_event def visitor_info(): event_id = session["event_id"] visitors = db.session.query(Visitor).join( Invitation ).filter( Invitation.event_id == event_id, Invitation.visitor_id.isnot(None) ).all() return render_template("card/visitor_info.html", visitors=visitors) @bp.route("/qr-scanning") @login_required_event def qr_scanning(): return render_template("card/scanner.html") وتحديث ملف visitor.py لاستقبال الدعوات: from flask import ( Blueprint, redirect, url_for, render_template, request, flash, current_app, ) import os, qrcode, secrets from weasyprint import HTML from flask_mail import Mail, Message from models import * from datetime import datetime bp = Blueprint("visitor", __name__, url_prefix="/visitor") mail = Mail() @bp.route("/register/<token>", methods=["GET", "POST"]) def register(token): invitation = Invitation.query.filter_by(unique_token=token).first() if not invitation: flash("رابط الدعوة غير صالح", "danger") return redirect(url_for("index")) if invitation.is_used: flash("هذه الدعوة تم استخدامها مسبقاً", "warning") return redirect(url_for("index")) card = invitation.card event = invitation.event if request.method == "POST": full_name = request.form["full_name"] email = request.form["email"] phone = request.form["phone"] photo = request.files["photo"] visitor = Visitor( full_name=full_name, email=email, phone=phone ) if photo and photo.filename: photo_filename = f"{datetime.now().timestamp()}_{photo.filename}" photo_filepath = os.path.join( current_app.config["UPLOAD_FOLDER"], photo_filename ) photo.save(photo_filepath) visitor.image_filename = photo_filename qr_token = secrets.token_urlsafe(16) visitor.qr_token = qr_token db.session.add(visitor) db.session.commit() invitation.visitor_id = visitor.id invitation.is_used = True db.session.commit() confirm_url = url_for("visitor.confirm_attendance", token=qr_token, _external=True) qr_img = qrcode.make(confirm_url) qr_filename = f"qr_{visitor.id}.png" qr_path = os.path.join(current_app.config["UPLOAD_FOLDER"], qr_filename) qr_img.save(qr_path) visitor.qr_filename = qr_filename db.session.commit() pdf_path = generate_pdf(visitor, event, card, confirm_url) send_visitor_email(visitor, event, pdf_path) flash("تم التسجيل بنجاح! تحقق من بريدك الإلكتروني", "success") return redirect(url_for("index")) return render_template("visitor/register.html", event=event, card=card) @bp.route("/confirm_attendance/<token>") def confirm_attendance(token): visitor = Visitor.query.filter_by(qr_token=token).first() if visitor: visitor.attended = True db.session.commit() invitation = visitor.invitation event = invitation.event if invitation else None return render_template("visitor/confirmed.html", visitor=visitor, event=event) else: return "رمز غير صالح", 404 def generate_pdf(visitor, event, card, confirm_url): pdf_filename = f"invitation_{visitor.id}.pdf" pdf_path = os.path.join(current_app.config["UPLOAD_FOLDER"], pdf_filename) image_url = url_for( "static", filename=f"uploads/{visitor.image_filename}", _external=True ) if visitor.image_filename else None qr_url = url_for( "static", filename=f"uploads/{visitor.qr_filename}", _external=True ) if visitor.qr_filename else None card_image_url = url_for( "static", filename=f"uploads/{card.image_filename}", _external=True ) if card.image_filename else None html = render_template( "visitor/invitation_pdf.html", visitor=visitor, event=event, card=card, visitor_image=image_url, qr_image=qr_url, card_image=card_image_url, confirm_url=confirm_url ) base_url = request.host_url HTML(string=html, base_url=base_url).write_pdf(pdf_path) return pdf_path def send_visitor_email(visitor, event, pdf_path): msg = Message( f"دعوتك لحضور {event.name}", sender=current_app.config["MAIL_USERNAME"], recipients=[visitor.email] ) msg.body = f""" مرحباً {visitor.full_name}, يسعدنا دعوتك لحضور {event.name} في تاريخ {event.event_time.strftime('%Y-%m-%d')}. تجد بطاقة الدعوة مرفقة مع هذا البريد. مع أطيب التحيات """ with current_app.open_resource(pdf_path) as pdf: msg.attach(f"invitation_{visitor.id}.pdf", "application/pdf", pdf.read()) mail.send(msg) ثم إنشاء القوالب الجديدة templates/card/invitations.html {% extends "base.html" %} {% block title %}دعوات البطاقة{% endblock %} {% block content %} <div class="container mt-4"> <h2>دعوات البطاقة: {{ card.name }}</h2> <p>عدد الدعوات: {{ card.card_no }}</p> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>رابط الدعوة</th> <th>الحالة</th> <th>الزائر</th> <th>الإجراءات</th> </tr> </thead> <tbody> {% for invitation in invitations %} <tr> <td>{{ loop.index }}</td> <td> <input type="text" class="form-control form-control-sm" value="{{ url_for('visitor.register', token=invitation.unique_token, _external=True) }}" readonly> </td> <td> {% if invitation.is_used %} <span class="badge bg-success">مستخدمة</span> {% else %} <span class="badge bg-warning">غير مستخدمة</span> {% endif %} </td> <td> {% if invitation.visitor %} {{ invitation.visitor.full_name }} {% else %} - {% endif %} </td> <td> <button class="btn btn-sm btn-primary copy-btn" data-url="{{ url_for('visitor.register', token=invitation.unique_token, _external=True) }}"> نسخ الرابط </button> </td> </tr> {% endfor %} </tbody> </table> </div> <a href="{{ url_for('card.card_show') }}" class="btn btn-secondary">العودة</a> </div> <script> document.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', function() { const url = this.getAttribute('data-url'); navigator.clipboard.writeText(url).then(() => { this.textContent = 'تم النسخ!'; setTimeout(() => { this.textContent = 'نسخ الرابط'; }, 2000); }); }); }); </script> {% endblock %} و templates/visitor/register.html {% extends "base.html" %} {% block title %}التسجيل في الحدث{% endblock %} {% block content %} <div class="container mt-4"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card"> <div class="card-header bg-primary text-white"> <h4>التسجيل في {{ event.name }}</h4> </div> <div class="card-body"> <div class="text-center mb-4"> {% if card.image_filename %} <img src="{{ url_for('static', filename='uploads/' + card.image_filename) }}" alt="شعار الحدث" class="img-fluid" style="max-height: 200px;"> {% endif %} <h5 class="mt-3">{{ card.name }}</h5> <p>{{ card.message }}</p> <p><strong>التاريخ:</strong> {{ event.event_time.strftime('%Y-%m-%d') }}</p> </div> <form method="post" enctype="multipart/form-data"> <div class="mb-3"> <label for="full_name" class="form-label">الاسم الكامل</label> <input type="text" class="form-control" id="full_name" name="full_name" required> </div> <div class="mb-3"> <label for="email" class="form-label">البريد الإلكتروني</label> <input type="email" class="form-control" id="email" name="email" required> </div> <div class="mb-3"> <label for="phone" class="form-label">رقم الهاتف</label> <input type="tel" class="form-control" id="phone" name="phone" required> </div> <div class="mb-3"> <label for="photo" class="form-label">صورة شخصية (اختياري)</label> <input type="file" class="form-control" id="photo" name="photo" accept="image/*"> </div> <button type="submit" class="btn btn-primary w-100">تأكيد التسجيل</button> </form> </div> </div> </div> </div> </div> {% endblock %} و templates/visitor/invitation_pdf.html <!DOCTYPE html> <html lang="ar" dir="rtl"> <head> <meta charset="UTF-8"> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; text-align: center; background-color: #f5f5f5; } .invitation-card { background: white; border: 2px solid #gold; border-radius: 10px; padding: 30px; max-width: 600px; margin: 0 auto; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } .header img { max-width: 150px; margin-bottom: 20px; } .event-name { font-size: 28px; color: #333; margin: 20px 0; } .message { font-size: 18px; color: #666; margin: 20px 0; line-height: 1.6; } .visitor-info { background: #f9f9f9; padding: 15px; border-radius: 5px; margin: 20px 0; text-align: right; } .qr-code { margin: 30px 0; } .qr-code img { width: 200px; height: 200px; } .instructions { text-align: right; margin-top: 30px; padding: 20px; background: #f0f0f0; border-radius: 5px; } .instructions h4 { color: #333; margin-bottom: 15px; } .instructions ol { text-align: right; padding-right: 20px; } .footer { margin-top: 30px; color: #888; font-size: 14px; } </style> </head> <body> <div class="invitation-card"> <div class="header"> {% if card_image %} <img src="{{ card_image }}" alt="شعار الحدث"> {% endif %} </div> <h1 class="event-name">{{ event.name }}</h1> <div class="message"> {{ card.message }} </div> <div class="visitor-info"> <h3>بيانات الضيف</h3> <p><strong>الاسم:</strong> {{ visitor.full_name }}</p> <p><strong>البريد الإلكتروني:</strong> {{ visitor.email }}</p> <p><strong>رقم الهاتف:</strong> {{ visitor.phone }}</p> <p><strong>تاريخ الحدث:</strong> {{ event.event_time.strftime('%Y-%m-%d %H:%M') }}</p> </div> <div class="qr-code"> <h3>رمز الدخول QR</h3> {% if qr_image %} <img src="{{ qr_image }}" alt="QR Code"> {% endif %} <p>يُرجى إبراز هذا الرمز عند الدخول</p> </div> <div class="instructions"> <h4>تعليمات مهمة:</h4> <ol> <li>يُرجى الحضور قبل الموعد بـ 15 دقيقة</li> <li>هذه الدعوة شخصية ولا يمكن تحويلها</li> <li>يُرجى إحضار هذه البطاقة أو إظهارها على الهاتف</li> <li>الدعوة صالحة لشخص واحد فقط</li> </ol> </div> <div class="footer"> <p>نتطلع لرؤيتكم في الحدث</p> <p>{{ event.name }} &copy; 2025</p> </div> </div> </body> </html> بعد ذلك تحديث templates/card/card_show.html {% extends "base.html" %} {% block title %}عرض البطاقات{% endblock %} {% block content %} <div class="container mt-4"> <h2>البطاقات المُنشأة</h2> <div class="row"> {% for card in cards %} <div class="col-md-4 mb-4"> <div class="card"> {% if card.image_filename %} <img src="{{ url_for('static', filename='uploads/' + card.image_filename) }}" class="card-img-top" alt="صورة البطاقة" style="height: 200px; object-fit: cover;"> {% endif %} <div class="card-body"> <h5 class="card-title">{{ card.name }}</h5> <p class="card-text">{{ card.message[:100] }}...</p> وتحديث templates/card/card_show.html {% extends "base.html" %} {% block title %}عرض البطاقات{% endblock %} {% block content %} <div class="container mt-4"> <h2>البطاقات المُنشأة</h2> <div class="row"> {% for card in cards %} <div class="col-md-4 mb-4"> <div class="card"> {% if card.image_filename %} <img src="{{ url_for('static', filename='uploads/' + card.image_filename) }}" class="card-img-top" alt="صورة البطاقة" style="height: 200px; object-fit: cover;"> {% endif %} <div class="card-body"> <h5 class="card-title">{{ card.name }}</h5> <p class="card-text">{{ card.message[:100] }}...</p> <ul class="list-unstyled"> <li><strong>عدد الدعوات:</strong> {{ card.card_no }}</li> <li><strong>التاريخ:</strong> {{ card.event_time.strftime('%Y-%m-%d') }}</li> <li><strong>الدعوات المستخدمة:</strong> {{ card.invitations|selectattr('is_used')|list|length }} من {{ card.card_no }} </li> </ul> <a href="{{ url_for('card.show_invitations', card_id=card.id) }}" class="btn btn-primary">عرض الدعوات</a> </div> </div> </div> {% endfor %} </div> <a href="{{ url_for('card.event_dashboard') }}" class="btn btn-secondary">العودة للوحة التحكم</a> </div> {% endblock %} كذلك تحديث templates/card/visitor_info.html {% extends "base.html" %} {% block title %}معلومات الزوار{% endblock %} {% block content %} <div class="container mt-4"> <h2>الزوار المسجلين</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>الصورة</th> <th>الاسم</th> <th>البريد الإلكتروني</th> <th>الهاتف</th> <th>تاريخ التسجيل</th> <th>الحالة</th> <th>الحضور</th> </tr> </thead> <tbody> {% for visitor in visitors %} <tr> <td> {% if visitor.image_filename %} <img src="{{ url_for('static', filename='uploads/' + visitor.image_filename) }}" alt="صورة الزائر" style="width: 50px; height: 50px; object-fit: cover; border-radius: 50%;"> {% else %} <span class="text-muted">لا توجد صورة</span> {% endif %} </td> <td>{{ visitor.full_name }}</td> <td>{{ visitor.email }}</td> <td>{{ visitor.phone }}</td> <td>{{ visitor.created_at.strftime('%Y-%m-%d %H:%M') }}</td> <td> {% if visitor.confirmed %} <span class="badge bg-success">مؤكد</span> {% else %} <span class="badge bg-warning">غير مؤكد</span> {% endif %} </td> <td> {% if visitor.attended %} <span class="badge bg-success">حضر</span> {% else %} <span class="badge bg-secondary">لم يحضر</span> {% endif %} </td> </tr> {% endfor %} </tbody> </table> </div> {% if not visitors %} <p class="text-center text-muted mt-4">لا يوجد زوار مسجلين حتى الآن</p> {% endif %} <a href="{{ url_for('card.event_dashboard') }}" class="btn btn-secondary mt-3">العودة للوحة التحكم</a> </div> {% endblock %} وتحديث templates/card/dashboard.html {% extends "base.html" %} {% block title %}لوحة التحكم{% endblock %} {% block content %} <div class="container mt-4"> <h1>لوحة تحكم الحدث: {{ event.name }}</h1> <div class="row mt-4"> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <h5 class="card-title">البطاقات</h5> <p class="card-text display-4">{{ cards|length }}</p> <a href="{{ url_for('card.card_maker') }}" class="btn btn-primary">إنشاء بطاقة</a> </div> </div> </div> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <h5 class="card-title">إجمالي الدعوات</h5> <p class="card-text display-4">{{ cards|sum(attribute='card_no') }}</p> <a href="{{ url_for('card.card_show') }}" class="btn btn-info">عرض البطاقات</a> </div> </div> </div> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <h5 class="card-title">الزوار المسجلين</h5> <p class="card-text display-4">{{ event.visitors|length }}</p> <a href="{{ url_for('card.visitor_info') }}" class="btn btn-success">معلومات الزوار</a> </div> </div> </div> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <h5 class="card-title">ماسح QR</h5> <i class="fas fa-qrcode display-4"></i> <br><br> <a href="{{ url_for('card.qr_scanning') }}" class="btn btn-warning">فتح الماسح</a> </div> </div> </div> </div> <div class="mt-4"> <h3>معلومات الحدث</h3> <ul class="list-group"> <li class="list-group-item"><strong>اسم الحدث:</strong> {{ event.name }}</li> <li class="list-group-item"><strong>البريد الإلكتروني:</strong> {{ event.email }}</li> <li class="list-group-item"><strong>تاريخ الحدث:</strong> {{ event.event_time.strftime('%Y-%m-%d') }}</li> </ul> </div> <div class="mt-4"> <a href="{{ url_for('auth.logout') }}" class="btn btn-danger">تسجيل الخروج</a> </div> </div> {% endblock %} وستحتاج إلى تحديث helper.py لإضافة دالة مساعدة from models import * from flask import redirect, url_for, g, current_app, session import functools, os def login_required_event(func): @functools.wraps(func) def decorator_func(*args, **kwargs): if 'event_id' not in session: return redirect(url_for("auth.login")) # تحميل بيانات الحدث g.event = EVENT.query.get(session['event_id']) if g.event is None: session.clear() return redirect(url_for("auth.login")) return func(*args, **kwargs) return decorator_func def login_required_admin(view): @functools.wraps(view) def wrapped_view(**kwargs): if g.admin is None: return redirect(url_for("admin_login")) return view(**kwargs) return wrapped_view وإضافة مسار API لإرسال الدعوات في card.py @bp.route("/send_invitations/<int:card_id>", methods=["POST"]) @login_required_event def send_invitations(card_id): card = CARD.query.get_or_404(card_id) if card.event_id != session["event_id"]: return {"error": "غير مصرح"}, 403 emails = request.json.get("emails", []) invitations = Invitation.query.filter_by( card_id=card_id, is_used=False ).limit(len(emails)).all() if len(invitations) < len(emails): return {"error": "عدد الإيميلات أكبر من الدعوات المتاحة"}, 400 for email, invitation in zip(emails, invitations): send_invitation_email(email, invitation, card) return {"success": True, "sent": len(emails)} def send_invitation_email(email, invitation, card): event = card.event invitation_url = url_for('visitor.register', token=invitation.unique_token, _external=True) msg = Message( f"دعوة لحضور {event.name}", sender=current_app.config["MAIL_USERNAME"], recipients=[email] ) msg.html = f""" <div dir="rtl" style="font-family: Arial, sans-serif;"> <h2>دعوة خاصة</h2> <p>يسرنا دعوتكم لحضور {event.name}</p> <p><strong>التاريخ:</strong> {event.event_time.strftime('%Y-%m-%d')}</p> <p>{card.message}</p> <p> <a href="{invitation_url}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;"> التسجيل وتأكيد الحضور </a> </p> <p>نتطلع لرؤيتكم في الحدث</p> </div> """ mail.send(msg) وإنشاء صفحة لإرسال الدعوات templates/card/send_invitations.html {% extends "base.html" %} {% block title %}إرسال الدعوات{% endblock %} {% block content %} <div class="container mt-4"> <h2>إرسال دعوات: {{ card.name }}</h2> <div class="card"> <div class="card-body"> <h5>إدخال عناوين البريد الإلكتروني</h5> <p>أدخل عنوان بريد إلكتروني واحد في كل سطر</p> <form id="sendInvitationsForm"> <div class="mb-3"> <textarea class="form-control" id="emails" rows="10" placeholder="example1@email.com&#10;example2@email.com&#10;example3@email.com"></textarea> </div> <div class="alert alert-info"> <strong>ملاحظة:</strong> لديك {{ available_invitations }} دعوة غير مستخدمة </div> <button type="submit" class="btn btn-primary">إرسال الدعوات</button> <a href="{{ url_for('card.show_invitations', card_id=card.id) }}" class="btn btn-secondary">إلغاء</a> </form> </div> </div> </div> <script> document.getElementById('sendInvitationsForm').addEventListener('submit', async function(e) { e.preventDefault(); const emailsText = document.getElementById('emails').value; const emails = emailsText.split('\n').filter(email => email.trim() !== ''); if (emails.length === 0) { alert('يرجى إدخال عنوان بريد إلكتروني واحد على الأقل'); return; } try { const response = await fetch('{{ url_for("card.send_invitations", card_id=card.id) }}', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ emails: emails }) }); const data = await response.json(); if (response.ok) { alert(`تم إرسال ${data.sent} دعوة بنجاح!`); window.location.href = '{{ url_for("card.show_invitations", card_id=card.id) }}'; } else { alert(`خطأ: ${data.error}`); } } catch (error) { alert('حدث خطأ في إرسال الدعوات'); } }); </script> {% endblock %} ثم عليك حذف قاعدة البيانات القديمة event.db وإنشاء جديدة: python create_superuser.py ثم تشغيل المشروع.
  18. الفكرة ليست في مكتبة أو إطار، بل في التوقيت والفترة التي صدرت بها React، فوقتها لم يكن هناك مكتبة أو إطار يوفر أداء ومميزات وتجربة جيدة للمطورين مثلها. ففي الفترة ما بين 2010 و 2012، كانت تطبيقات الويب تزداد تعقيدًا، وواجهت فيسبوك مشكلة حقيقية في إدارة واجهتها المعقدة، خاصة مع الإشعارات والرسائل التي تحتاج إلى تحديث مستمر، والطرق التقليدية مثل jQuery تؤدي إلى ما يسمى بـ spaghetti code أو كود معقد ومتشابك يصعب تطويره. وظهرت أطر عمل مختلفة ومنها AngularJS لكنها كانت تأتي مع تعقيدات خاصة بها، كالربط ثنائي الاتجاه للبيانات والذي جعل تتبع الأخطاء صعبًا في التطبيقات الكبيرة. فقدمت React فلسفة جديدة وبسيطة وهي تدفق البيانات أحادي الاتجاه، أي البيانات تتدفق من المكون الأب إلى المكون الابن، وذلك يجعل تتبع مصدر التغييرات والأخطاء أسهل بكثير، وحلت مشكلة حقيقية للمبرمجين في ذلك الوقت. والتعامل المباشر مع DOM المتصفح بطيء، وفكرة وجود نسخة افتراضية في الذاكرة ومقارنة التغييرات وتحديث الأجزاء الضرورية فقط كانت ثورية، مما أعطى تطبيقات رياكت دفعة أداء هائلة مقارنة بمنافسيها في ذلك الوقت. ثم إصدار React Native في 2015 وتلك كانت اللحظة التي غيرت كل شيء، فقبل React Native، كان على الشركات توظيف فريقين منفصلين لتطوير تطبيقات الموبايل، أي فريق لـ iOS باستخدام Swift وObjective-C وفريق لـ Android باستخدام Java وKotlin مؤخرًا. وجاءت React Native لتوظيف نفس مفاهيم رياكت لبناء تطبيقات موبايل حقيقية Native تعمل على النظامين. بالتالي لعدّة سنوات لحين صدور Vue.js وتطور Angular.js ظلت React هي المتربعة على عرش أفضل تقنية لتطوير واجهة المستخدم، وحظيت بدعم شركة فيسبوك المطورة لها، وتكون حولها نظام بيئي Ecosystem هائل.
  19. المُلخصات الخاصة بالدروس غير متاحة للحفاظ على محتوى الدورة من النشر، لكن يوجد موسوعة حسوب وبها تفصيل أو مُلخص لكل ما تريد في بايثون: https://wiki.hsoub.com/Python ويوجد مقالات ودروس خاصة ببايثون على أكاديمية حسوب. كذلك يوجد كتاب لتعلم لغة بايثون بالأكاديمية. أيضًا تستطيع البحث على جوجل عن Python Cheatsheet وستجد مُلخصات لكن بالإنجليزية. بالنسبة للأسئلة أرجو السؤال أسفل الدرس الذي تريد أسئلة حوله أو أسئلة عن الدروس الماضية حتى ذلك الدرس وسيتم توفيرها لك.
  20. أرجو توضيح ما هي الدورة المقصودة؟
  21. بتثبيت المكتبة من خلال الأمر: npm install react-scroll ثم استيرادها في المكون الذي تريده وليكن App.js أو مكون الشريط العلوي Navbar.js، عليك باستيراد المكونات الرئيسية من المكتبة Link و Element. حيث Link هو المكون الذي ستضغط عليه لبدء التنقل مثل رابط في القائمة، بينما Element هو المكون الذي يلتف حول القسم الذي تريد الانتقال إليه أي الوجهة. import { Link, Element } from 'react-scroll'; وكمثال بسيط: import React from 'react'; import { Link, Element } from 'react-scroll'; import './App.css'; function App() { return ( <div className="App"> <nav className="navbar"> <Link to="section1" smooth={true} duration={500} className="nav-link"> القسم الأول </Link> <Link to="section2" smooth={true} duration={500} className="nav-link"> القسم الثاني </Link> <Link to="section3" smooth={true} duration={500} className="nav-link"> القسم الثالث </Link> </nav> <Element name="section1" className="section"> <h1>القسم الأول</h1> </Element> <Element name="section2" className="section bg-grey"> <h1>القسم الثاني</h1> </Element> <Element name="section3" className="section"> <h1>القسم الثالث</h1> </Element> </div> ); } export default App;
  22. قم بتجربة تسجيل الدخول من خلال البرنامج الخاص بالحاسوب ثم تسجيل الدخول على الهاتف ومن المفترض أن يصلك رقم التأكيد على حساب تيليجرام الخاص بك أو رسالة على الهاتف: https://desktop.telegram.org/
  23. قم بتشغيل المشروع من خلال الأمر: npm run dev -- --host
  24. ليس من خلال الهاتف، بل توجه للحاسوب الخاص بك وقم بتشغيل المشروع، وسيظهر لك عنوان ip في منفذ الأوامر استخدمه لتصفح المشروع من خلال الهاتف، وأبقي الخادم يعمل على الحاسوب
  25. العنوان localhost الذي تستخدمه ذلك خاص بالخادم المحلي على حاسوبك أي من الجهاز الذي يعمل عليه الخادم الخاص بالمشروع، بينما لو أردت تصفحه من الهاتف، ستجد عنوان مختلف للوصول للمشروع عبر الشبكة لديك، ابحث في منفذ الأوامر لديك عن رقم IP يبدأ بـ 192 ثم قم بكتابته كما هو في المتصفح لديك في الهاتف لعرض المشروع
×
×
  • أضف...