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

Mustafa Suleiman

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

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

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

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

    476

إجابات الأسئلة

  1. إجابة Mustafa Suleiman سؤال في طريقة الربط باستخدام SQLAlchemy and flask كانت الإجابة المقبولة   
    ستحتاج إلى تحديث نماذج المشروع وتحديد العلاقات المناسبة حيث كل بطاقة مرتبطة بحدث واحد، وكل بطاقة يمكن أن تكون مرتبطة بزائر واحد  وكل زائر مرتبط بحدث.
    ثم عليك إنشاء حدث جديد وبطاقةوحدد عدد الدعوات، ثم إنشاء زوار بعدد الدعوات، واربطهم بالحدث، ثم بطاقة لكل زائر واربطها بالحدث والزائر.
    بعد ذلك أنشئ رابط فريد لكل زائر وأرسله له، وعند دخول الزائر للرابط، تحقق من الحدث والبطاقة الخاصة به.
    وذلك حتى يتمكن المشرف من تسجيل الدخول من /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 ثم تشغيل المشروع.
  2. إجابة Mustafa Suleiman سؤال في مشكلة عند نقل صورة إلى صفحة أخرى في Android Studio باستخدام جافا كانت الإجابة المقبولة   
    ربما حجم الصورة كبير، فالصو الملتقطة بالكاميرا أو المختارة من المعرض، دقتها عالية جداً مثلاً 4000x3000 بكسل، وعند تحميلها في الذاكرة كـ Bitmap، تستهلك مساحة كبيرة.
    أو تمرر الصورة كـ Bitmap كامل بين صفحة 2 وصفحة 3 عبر Intent Extras، فتواجه خطأ TransactionTooLargeException.
    لذا قبل إرسال الصورة إلى Gemini API، يجب تصغير حجمها وضغطها، فنماذج Gemini لا تحتاج إلى صور بدقة 4K للتعرف عليها، يكفي دقة 720p أو 1080p.
    وإليك مثال:
    import android.graphics.Bitmap import android.graphics.BitmapFactory import java.io.ByteArrayOutputStream fun resizeAndCompressImage(bitmap: Bitmap, maxDimension: Int = 1024, quality: Int = 80): Bitmap { val originalWidth = bitmap.width val originalHeight = bitmap.height var resizedWidth = originalWidth var resizedHeight = originalHeight // Resize the image (تصغير الأبعاد) if (originalHeight > maxDimension || originalWidth > maxDimension) { if (originalWidth > originalHeight) { resizedWidth = maxDimension resizedHeight = (resizedWidth * originalHeight / originalWidth.toFloat()).toInt() } else { resizedHeight = maxDimension resizedWidth = (resizedHeight * originalWidth / originalHeight.toFloat()).toInt() } } val resizedBitmap = Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false) // Compress the image (ضغط الجودة) val outputStream = ByteArrayOutputStream() resizedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream) val compressedBytes = outputStream.toByteArray() return BitmapFactory.decodeByteArray(compressedBytes, 0, compressedBytes.size) } val originalBitmap: Bitmap = // الصورة الأصلية val optimizedBitmap = resizeAndCompressImage(originalBitmap) كذلك يجب أن تتم كل عمليات معالجة الصورة والاتصال بالـ API في thread خلفي لتجنب تجميد واجهة المستخدم، وأفضل طريقة للقيام بذلك في أندرويد هي بواسطة Coroutines
    import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.ai.client.generativeai.GenerativeModel import com.google.ai.client.generativeai.type.content import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import android.graphics.Bitmap class ChatViewModel : ViewModel() { private val generativeModel: GenerativeModel = // تهيئة النموذج هنا fun analyzeImage(prompt: String, image: Bitmap) { viewModelScope.launch { try { val optimizedImage = withContext(Dispatchers.Default) { resizeAndCompressImage(image, maxDimension = 768, quality = 75) } val inputContent = content { image(optimizedImage) text(prompt) } val response = generativeModel.generateContent(inputContent) withContext(Dispatchers.Main) { _chatResponse.value = response.text } } catch (e: Exception) { withContext(Dispatchers.Main) { } } } } } ولا تقم بتمرير الـ Bitmap مباشرة، بل تمرير مسار URI للصورة كـ String من صفحة 1 إلى 2 ثم إلى 3، وفي صفحة 3، قم بقراءة الـ Bitmap من الـ URI ثم قم بمعالجته وإرساله.
    وبعد التصنيف في صفحة 1، قم بحفظ النسخة المصغرة في ذاكرة التخزين المؤقت للتطبيق  ومرر مسار الملف الجديد إلى الصفحات التالية.
    النموذج المحلي سيعمل على جهاز المستخدم للتصنيف الأولي، لكن مهمة الشات مع Gemini تستطيع نقلها إلى الخادم باستخدام Cloud Functions for Firebase والتي هي أساس Firebase AI Extensions و Genkit.
    أي في التطبيق سيقوم المستخدم بتصنيف الصورة باستخدام النموذج المحلي، وعند الانتقال لصفحة الشات، ثم يعمل التطبيق على رفع الصورة المُصغّرة إلى Cloud Storage for Firebase.
    وعند كتابة المستخدم لسؤال، سيقوم التطبيق بإرساله ورابط الصورة في Cloud Storage إلى Cloud Function.
    وفي الخادم Firebase Cloud Function سيتم تفعيل الـ Function عند استدعائها من التطبيق، سواء مكتوبة بـ Node.js وTypeScript أو Python لتستقبل السؤال ورابط الصورة.
    ثم تقوم باستدعاء Gemini API من الخادم وذلك أفضل من حيث الأمان، ثم تستقبل الرد من Gemini، ثم إرسال الرد مرة أخرى إلى التطبيق، إما مباشرة كرد على الاستدعاء أو عن طريق كتابته في Firestore أو Realtime Database حيث يستمع التطبيق للتحديثات.
  3. إجابة Mustafa Suleiman سؤال في حاولت ارفع المجلدات على Github بس لما رفعتهم وفتحتهم من GitHub بينو انهم فاضيين كانت الإجابة المقبولة   
    يجب أولاً إنشاء ملف باسم:
    .gitignore وذلك في مجلد المشروع الرئيسي، ووضع التالي به:
    .env .envrc .venv env/ venv/ ENV/ env.bak/ venv.bak/ .python-version Pipfile.lock .idea/ __pycache__/ # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST وذلك لتجنب رفع أية مجلدات أو ملفات لا علاقة لها بالكود مثل مجلد env الخاص بالبيئة الإفتراضية.
    ثم:
    git init لإنشاء مستودع محلي في مجلد المشروع الرئيسي.
    ثم تنفيذ الأوامر التالية بالترتيب في مجلد المشروع الرئيسي لرفع المشروع:
    git add . // ثم git commit -m "first commit" // ثم git branch -M main // ثم git remote add origin رابط المستودع على جيت هب هنا // ثم git push -u origin main لاحظي النقطة في  الأمر . git add  تعني إضافة جميع محتويات المجلد الرئيسي تمهيدًا لرفعها للمستودع البعيد على جيت هب.
     
  4. إجابة Mustafa Suleiman سؤال في مشكلة في عرض ملفات csv وipynb على جيت هب كانت الإجابة المقبولة   
    بالنسبة لملفات csv لعرضها يجب الضغط على رابط view raw
    بالنسبة لملفات jupyter فالمشكلة عند حفظك للملف على جهازك، تم تخزين معلومات عن الـ Widgets بطريقة قديمة أو غير مكتملة، ونظام العرض في GitHub يتوقع وجود معلومة محددة اسمها state أي الحالة لكل Widget، ولكنه لم يجدها.
    أسهل حل قومي بفتحها على حاسوبك من خلال VS Code، ثم من القائمة العلوية، اختاري Kernel ثم اختاري Restart and Clear Output لإعادة التشغيل ومسح المخرجات التي تظهر نتيجة تشغيل الخلايا.
    واحفظي الملف مرة أخرى، ورفع الملف الجديد إلى GitHub مرة أخرى:
    git add . git commit "clearing Jupyter Notebook output" git push  
  5. إجابة Mustafa Suleiman سؤال في بناء نموذج AI تنبؤي وربطه مع أندرويد ستوديو كانت الإجابة المقبولة   
    لا مشكلة في البيانات الاصطناعية كبداية، ويجب أن تكون منطقية تشبه البيانات التي تتوقع جمعها مستقبلاً قدر الإمكان، وبالطبع للتجربة ولن تعتمد على أرقام الدقة لاتخاذ قرارات نهائية، لأنك بنيت البيانات على افتراضاتك الخاصة، فالهدف منها هو فقط بناء النظام، وعندما تجمع بيانات حقيقية كافية، ستقوم بإعادة تدريب النموذج عليها.
    عامًة الـ Synthetic Data مفيدة لأنها تسمح ببناء وتجربة pipeline كامل للتعلم الآلي، من معالجة البيانات، إلى التدريب، إلى التكامل مع التطبيق، أيضًا تكتشف المشاكل التقنية في وقت مبكر.
    أول خطوة هي تحديد الـ features بالتفكير في العوامل التي تؤثر على المسافة التي يقطعها المستخدم، وهي:
    معلومات المستخدم: العمر، نوع الوظيفة (مندوب مبيعات، موظف مكتبي، يعمل من المنزل)، هل لديه عائلة. معلومات السيارة: سنة الصنع، نوع السيارة (صغيرة، SUV)، كفاءة الوقود. معلومات زمنية: يوم الأسبوع (1-7)، هل هو عطلة نهاية أسبوع (نعم/لا)، الشهر. معلومات سلوكية إن أمكن: مثل متوسط المسافة في الأسابيع السابقة، وتلك ميزة لها ثقل. ثم إنشاء صيغة وهمية لحساب المسافة، ولا يجب أن تكون مثالية، بل فقط لإنشاء بيانات ذات هيكل، ولتكن:
    distance = base_distance + job_effect + weekend_effect + previous_dist_effect + noise حيث base_distance  هي مسافة أساسية يومية، مثلاً 20 كم، وjob_effect  تعني تأثير نوع الوظيف، فمندوب مبيعات سيقطع 50كم أو يزيد، وموظف مكتبي +15 كم، يعمل من المنزل -10 كم.
    والـ weekend_effect خاص بالعطلة، حيث ستزيد المسافة للرحلات أو تقل لعدم الذهاب للعمل -15 كم.
    والـ noise خاصة بإضافة قيمة عشوائية صغيرة لتبدو البيانات واقعية أكثر.
    ثم توظيف ما سبق في الكود كالتالي:
    import pandas as pd import numpy as np num_samples = 5000 job_types = ['sales', 'office', 'remote', 'student'] data = { 'age': np.random.randint(18, 65, num_samples), 'car_model_year': np.random.randint(2010, 2024, num_samples), 'job_type': np.random.choice(job_types, num_samples), 'is_weekend': np.random.choice([0, 1], num_samples, p=[0.71, 0.29]), 'previous_week_distance': np.random.normal(loc=150, scale=50, size=num_samples) } df = pd.DataFrame(data) def calculate_distance(row): base_distance = 20 if row['job_type'] == 'sales': job_effect = 40 elif row['job_type'] == 'office': job_effect = 15 else: job_effect = -10 weekend_effect = -15 if not row['is_weekend'] else 5 previous_dist_effect = row['previous_week_distance'] * 0.5 noise = np.random.normal(0, 10) distance = base_distance + job_effect + weekend_effect + previous_dist_effect + noise return max(0, distance) df['weekly_distance'] = df.apply(calculate_distance, axis=1) print(df.head()) df.to_csv('synthetic_car_data.csv', index=False) وبخصوص:
    الخوارزمية لا بأس بها كنقطة بداية، لكونها سهلة الفهم والتنفيذ، والتدريب من خلال لا يتطلب موارد كبيرة، وبسهولة تستطيع معرفة كيف تؤثر كل ميزة مثل العمر على التنبؤ.
    بالتالي سرعة في التأكد من أن كل شيء يعمل من تدفق البيانات إلى التكامل قبل الانتقال إلى نماذج معقدة أكثر.
    وبالطبع على المدى الطويل ستحتاج إلى خوارزميات أفضل، فسلوك القيادة في الواقع أكثر تعقيدًا من علاقة خطية بسيطة، لوجود تفاعلات بين الميزات وعلاقات غير خطية.
    فتأثير العمر ربما لا يكون خطيًا، فقد يقود الشباب وكبار السن لمسافات أقل من الأشخاص في منتصف العمر، ومندوب المبيعات في عطلة نهاية الأسبوع ربما يقود مسافة مختلفة تمامًا عن مندوب المبيعات خلال أيام العمل.
    لذا ستحتاج إلى LightGBM أو XGBoost أو Random Forest، وذلك عند توفر بيانات عدة آلاف من المستخدمين على مدى بضعة أشهر، ثم قارن النتائج.
    لكن لو التنبؤ يعتمد بشكل كبير على تسلسل المسافات السابقة، أي التنبؤ بمسافة الأسبوع الحالي بناءًا على آخر 10 أسابيع، فستحتاج إلى LSTM.
    من خلال  TensorFlow Lite، وهناك طريقتين الأولى لو استخدمت scikit-learn، ستحتاج إلى أداة لتحويله إلى صيغة متوافقة مثل ONNX ثم إلى TFLite أو إعادة تدريب النموذج باستخدام TensorFlow أو Keras وهو الأسهل.
    ثم أنشئ مجلد assets في مجلد app/src/main/ في مشروع أندرويد ستوديو، وانسخ ملف model.tflite إلى المجلد، وفي ملف build.gradle (Module: app)، أضف مكتبة TensorFlow Lite.
    وستحتاج إلى تحميل النموذج من مجلد assets واستخدام Interpreter لتشغيله، من خلال كتابة كود Java أو Kotlin
  6. إجابة Mustafa Suleiman سؤال في تصميم تقويم من خلال Lovable كانت الإجابة المقبولة   
    عليك بتوجيه الأداة لاستخدام Intl.DateTimeFormat API المتاحة في المتصفح، وبمثال بسيط ستكتب السكريبت التالي:
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="dual-calendar-container"> <p class="gregorian-date"></p> <p class="hijri-date"></p> </div> <script> document.addEventListener('DOMContentLoaded', function() { const today = new Date(); const gregorianOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; const gregorianDateFormatter = new Intl.DateTimeFormat('ar-EG', gregorianOptions); const gregorianDateString = gregorianDateFormatter.format(today); const hijriOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }; const hijriDateFormatter = new Intl.DateTimeFormat('ar-SA-u-ca-islamic-umalqura', hijriOptions); const hijriDateString = hijriDateFormatter.format(today); const container = document.getElementById('dual-calendar-container'); if (container) { container.querySelector('.gregorian-date').textContent = `الميلادي: ${gregorianDateString}`; container.querySelector('.hijri-date').textContent = `الهجري: ${hijriDateString}`; } }); </script> </body> </html>  
  7. إجابة Mustafa Suleiman سؤال في الخطأ في التوثيق كانت الإجابة المقبولة   
    الدورة ليس لها علاقة بالتوثيق، فهي متاحة لك مدى الحياة، وتستطيع تعديل الاسم.
     
  8. إجابة Mustafa Suleiman سؤال في مُلخص للنقاط الأساسية التي تم تناولها خلال دورة تطوير واجهات المستخدم للاستعداد للإختبار كانت الإجابة المقبولة   
    تلخيص كامل محتوى الدورة أمر صعب، ذلك كان يجب أن يتم أثناء دراستك لها بكتابة النقاط الهامة في الدروس لتكون مرجع لك، وبشكل عام غرض الدورة بشكل أساسي  تزويدك بالمهارات اللازمة لإنشاء واجهات مستخدم تفاعلية، جذابة، ومتجاوبة لمختلف أنواع المواقع والتطبيقات. 
    لديك موسوعة حسوب كمرجع لك، وأيضًا مقالات البرمجة التي بالأكاديمية.
    وبشكل عام، حجر الأساس هي لغة HTML والتي من خلالها نبني هيكل المحتوى، حيث يجب أن تكون قادر على بناء هيكل سليم ومنطقي لصفحات الويب، وتذكر أهمية العناصر الدلالية Semantic HTML لتنظيم المحتوى وتحسين قابلية الوصول ومحركات البحث، وفكر في كيف استخدمت <div>, <nav>, <header>, <footer>, <article>, <section> وغيرها في المشاريع المختلفة.
    ثم CSS، حيث درسنا الأساسيات وهي المحددات، خصائص التنسيق (ألوان، خطوط، هوامش، مسافات داخلية)، ونموذج الصندوق Box Model.
    ثم التخطيط Layout وفهمك لـ Flexbox و Grid ضروري لبناء تخطيطات معقدة ومتجاوبة، وتذكر كيف استخدمتها في بناء الصفحات الرئيسية، القوائم، وتوزيع العناصر.
    ثم Sass لمعالجة التنسيقات المتقدمة، وتم تقديمه في مشروع المتجر الإلكتروني، مما يعني أنك يجب أن تفهم فوائده مثل المتغيرات، التضمين، المكسنات والوراثة وغيرهم، وكيف يساعد في كتابة CSS أكثر تنظيمًا، خاصة في المشاريع الكبيرة.
    ثم أساسيات JS ودرسنا المتغيرات، الدوال، التحكم في DOM للوصول للعناصر وتعديلها، والتعامل مع الأحداث Events.
    ثم درسنا jQuery لتسهيل كتابة كود JS، وعلى الرغم من أن الاتجاه الحديث يقلل من الاعتماد على تلك المكتبة، إلا أن الدورة غطتها لأنّ هناك مشاريع قائمة تعتمد عليها بالفعل في الواقع العملي، وتذكر كيف تُسهل jQuery تحديد العناصر، التلاعب بالـ DOM، وإضافة التأثيرات والتعامل مع AJAX.
    ثم انتقلنا للتصميم المتجاوب Responsive Design، وذلك مفهوم محوري في الدورة، حيث يجب أن تفهم كيف تجعل واجهاتك تعمل بشكل جيد على مختلف أحجام الشاشات (موبايل، تابلت، ديسكتوب)، وتقنيات مثل Media Queries، واستخدام وحدات القياس النسبية (%, vw, vh)، وتخطيطات Bootstrap المرنة هي كلمة السر لذلك وهو النظام الشبكي Grid System.
    للتذكير Bootstrap يوفر لك كلاسات مثل .col-sm-12 للشاشات الصغيرة، .col-md-6 للشاشات المتوسطة، .col-lg-4 للشاشات الكبيرة لتحقيق ذلك بسهولة بدون كتابة Media Queries معقدة بنفسك لكل عنصر.
    أي كلاسات sm, md, lg هي الخاصة بمقاسات الشاشات.
    وللعلم نظام الشبكة يجب استيعابه، أي فهم كيفية تقسيم الصفحة إلى صفوف وأعمدة container, row, col-* فهو أساس بناء تخطيطات متجاوبة بسرعة.
    أيضًا المكونات الجاهزة من أزرار، قوائم تنقل، بطاقات، نماذج، إلخ، تذكر كيف استخدمتها لتوفير الوقت والجهد وضمان مظهر متناسق.
    والترقية إلى Bootstrap 5، عليك استيعاب الفروقات الأساسية وكيفية الانتقال بين الإصدارات.
    ثم قمنا بدراسة Hugo وهو إطار مولد للمواقع الساكنة static، واستخدمناه لبناء مواقع ثابتة لشركة، شخصية، مدونة بسيطة.
    ومن ميزاته السرعة، الأمان، سهولة النشر، وعليك استيعاب معمارية القوالب Templates به وفهم كيفية استخدام وتعديل القوالب في Hugo.
    ثم درسنا إظهار بيانات من API داخل القالب في مشروع الموقع الإخباري، وذلك للربط بين عالم المواقع الساكنة والمحتوى الديناميكي، للتوضيح أن مواقع الويب هي أكثر من مجرد صفحات HTML ثابتة، وستحتاج إلى مراجعة مفهوم الـ API:
    والآن ما عليك فعله بعد المراجعة هو التطبيق العملي فهو الأهم حيث يجب تخصيص 60% من وقت الدراسة للتطبيق العملي ولا تشتت نفسك وتنجرف وراء المشاهدة والقراءة فقط وذلك يُعرف باسم Tutorial Hell، وبعد مدة زد النسبة إلى 70-80% الفكرة هي أن يكون الجزء الأكبر من وقتك مخصصًا للكتابة الفعلية للكود، بناء المشاريع، وحل المشكلات.
    تستطيع إعادة بناء مشاريع الدورة بنفسك من الصفر دون النظر إلى الحل إلا عند الضرورة القصوى ،ويا حبذا لو قمت بالتعديل عليها وإضافة ميزات جديدة.
    ثم البدء في مشاريع شخصية جديدة حتى لو كانت بسيطة، لتطبيق ما تعلمته بأفكار وتطبيقات مختلفة، أي حاول بناء صفحة بسيطة من الصفر باستخدام HTML, CSS, و JS.
    وجرب استخدام مكونات Bootstrap لإنشاء تخطيط سريع، وفكر في كيفية تطبيق مبادئ التصميم المتجاوب على أي واجهة تراها.
    بشكل مُتدرج ستحتاج إلى القيام بما تم ذكره هنا:
    قم ببناء جزء صغير من الموقع باستخدام html و CSS وأنصحك بتنفيذ التحديات على موقع Frontend mentor مع تحديد التمارين الخاصة بالمستوى المبتدئ newbie ثم  junior ثم تستطيع زيادة الصعوبة فيما بعد إلى intermediate، وإليك التمارين مباشرًة:
    https://www.frontendmentor.io/challenges?difficulty=1&type=free%2Cfree-plus اختر أي تمرين تراه مناسب ثم حاول تنفيذه، وفي حال واجهتك مشكلة تستطيع الاستفسار في قسم أسئلة البرمجة، ومع الوقت والإلتزام والصبر سيتحسن مستواك وتستطيع بعدها تنفيذ تصميم كامل.
    ثم التمارين التالية:
    ثم بعد ذلك تدرج في الصعوبة نحو إنشاء موقع بالكامل، ثم تدرج في الصعوبة نحو إنشاء موقع متعدد الصفحات.
    ثم الإنتقال لمرحلة تعلم الـ JS والتعمق بها من خلال المسار الأول من دورة جافاسكريبت هنا بالأكاديمية فهو مجاني لك كحال باقي المسارات الأولى من جميع الدورات، بعد أن تصبح قادرًا على رؤية تصميم وقادر على تنفيذه أي أنك قادر على بناء موقع بالكامل باستخدام HTML و CSS على الأقل التصاميم المتوسطة في الصعوبة، أي بعد إنشاء تصميمين كاملين.
    الإختبار سيكون كالتالي:
    إجراء محادثة صوتيّة لمدة 30 دقيقة يطرح المدرّب عليك أسئلة متعلّقة بالدورة والأمور التي نفّذتها خلالها. يحدد لك المدرّب مشروعًا مرتبطًا بما قمت به أثناء الدورة لتنفيذه خلال فترة محددة تتراوح بين أسبوع إلى أسبوعين. إجراء محادثة صوتيّة أخرى لمدّة 30 دقيقة يناقش بها مشروعك وما نفذته وتطرح أسئلة خلالها. إن سارت على جميع الخطوات السابقة بشكل صحيح، تحصل على الشهادة أو يرشدك المدرّب لأماكن القصور ويطلب منك تداركها ثم التواصل معنا من جديد.
  9. إجابة Mustafa Suleiman سؤال في هل أنا بحاجة إلى خبرة برمجية مُسبقة لدراسة الدورة كانت الإجابة المقبولة   
    محتوى الدورة تم إعداده ليبدأ معك من نقطة البداية في مجال البرمجة، وذلك بدراسة أساسيات البرمجة من خلال لغة بايثون وهي اللغة المُستخدمة في مجال الذكاء الاصطناعي وتحليل البيانات،  ثم خلال الدورة ستتعلم ما يلي:
    التعامل مع البيانات بمختلف الصيغ التعامل مع قواعد البيانات SQL و NoSQL التعامل مع نماذج الذكاء الاصطناعي LLMs مثل GPT من OpenAI و LLaMA من Meta تحليل البيانات واستخلاص المعلومات والتمثيل البياني لها التعامل مع مكتبات شهيرة مثل Pandas و Numpy و Matpoltlib و Seaborn تعلم الآلة Machine Learning الخورازميات الشهيرة في تعلم الآلة مثل الانحدار Regressions والتصنيف Classification والتجميع Clustering خوارزميات التعلم الخاضعة للإشراف Supervised learning وخوارزميات التعلم الغير خاضعة للإشراف Unsupervised learning وخوارزميات التعلم المعزز Reinforced learning دمج تقنيات الذكاء الاصطناعي مع متجر إلكتروني لو أردت تعلم الأساسيات في مجال البرمجة بشكل أفضل، فعليك دراسة دورة علوم الحاسوب قبل دورة الذكاء الاصطناعي، لكن الأمر ليس ضروري أو إلزامي، لكن في رأي ذلك أفضل لك.
  10. إجابة Mustafa Suleiman سؤال في التفكير الزائد عند تطوير مشروع كانت الإجابة المقبولة   
    لا تكن حبيس أفكارك، ما تقوم به غير عملي وغير واقعي، فحتى الشركات الكبيرة لا تقوم بما تفعله، أحيانًا كثيرة يتم نشر ميزة ليست كاملة وبها أخطاء ويتم إصلاحها بعد النشر.
    المهم هو إخراج نتيجة بجودة مقبولة ثم التحسين، فلو لاقت قبول ومفيدة فعلاً يتم تطويرها لتجنب إضاعة الوقت والمجهود في أمر لا عائد منه عند إختباره في الواقع.
    وعامًة المشكلة شائعة جداً جداً بين المبرمجين، خصوصاً في البدايات، ويسمى شلل التحليل Analysis Paralysis أو السعي للكمال المضر.
    اعتمد منهجية MVP وابدأ دائماً بأبسط نسخة تعمل وتحقق الهدف الأساسي المطلوب، ولديك الهدف الأساسي هو إدخال بيانات لتقرير واحد بطريقة ثابتة ربما اجعل ذلك يعمل أولاً، أي ركز على أن يعمل الكود بشكل صحيح مبدئياً، حتى لو كان غير أنيق أو غير قابل للتوسع بسهولة وبعد أن يعمل، تستطيع العودة لتحسينه وعمل Refactoring، وجعله يتعامل مع حالات أكثر تعقيداً.
    وبدلاً من التفكير في المشروع ككتلة واحدة ضخمة تتطلب حلاً مثالياً لكل شيء، قسّمه إلى مهام صغيرة جداً يمكن إنجازها في فترة قصيرة.
    وستواجهك مشكلة ماذا لو؟ فقم بكتابة ما تفكر به جانبياً وامضِ قدماً، بأي طريقة تريدها تحت عنوان تحسينات مستقبلية أو مشاكل محتملة، ثم انسَها في الوقت الحالي وركز على المهمة التي بين يديك وستتعامل مع ذلك لاحقاً عندما يحين وقتها.
  11. إجابة Mustafa Suleiman سؤال في مشكلة بسبب إضافة Code runner كانت الإجابة المقبولة   
    لا مشكلة في ذلك، عامًة لو أردت استخدم code runner لأنه يقوم بتنفيذ أمر cls، قم بإضافة التالي في ملف الإعدادات لتشغيله في التيرمنال كما تم التوضيح:
    "code-runner.runInTerminal": true,
  12. إجابة Mustafa Suleiman سؤال في laravel Starter Kits سؤال بخصوص كانت الإجابة المقبولة   
    محاولة جيدة وأعتقد أنك تعلمت منها بعض الأمور، عامًة في الإصدار 12 تم إتاحة Custom Community Laravel Starter Kits بمعنى  Starter Kits تم تطويرها من قبل مبرمجين آخرين في مجتمع لارافل، ولم تعد الخيارات محصورة في الـ Starter Kits المقدمة من قبل لارافل فقط.
    مع الوقت سيتم إتاحة الكثير من الـ Starter Kits المطورة من قبل مبرمجين آخرين، وحاليًا يوجد بالفعل مجموعة بدء تدعم RTL ها هي:
    https://github.com/AryanpAzadeh/RTL-blade-starter-kit الشرح الموجود في المستودع بالفارسية قم بترجمته من خلال جوجل ترجمة لتفهم المميزات المتاحة
  13. إجابة Mustafa Suleiman سؤال في كود لزر استئناف التشغيل في السي شارب كانت الإجابة المقبولة   
    من الأفضل محاولة تنفيذه لتحقيق استفادة، من خلال Visual Studio اختاري Create a new project وابحثي عن Windows Forms App (.NET Framework) أو Windows Forms App في حال تستخدمين .NET Core
    ثم اختاري اختر C# كلغة البرمجة، وتسمية المشروع واضغطي على Create.
    ستظهر لكِ نافذة تحتوي على نموذج فارغ Form، وهو الواجهة التي سنضيف إليها الزر، ثم في نافذة Solution Explorer على الجانب الأيمن، افتحي ملف Form1.cs.
    وفي وضع التصميم Design View، انقر يبزر الفأرة الأيمن على النموذج واختاري Properties وبها:
    غيّري Text إلى Resume Button Example ليظهر كعنوان النافذة ثم Size إلى 300 عرض × 200 ارتفاع ليكون حجم النافذة مناسبًا ثم StartPosition إلى CenterScreen لجعل النافذة تظهر في المنتصف وفي في شريط الأدوات Toolbox على الجانب الأيسر، ابحثي عن Button واسحبيه إلى النموذج، ثم ضعي الزر في مكان مناسب وليكن وسط النموذج تقريبًا.
    بعد ذلك ستكتبي الكود، انقري بزر الفأرة الأيمن على الزر في وضع التصميم واختاري View Code، أو افتحي ملف Form1.cs مباشرة، ستجدي كود يبدأ بـ public partial class Form1 : Form.
    قبل أي دالة قومي بكتابة المتغيرات التالية:
    تخزين العرض الأصلي تخزين الإرتفاع متغير لمتابعة حجم الزر بتصغيره وتكبيره متغير خاص بوقت الـ animation كالتالي:
    private int originalWidth; private int originalHeight; private bool isExpanding = false; private Timer animationTimer; ثم في دالة في دالة Form1()، أضيفي كود بعد InitializeComponent() لإسناد متغيري الطول والعرض إلى button1.Width وbutton1.Height
    بعد ذلك عليك إعداد المؤقت timer 
    animationTimer = new Timer(); animationTimer.Interval = 30; ثم إضافة حدث النقر Click، في في وضع التصميم، انقري مرتين على الزر، وسيُنشئ Visual Studio دالة button1_Click، اكتبي بها:
    MessageBox.Show("تم النقر على زر الاستئناف!"); ثم حدث تمرير المؤشر على الزر MouseEnter  في نافذة Properties للزر، انقري على أيقونة البرق Events وابحثي عن MouseEnter، انقري مرتين بجانبه لإنشاء دالة واكتبي بها:
    isExpanding = true; animationTimer.Start(); ثم حدث مغادرة المؤشر MouseLeave بنفس الكيفية حاولي كتابة الكود الخاص به.
    بعد ذلك تنفيذ الرسوم المتحركة، بربط المؤقت بحدث، في Form1() بعد إعداد animationTimer.Interval، أضيفي التالي لربط المؤقت بدالة:
    animationTimer.Tick += AnimationTimer_Tick; ثم عليكِ كتابة تلك الدالة وهي AnimationTimer_Tick لتحريك الزر بناءًا على ما سبق.
    في حال واجهتي صعوبة أخبريني.
  14. إجابة Mustafa Suleiman سؤال في تثبيت قاعدة بيانات محلية كانت الإجابة المقبولة   
    تقصد تحميل قاعدة بيانات الخاصة بالمتجر من الاستضافة إلى حاسوبك أي قاعدة بيانات موجودة بالفعل؟ أم تريد إنشاء قاعدة بيانات محلية من الصفر لأنّ الأمر مختلف؟
  15. إجابة Mustafa Suleiman سؤال في ngnix لايعرض تنسيقات لوحة الادارة لمشروع جانغو كانت الإجابة المقبولة   
    لنحاول التأكد من أن Nginx يستخدم location /static/ بشكل صحيح لجميع الطلبات التي تبدأ بـ /static/، وأن الـ location يتمتع بالأولوية على أي كتلة أخرى ربما تتعارض معه.
    أولاً بتعديل ملف الإعدادات لاستخدام ^~ مع location /static/ لإعطائه الأولوية:
    server { root /opt/Fikra-Project; location ^~ /static/ { alias /opt/Fikra-Project/staticfiles/; } location /media/ { alias /opt/Fikra-Project/media/; } # إعدادات Gunicorn location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } حيث ^~ يخبر Nginx أنه إن تم العثور على تطابق مع /static/، فلا يتم التحقق من أي كتل location أخرى، واستخدام ذلك الـ location مباشرًة، ويجب أن يكون location ^~ /static/ يأتي قبل location / في ملف الإعدادات.
    ثم تنفيذ أمر:
    python manage.py collectstatic --noinput بعد التعديل، تفقد صحة الإعدادات وأعد تحميل Nginx:
    sudo nginx -t sudo systemctl reload nginx  
  16. إجابة Mustafa Suleiman سؤال في كيف تقسمون وقت دراستكم كانت الإجابة المقبولة   
    القاعدة ليست مرتين أو أكثر، بل ما تحتاجه هو 4 إلى 5 أضعاف وقت الدورة، وذلك ما بين مشاهدة للدرس واستيعابه بشكل مبدأي أو كلي ثم التطبيق العملي بمفردك والمراجعة على النقاط التي يقل بها استيعابك، ثم محاولة التطبيق بشكل جانبي على تمرين مختلف أو مشروع بسيط.
    أيضًا البحث والقراءة للاستزادة فلا تكتفي بما يتم تقديمه في أي دورة مهما كانت، ولا حاجة لإعادة الدرس طالما أنك قادر على التطبيق على ما جاء به بمفردك وليس الكتابة مع الشرح ومتفهم لما تقوم به وليس مجرد حفظ للخطوات، عند الوصول لذلك تنتقل للدرس التالي.
    إذا أردت نصيحتي، فلا تستمتع لمن يخبرك بالدراسة 30 دقيقة ثم استراحة 10 أو 5 دقائق وهي تقنية Pomodoro، فهو يضرك بدون قصد.
    فالعقل يستغرق حوالي 23 دقيقة للدخول في مرحلة التركيز، والتوقف بعد 25 دقيقة يحرمك من التركيز العميق وحالات التدفق flow states.
    وسأوضح لك كيفية خداع عقلك لتعلم شيء جديد بسرعة وبفعالية أكبر:
    حدد فترة زمنية مركزة، أي ابدأ بأن تخبر نفسك أنك ستخصص الـ 45 دقيقة القادمة فقط لتعلّم الموضوع أو المهارة الجديدة، وذلك لا يضعك تحت ضغط ويخبر عقلك أن الأمر مجرد 45 دقيقة، ويساعدك في دخول وضعية تعلّم ذات تركيز عالٍ.
    خلال تلك الفترة المُركّزة لمدة 45 دقيقة، يُفرَز اثنان من المواد الكيميائية المهمة في عقلك:
    الأدرينالين وهو الهرمون الذي يزيد من اليقظة ويجعلك أكثر انتباهًا واستعدادًا لاستيعاب المعلومات الجديدة. الأسيتيل كولين، ويلعب دورًا حاسمًا في عملية التعلم وتشكيل الذاكرة، وعند إفرازه، يقوم بتعزيز تعديل الاتصالات العصبية المرتبطة بالمهمة المحددة، مما يعني تسليط الضوء على المناطق التي يرغب الدماغ في تغييرها لاستيعاب المعلومات الجديدة. وبمجرد انتهاء الفترة المُركّزة للتعلم، فقد حان الوقت لوقف كل شيء وإعطاء عقلك فترة راحة، وذلك الوضع مهم لسببين رئيسيين:
     تثبيت المعلومات المكتسبة: فأثناء الراحة، يعمل عقلك على تنظيم وتثبيت المعلومات التي اكتسبتها حديثًا في مسارات عصبية دائمة، وتلك العملية تُعزز من ترسيخ التعلم.  زيادة الدافعية: حيث معرفة أنك ستسترخي وتأخذ استراحة بعد الجلسة المركزة يعتبر مكافأة، مما يزيد من دافعيتك للانخراط في المهمة التعليمية من البداية. والآن نتأتي لزيادة فترة التركيز مع مرور الوقت، وذلك مع التمرس في تلك التقنية، يمكنك تدريجياً زيادة فترة التعلم المركز إلي حتى 2 ساعة (أو أي فترة مناسبة)، فالجلسات الأطول تؤدي إلى تعلم أعمق ودخول حالات من التدفق الإبداعي flow states وعندها لن تشعر بنفسك بل ينصب تركيزك بالكامل على ما تفعله.
    وعندما كنت أتعلم البرمجة، كنت أقضي 10 ساعات يوميًا وربما أكثر، بين مشاهدة الدروس والتوقف للاستيعاب ثم المراجعة والتطبيق بمفردي لتثبيت ما تعلمته وأحيانًا البحث عن الأمور التي لا أفهمها سواء بمشاهدة شرح على اليوتيوب أو قراءة مقال على جوجل.
    أو رؤية مثال، أو البحث عن تمرين للتطبيق ومحاولة التغيير في الكود لفهم طبيعة عمله وكيف يعمل ولماذا استخدمنا ذلك ولم نستخدم ذلك وهكذا.
  17. إجابة Mustafa Suleiman سؤال في خطأ في دالة addEventListener كانت الإجابة المقبولة   
    لا يوجد مشكلة عند الضغط على زر تسجيل الدخول أو زر إنشاء الحساب، قمت بكتابة إيميل وباسورد وتم تسجيل الدخول والتحويل للصفحة الرئيسية.
    عامًة قم بتغيير الكود للتالي:
    registerBtn?.addEventListener('click', () => { container.classList.add("active"); }); loginBtn?.addEventListener('click', () => { container.classList.remove("active"); }); لاحظ ? وهو Optional chaining operator فائدته التحقق من وجود قيمة أي يعمل على التحقق من وجود قيمة registerBtn و loginBtn قبل محاولة الوصول إلى خاصية addEventListener.
    وفي حال registerBtn أو loginBtn تساوي null أو undefined، فإن الكود لن يُنفذ addEventListener وسيتم تجنب حدوث خطأ.
  18. إجابة Mustafa Suleiman سؤال في رفع موقع الكتروني على vps ومشكلة في تشغيل WebSocket كانت الإجابة المقبولة   
    أولاً، تفقد هل الخادم (VPS) يعمل على المنفذ 8000. يمكنك فعل ذلك عن طريق تشغيل الأمر التالي في منفذ الأوامر، حيث يجب أن يكون المنفذ الذي تستخدمه Nginx لـ WebSocket هو نفس المنفذ الذي يستخدمه Django:
    sudo netstat -tlnp | grep 8000 ثانياً، لنتأكد من أن تكوين Nginx  صحيح، عن طريق تشغيل الأمر التالي:
    sudo nginx -t ثم لنتأكد من أنّ الخادم يعمل على بروتوكول HTTP/1.1،  عن طريق إضافة السطر التالي إلى تكوين Nginx:
    proxy_http_version 1.1; نفس الأمر لنتأكد من أنه يعمل مع بروتوكول WebSocket، بإضافة التالي:
    proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; ثم التأكد من أنّه يعمل مع بروتوكول SSL/TLS، بإضافة التالي:
    ssl_certificate /etc/letsencrypt/live/domain_name/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/domain_name/privkey.pem; أيضًا قم بتجربة إضافة / إلى نهاية العنوان إن حدث مشكلة قم بإزالتها فذلك يعتمد على إعدادات websocket  في الخادم لديك:
    location /ws/ { proxy_pass http://127.0.0.1:8000/; وفي ملف الإعدادات في django   في مصفوفة ALLOWED_HOSTS يجب أن تحتوي على يحتوي على اسم المجال، وإليك مثال عليك تعديله:
    ALLOWED_HOSTS = ['test.com', 'www.news.com', 'blog.news.com', '111.222.333.444'] أيضًأ في حال تستخدم SSL/TLS، فقم بتعيين SECURE_PROXY_SSL_HEADER في ملف settings.py.
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')  
  19. إجابة Mustafa Suleiman سؤال في بناء معرض أعمال كمصمم كانت الإجابة المقبولة   
    كمصمم جرافيك عليك تحديد تخصصك أولاً فلا يوجد مصمم لكل شيء، مثلاً مصمم شعارات وهويات أو مصمم منتجات أو UI/UX وهكذا.
    ثم قم بتفقد مواقع العمل الحر والمشاريع الخاصة بالتصميم ثم اختر مشروع مناسب لك واعمل على تنفيذه كأنك تعمل عليه بالفعل ثم ضعه بمعرض أعمالك، وهكذا لحين بناء معرض أعمال جيد وذلك سيحقق لك فائدة لأنك تقوم ببناء مشاريع مطلوبة بالفعل وسيصبح لديك أمثلة عليها.
     
  20. إجابة Mustafa Suleiman سؤال في استفسار بخصوص العودة لتعلم البرمجة كانت الإجابة المقبولة   
    أتفهم شعورك بالتشتت في الوقت الحالي، لكن ستحتاج إلى الإنتهاء من مرحلة ثم التي تليها، فحاليًا أنت تتعلم أساسيات البرمجة من خلال دورة علوم الحاسوب، أرجو قراءة التالي:
    ثم التالي:
    وبالنسبة لمطور Full-stack فهل تريد تعلم جافاسكريبت أم PHP أم بايثون؟
    عامًة عليك تحديد ذلك من خلال تفقد المطلوب في سوق العمل لديك بالنسبة لمستوى Junior، وليس ما تفضله أنت.
    وتستطيع التعلم من مصادر أخرى، وذلك أفضل بالطبع فلا توجد دورة واحدة تقدم لك كل شيء، وأحيانًا تحتاج إلى المزيد من الشرح أو التوضيح أو رؤية شرح آخر وهكذا، أيضًا للتطبيق العملي أنت بحاجة إلى للبحث على اليوتيوب عن مشاريع للمبتدئين وتنفيذها ورؤية الشرح.
    لكن لا تشتت نفسك، عليك الإلتزام بدورة واحدة مناسبة لك، ثم الاستزادة من مصادر أخرى وتنفيذ مشاريع والممارسة.
     
  21. إجابة Mustafa Suleiman سؤال في خطأ إنشاء جداول وتعديلات على قاعدة البيانات كانت الإجابة المقبولة   
    أثناء تثبيت Postgres هل قمت بكتابة باسورد؟ عليك كتابته وليس الباسورد الخاص بـ pgAdmin فذلك هو ال Master password للوصول لسيرفرات قاعدة البيانات وليس باسورد قاعدة البيانات نفسها، في حال قمت بكتابة نفس الباسورد فلا مشكلة.
    المستخدم الإفتراضي لقاعدة البيانات هو postgres عليك كتابة  كلمة المرور التي قمت بتحديدها.
    إن لم تتذكرها، قم بكتابة التالي في psql لتعيين كلمة مرور:
    ALTER USER postgres WITH PASSWORD 'admin' تستطيع تعديل admin إلى كلمة تريدها.
    بعد ذلك قم بإعادة تشغيل Postgres من خلال تنفيذ التالي في CMD أو أي منفذ أوامر وليس من خلال PSQL:
    net stop postgresql net start postgresql  
  22. إجابة Mustafa Suleiman سؤال في كيف اعرف نوع table في laravel كانت الإجابة المقبولة   
    لم تقم بإنشاء عمود من أجل الإختيارات (نعم أو لا) والنوع المناسب له هو Boolean أي قيمة منطقية True أو False، وليكن باسم has_agreed.
    أيضًا لم تقم بإنشاء عمود للمرفقات وتعيين نوعه كـ String لتخزين مسار الملف.
    وبقية الأعمدة نوعها مناسب، وهيكل الجدول سيكون كالتالي:
    Column Name Data Type Description id int Auto-incrementing primary key firstname varchar(255) First name lastname varchar(255) Last name email varchar(255) Email address phone varchar(20) Phone number category varchar(255) Category (dropdown list) has_agreed boolean Has agreed to terms (yes/no options) attachment varchar(255) File attachment created_at timestamp updated_at timestamp وذلك هو الكود:
    use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateArticalesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('articales', function (Blueprint $table) { $table->id(); $table->string('firstname', 255); $table->string('lastname', 255); $table->string('email', 255); $table->string('phone', 20); $table->string('category', 255); $table->boolean('has_agreed')->default(false); $table->string('attachment', 255)->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('articales'); } }  
  23. إجابة Mustafa Suleiman سؤال في استفسار بخصوص السن والعمل الحر ومسار تعلم البرمجة كانت الإجابة المقبولة   
    دخولك جامعة متخصصة في مجال البرمجة يزيد من فرصك بالطبع، لكن الجامعة هي ميزة إضافية ولا يتوقف عليها الأمر في مجال البرمجة، فستجد الكثيرين لم يحصلوا على شهادة جامعية في البرمجة ويعملون في ذلك المجال بشركات كبيرة.
    وطالما أنك مهتم بذلك المجال وتريد العمل به، فمن باب أولى دخول جامعة خاصة به مثل حاسبات ومعلومات أو أيًا يكن المسمى، ولا مشكلة لو قمت بدخول كلية أخرى فكما ذكرت الأمر لا يتعلق بالجامعة حيث أتفهم أحيانًا رغبة الأهل.
    بخصوص العمل الحر، ففي الوقت الحالي لن تتمكن من ذلك حيث أنّ مواقع العمل الحر تشترط أن يكون سنك 18 عام على الأقل، فلا تستعجل على ذلك لو استمريت في تطوير مستواك فبحلول 18 عام ستصبح قادر على اكتساب مبالغ كبيرة أضعاف ما كنت ستعمل به حاليًا.
    وبخصوص الـ Roadmap فأشيد بما قمت به حقًا، فأنت في سن صغير وقد وضعت خارطة طريق صحيحة وأيضًا على علم بتقنية مثل Three.js ونعم تستطيع تعلمها وستضيف لك الكثير كمطور واجهة أمامية وأنت حاليًا تستطيع تعلم ما تريد استمتع بذلك حقًا، فمستقبلاً لن تجد الوقت الذي بين يديك حاليًا.
  24. إجابة Mustafa Suleiman سؤال في استفسارات بخصوص ال responsive design في CSS كانت الإجابة المقبولة   
    بخصوص أفضل الممارسات، فتلك ليست الطريقة المناسبة، الإعتماد على حجم الشاشة لتعيين الخط أمر يسبب مشاكل غير متوقعة بطبيعة الحال حيث لا تستطيع التحكم في ذلك بشكل كامل، فلا يوجد حد أدنى أو حد أعلى لمدى حجم الخط والأمر يظهر بشكل جلي في الشاشات الصغيرة و الشاشات الكبيرة، أيضًا لو قمت بعمل تقريب للصفحة فلن يزداد حجم الخط في حال كان مساوي لـ 80vw مثلاً وستزداد النصوص التي أقل من ذلك.
    بالتالي الطريقة الحديثة هي استخدام دالة clamp والصيغة الخاصة بها كالتالي:
    font-size: clamp(MIN, VALUE, MAX); MIN: الحد الأدنى لحجم الخط. VALUE: القيمة المفضلة لحجم الخط والتي ستتغير حسب حجم الشاشة، أي القيمة التي نريدها للخط. MAX: الحد الأقصى لحجم الخط. وهناك أداة توفر لك القيم للنصوص المختلفة في موقعك، أي تقوم بتوليد القيم لك بناءًا على مساحة العرض وحجم الخط الذي تريده، وهي:
    utopia.fyi https://modern-fluid-typography.vercel.app/ وهناك حل آخر أُفضله، وهو إنشاء TYPOGRAPHY SYSTEM، بحيث يكون هناك نظام للخطوط متبع في كامل التصميم.
    أولاً عليك تحديد حجم الخط الأساسي للموقع ليصبح 10px، ونفعل ذلك بوضع القيمة التالية:
    html { font-size: 62.5%; } 62.5% من حجم الخط الأساسي أو الإفتراضي وهو 16px تصبح القيمة 10px.
    وذلك لتسهيل استخدام rem فحاليًا 1.8rem تعني 18px وبالتالي الاستخدام أصبح أسهل من أجل التجاوبية.
    بعد ذلك عليك تحديد مقاسات الخطوط في الـ  TYPOGRAPHY SYSTEM وأحجامها font weight وأيضًا الـ line height  وفي النهاية ستجد أنّ لديك نظام يشبه التالي:
    *** 01 TYPOGRAPHY SYSTEM - Font sizes (px) 10 / 12 / 14 / 16 / 18 / 20 / 24 / 30 / 36 / 44 / 52 / 62 / 74 / 86 / 98 - font weight default: 400 medium: 500 semi-bold:600 bold: 700 - line height default: 1.5 medium: default paragraph: 1.5 الآن قم بإنشاء متغيرات مخصصة تستطيع استخدامها في كامل التصميم كالتالي:
    :root { --font-family: 'Noto Kufi Arabic', sans-serif; --font-color--white: #ebedf8; --font-color: #322143; --font-size: 1.8rem; --font-size-small: 1.4rem; --font-size-medium: 1.6rem; --font-weight--medium: 500; --font-weight--semi: 600; --line-height: 1.5; }  
  25. إجابة Mustafa Suleiman سؤال في قاعد البيانات فارغة بعد تنفيذ كود PHP كانت الإجابة المقبولة   
    تقوم بتضمين ملف register.php مرتين، وذلك  يسبب حدوث تعارضات أو الكتابة فوق المتغيرات،  احذف السطر include 'register.php'; من داخل كتلة if (isset($_POST['save_btn']))، حيث تحتاج فقط إلى تضمينه مرة واحدة في بداية البرنامج.
    <?php include 'register.php'; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="styles.css"> <title>User Registration</title> </head> <body> <div> <h1>User Registration Form</h1> <form action="register.php" method="POST"> <input type="text" name="Firstname" placeholder="Firstname"> <br> <br> <input type="text" name="Lastname" placeholder="Lastname"> <br> <br> <input type="number" name="Age" placeholder="Age"> <br> <br> <input type="submit" name="save_btn" value="Save Data"> </form> </div> <?php if (isset($_POST['save_btn'])) { $fname = mysqli_real_escape_string($connect, $_POST['Firstname']); $lname = mysqli_real_escape_string($connect, $_POST['Lastname']); $age = mysqli_real_escape_string($connect, $_POST['Age']); $query = "INSERT INTO students (Firstname, Lastname, Age) VALUES ('$fname', '$lname', '$age')"; $result = mysqli_query($connect, $query); if ($result) { echo "تم إدراج السجل بنجاح!"; } else { echo "خطأ: " . mysqli_error($connect); } } ?> </body> </html>  
×
×
  • أضف...