ستحتاج إلى تحديث نماذج المشروع وتحديد العلاقات المناسبة حيث كل بطاقة مرتبطة بحدث واحد، وكل بطاقة يمكن أن تكون مرتبطة بزائر واحد وكل زائر مرتبط بحدث.
ثم عليك إنشاء حدث جديد وبطاقةوحدد عدد الدعوات، ثم إنشاء زوار بعدد الدعوات، واربطهم بالحدث، ثم بطاقة لكل زائر واربطها بالحدث والزائر.
بعد ذلك أنشئ رابط فريد لكل زائر وأرسله له، وعند دخول الزائر للرابط، تحقق من الحدث والبطاقة الخاصة به.
وذلك حتى يتمكن المشرف من تسجيل الدخول من /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 }} © 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 example2@email.com 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
ثم تشغيل المشروع.