Qtalk! :))

Jomana

گربه بالدار!
ارسال‌ها
210
امتیاز
1,303
نام مرکز سمپاد
فرزانگان ۱
شهر
بندرعباس
سال فارغ التحصیلی
1403
من لوگو رو بسازم حمایت میشه؟ :-"
درحال حاضر کلی بدبختی و مشقت دارم ولی تموم شد دوست دارم ی لوگو هم خودم درست کنم واسش... مایه افتخاره :D
~~~~
اصلاح میشه: توی این نصف شبی نشستم توی چند دقیقه دو تا لوگوی خیلی کیوت طراحی کردم. (با این اپ های طراحی لوگو و اینا ن... با برنامه نقاشی خودم)... بعد حالا نمیدونم همینجا بذارم یا کجا...
~~~~
اصلاح میشه²: لینک ها رو میذارم اینجا... حداقل میدونی کپی رایت ندارن و کار دست خودمه :D
حالا بنده به عنوان رتبه آور طراحی پوستر شهرستان در سال ۱۳۹۸ (ب حرفام گوش ندین چرت و پرت میگم) اومدم دارم طرح های خیلی خیلی متواضعانه خویش رو در جهت ارتقای کیوتاک قرار می‌دم. درضمن، من دوتا لوگو درست کردم ولی واسه هرکدوم ی حالت ساده هست و ی حالت رنگی، همه فایل ها هم png هستن، فقط اون ساده ها png بدون پسزمینه‌ن. امیدوارم دیگه خودت متوجه بشی :D
~~~~
اصلاح میشه³: بی‌خوابی دارم.. درک کنید :)
فقط خواستم گفته باشم، اگه خوشت نیومد یا چیز خاصی مد نظرت بود حتما بگو منم نهایت تلاشمو می‌کنم ^__^
 
آخرین ویرایش:

karen.m

کاربر فوق‌حرفه‌ای
عضو کادر مدیریت
مدیر داخلی
عضو مدیران انجمن
ارسال‌ها
620
امتیاز
9,853
نام مرکز سمپاد
شهید بهشتی
شهر
خرم آباد
سال فارغ التحصیلی
1397
اینستاگرام
هلو ایتس می اگن :)
بعد مدت نسبتا زیادی یه فیچر جدید اضافه میکنیم
راستش مدتیه میخوام معماری داستان رو کلا عوض کنم و کلا یه اپ RESTFul درست کنم ک کلاینت های مختلف بهش وصل شن
ولی هنوز سمت کلاینت رو نتونستم با خودم حل کنم و همزمان انگیزمو واسه کارکردن رو معماری فعلی از دست دادم
-------------------
پروژه کم کم بزرگ شده و مدیریت فایل ها داره از دستم خارج میشه
مثلا فایل routes.py ک url هارو نگه میداره شده ۴۰۰ خط کارکردن باهاش داره پیچیده میشه
امروز داشتم blueprint فلسک رو اضافه میکردم تا مدیریت داستان آسون شه ولی سخت تر از چیزی بود ک فک میکردم و با یه نامیدی بزرگ گفتم یه فیچر جدید اضافه کنم ک لااقل یکاری کرده باشم:)
میخوایم واسه پست هامون قابلیت کامنت گذاشتن بزاریم

خب میخوایم کامنت بزاریم .... تو اولین قدم باید یه کاسه درست کنیم :)) ک کامنت هامونو توش ذخیره کنیم
ما تو مدل هامون با دیتابیس ارتباط میگیریم پس بریم تغیرش بدیم
app/Qtalk/models.py:
Python:
class User(db.Model, UserMixin):
............
    comments = db.relationship('Comment', backref='sender', lazy=True)
   
class Post(db.Model):
.............
    comments = db.relationship('Comment', backref='author', lazy=True)
   
class Comment(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    date_commented = db.Column(db.DateTime, nullable=False, default=datetime.now)
    content = db.Column(db.Text, nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
قبلنا یه کاسه داشتیم ک پست هامون توش بود
یه کاسه دیگه داشتیم ک یوزر هامون اون تو بودن
الانم یه کاسه واسه کامنتا ساختیم
کامنت ها با یوزر ها در ارتباطن .... درواقع هرکامنت مختص به یوزر هستش ک اون یوزر اسم و عکس و ... داره
کامنت باید با پست هم در ارتباط باشه ... خیلی ضایع میشه ک من واسه پست صفدر کامنت بزارم ولی کامنتم بره زیر پست حشمت :))
هر کامت مال یک یوزره ک برای یک پست نوشته شده
کدای بالا دقیقا ترجمه همین چیزیه ک گفتم

حالا ک ظرفامونو داریم باید بتونیم پرش کنیم
واسه کامنت گذاشتن باید یه فرم رو پر کنیم ... پس بریمم فرم رو بسازیم :
app/Qtalk/forms.py :
Python:
class CommentForm(FlaskForm):
    content = TextAreaField(validators=[DataRequired()])
    submit = SubmitField('ارسال نظر')
این دیگه واضحه .... یه فرم ساختیم ک یه محتوا از ما قبول میکنه و یه دکمه واسه ارسال نظر داره
تا اینجا ما فرم رو ساختیم و یه ظرف هم داریم ک محتوای فرم رو واسمون نگه داره ..... دیگه چی میخوایم ؟
باید فرم رو بریزیم تو ظرف :
app/Qtalk/routes.py
Python:
@app.route("/new_comment/<int:post_id>", methods=['POST'])
@login_required
def new_comment(post_id):
    form = CommentForm()
    if form.validate_on_submit():
        content = form.content.data
        comment = Comment(content=form.content.data, post_id=post_id, sender=current_user)
        db.session.add(comment)
        db.session.commit()
        flash('نظر شما ارسال شد.', 'success')
        return redirect(url_for('post', post_id=post_id))
تو backend ماجرا دیگه کاری نداریم ... همه چیزیایی ک میخواستیم رو ساختیم ... فقط باید نشونشون بدیم :
واسه نشون دادن کامنت ها برای هر پست یه پیج درست میکنم (خیلی وقت پیش کرده بودم ولی کاری انجام نمیداد)
زیر همون پیج کامنت هارو نشون میدیم:
HTML:
{% if post.comments %}
{% for comment in post.comments %}
<div class="card" style="margin:20px;">
<div class="card-body">
<img class="rounded-circle article-img float-right" src="{{ url_for('static', filename='profile_pics/'+ comment.sender.image_file) }}">

<h5 class="card-title">

  <a class="mr-4 float-right" href="{{url_for('user', username=comment.sender.username)}}">{{ comment.sender.username }}</a>
</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ comment.date_commented.strftime('%Y-%m-%d') }}</h6>
<hr>
<p class="card-text article-content ">{{comment.content|safe}}</p>
</div>
</div>
{% endfor %}
{% endif %}
ترجمه html بالا :
اگه کامنتی وجود داشت مث بچه آدم نشونش بده :))
حالا تو همین صفحه باید wysiwyg هم بزاریم تا بتونیم پست جدید ارسال کنیم :

HTML:
<div class="content-section">
  <legend class="border-bottom mb-4">ارسال نظر</legend>

  <form method="POST" action="{{ url_for('new_comment', post_id=post.id) }}">
      {{ form2.hidden_tag() }}
      <filfieldset class="form-group ">
          <div class="form-group">
              {% if form2.content.errors %}
              {{ form2.content(id="summernote", class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in form2.content.errors %}
                      <span>{{ error }}</span>
                    {% endfor %}
                </div>
              {% else %}
                  {{ form2.content(id="summernote", class="form-control form-control-lg") }}
              {% endif %}
          </div>
          <div class="modal-footer">
              {{ form2.submit(class="btn btn-outline-info") }}
          </div>
  </form>
    </div>
خب اینم ردیفه
الان آخر صفحه یجایی داریم ک میشه توش نظر نوشت
ولی خیلی خوب نیست ... شاید تعداد کامنتا زیاد باشه و اونوقت باید تا تهه پیج اسکرول کنی
یه مادل بوت استرپ هم میزارم ک اگه آیکن گوشه صفحه لمس شد ادیتور کامنت جدید رو نشون بده :
HTML:
{% if current_user.is_authenticated %}
<button type="button" class="fixedbutton btn" data-toggle="modal" title="ارسال نظر" data-target="#commentModal"><img src="{{url_for('static', filename='logo/add_btn.png')}}" width="65" height="65"></button>
{% endif %}

<div class="modal fade" id="commentModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">ارسال نظر</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form method="POST" action="{{ url_for('new_comment', post_id=post.id) }}">
            {{ form2.hidden_tag() }}
            <filfieldset class="form-group ">
                <div class="form-group">
                    {% if form2.content.errors %}
                    {{ form2.content(id="summernote", class="form-control form-control-lg is-invalid") }}
                      <div class="invalid-feedback">
                          {% for error in form2.content.errors %}
                            <span>{{ error }}</span>
                          {% endfor %}
                      </div>
                    {% else %}
                        {{ form2.content(id="summernote", class="form-control form-control-lg") }}
                    {% endif %}
                </div>
                <div class="modal-footer">
                    {{ form2.submit(class="btn btn-outline-info") }}
                </div>
        </form>
      </div>
    </div>
  </div>
</div>
خب قسمت کامنت دادن هم اوکیه دو تا کار دیگه مونده :
۱. کنار هر پست نشون بدیم ک چنتا نظر داره
۲. کاربری ک میخواد نظر بده رو هدایت کنیم با صفحه نظر دادن
دوتارو همزمان انجام میدیم :
HTML:
      <p class="text-muted mr-4 mb-2 show_score" id="result-{{post.id}}" style="text-align:left">
        {{ post.likes.count() }} لایک
  و
          {{ post.dislikes.count() }} دیسلایک
          <div class="mb-4 ml-4">
            <a href="{{url_for('post',post_id=post.id )}}" type="button" class="btn btn-light">{{post.comments|length}} نظر</a>
          </div>
      </p>
بنظر میرسه همه چی درست کار کنه ... یسری ریزه کاریا مونده ک بعدا انجامش میدم
شبتون خوش :)
 

karen.m

کاربر فوق‌حرفه‌ای
عضو کادر مدیریت
مدیر داخلی
عضو مدیران انجمن
ارسال‌ها
620
امتیاز
9,853
نام مرکز سمپاد
شهید بهشتی
شهر
خرم آباد
سال فارغ التحصیلی
1397
اینستاگرام
یچیزی یادمون رفت دیشب :
ما یسری پست (جدول پست ها توی دیتابیس) داریم ک اون پست ها یسری کامنت (جدول کامنت ها توی دیتابیس) دارن
حالا فرض کنیم بخوایم یکی از پستهای دارای کامنت رو حذف کنیم:
کامنت میخواد بگرده ک پستش رو پیدا کنه ولی پستی وجود نداره ک مال اون کامنت باشه :
چی میشه :
Screenshot_20200614_144112_Chrome.jpg
error پونصد -ـــ-
حالا ک میدونیم مشکل چیه درست کردنش آسونه :
Python:
    if post.comments:
        for comment in post.comments:
            db.session.delete(comment)
قبل اینکه بزنیم پست رو پاک کنیم -> چک میکنیم ک آیا اون پست کامنت داره یا نه -> اگه داشت کامنت هارو پاک میکنیم -> حالا میشه پست رو بی دردسر پاک کرد :)
 

shnava

نوا
ارسال‌ها
1,594
امتیاز
18,836
نام مرکز سمپاد
فرزانگان 2
شهر
تهران
سال فارغ التحصیلی
94
دانشگاه
شهید بهشتی
رشته دانشگاه
پزشکی
تلگرام
اینستاگرام
دمت گرم که وقت میذاری
همین : ))
 

ای فر

کاربر نیمه‌حرفه‌ای
ارسال‌ها
201
امتیاز
2,730
نام مرکز سمپاد
فرزانگان
شهر
همینجا
سال فارغ التحصیلی
1398
لینک رو پیدا نمیکنم ...
کو لینکش ؟
 

marone

مروان
کنکوری ۱۴۰۰
ارسال‌ها
41
امتیاز
554
نام مرکز سمپاد
فرزانگان
شهر
رفسنجان
سال فارغ التحصیلی
1400

arshia.r

کاربر نیمه‌فعال
ارسال‌ها
14
امتیاز
38
نام مرکز سمپاد
شهید بهشتی
شهر
ساری
سال فارغ التحصیلی
0
خب سلام :)
خیلی سریع بریم باگ دیروز رو فیکس کنیم و یه فیچر جدید اضافه کنیم و ورژن ۱.۳ رو دیپلوی کنیم:)
دکمه های فالو و آنفالو عضو یک کلاس مشترک بودن ولی آیدیشون متفاوت و برابر یوزرنیم کاربر بود
از روی آیدیشون تشخیص میدادیم ک کروم کاربر داره فالو میشه و ....
خوب کار میکرد ولی با یوزر هایی ک تو اسمشون اسپیس یا کولن داشت اشتباه داشتیم
اولش داشتم فک میکردم ک یه تابع نرمالایز بنویسم و اسپیس هارو هندل کنم ولی خیلی داشت کثافط بازی میشد :))
اینجا بود ک فهمیدم عجب کار ضایع ای کردم ک یوزرنیم کاربر رو آیدی دکمه درنظر گرفتم .... درحالی ک هر کاربر تو دیتابیس یه یوزر آیدی داره ک هم یونیکه و هم عددیه
انی وی با تغیر username به userid مشکل حل شد و دیگه مث ساعت کار میکنه :P :P
حالا بریم لایک ها و دیسلایک هارو نشون بدیم :)
من از یه orm به عنوان رابط خودم و دیتابیس استفاده میکنم
درواقع جدول دیتابیسم یجور کلاسه ک هر سطرش یه شی ازون کلاسه ومیشه متود های مختلف واسش نوشت
مشاهده پیوست 1263
این یه قسمت از جدولیه ک لایک ها توش ثبت شدن
مثلا تو ستون اولی ک تو عکس هست گفته ک کاربری ک یوزر آیدیش۱۲ هستش اومده به پستی ک آیدیش یکه لایک داده ( همون پست اولیه خودمه)
خب یه متود نوشته بودم که کل لایک های یه پست رو بیرون میکشید و تعدادشون رو میشمرد و میگفت این پست مثلا ۱۰ تا لایک داره
از همون استفاده میکنم
فقط مشکلی ک هست اینه ک ما نمیدونیم کاربر شماره ۱۲ کیه؟ و اول باید اینو بفهمیم بعدش باید اون آرایه ای ک userid کاربرا توشن رو به آرایه ای تبدیل کنیم ک username ها توشن .... اینجاش آسونه ... دوتا تابع مینویسیم ک هردوتای این کارا رو انجام بدن
Python:
def find_username(user_id):
    user = User.query.filter_by(id=user_id).first()
    result = user.username
    return result

def score_list(scores):
    result = []
    for i in scores :
        result.append(find_username(i.user_id))
    return result
اینطوره ک آرایه لایک های یک پست به تابع دوم داده میشه و ب ازای هر آیدی توی اون آرایه تابع اول صدا زده میشه و نهایت یه لیست از کاربرایی ک اون پست رو لایک کردن برگردونده میشه
خب الان میتونیم امتیازا رو پیدا کنیم و مث قسمت قبلی ک فالوور هارو نشون دادیم نشون بدیم
ولی خیلی کثیف کاری :
هر صفحه ۲۵ تا پست داره و ما امتیازات همشون رو داریم میفرستیم تو اون صفحه درحالی ک کسی قرار نیست هربار ک صفحه رو باز میکنه همه رو نگاه کنه
بهتره هرموقع کسی خواست امتیازای یه پست رو ببینه یه درخواست به سرور بره و فقط همون چیزی رو ک میخواد واسش بفرستیم
پس تو روت یه url جدید درست میکنیم ک وقتی صداش زدیم آرایه امتیازات رو بفرسته
Python:
@app.route("/post/<int:post_id>/scores", methods=['POST'])
def scores(post_id):
    post = Post.query.get_or_404(post_id)
    likes = score_list(post.likes.all())
    dislikes = score_list(post.dislikes.all())
    return jsonify(post_id = post_id,
                   likes = likes,
                   dislikes = dislikes)
تو بک اند دیگه کاری نداریم و الان باید به فکر راهی واسه نشون دادنش باشیم
توی آخر فایل base.html ک همه html ها ازش ارث بری دارن یه مادل بوت استرپ اضافه میکنم ک بعدا بتونم امتیازا رو بریزم توش:
HTML:
<div class="modal fade" id="scoreModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">امتیازات</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body modal-scores">
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">خروج</button>
      </div>
    </div>
  </div>
</div>
باید یه دکمه هم باشه ک وقتی روش کلیک شد این مادل باز بشه
خب نمیخام دکمه جدا بزارم .... واسه همون جایی ک تعداد لایکارو نشون میدادم یه کلاس جدا تعریف میکنم ک بتونم رویداد های کلیکش رو چک کنم:
HTML:
      <p class="text-muted mr-4 mb-2 show_score" id="result-{{post.id}}" style="text-align:left">
        {{ post.likes.count() }} لایک
  و
          {{ post.dislikes.count() }} دیسلایک
      </p>
خب تو template ها دیگه کاری نداریم ... ازینجا ب بعدش با جاوا اسکریپته :
اول باید حواسمون باشه ک هرکسی روی امتیازا کلیک کرد بفهمیم :
JavaScript:
  $('.show_score').each(function (){
    $(this).click(getScores);
});
الان هرکسی رو امتیازا کلیک کنه رو میفرستیم به تابع getScores
حالا باید با ajax یه درخواست به سرور بزنیم ک لیست امتیاز دهنده هارو بگیریم :
JavaScript:
function getScores(e){
  var fid = $(this).attr('id');
  var id = fid.slice(7)
  $.ajax('/post/'+ id + '/scores' ,{
    type: 'POST',
    success: showScoresModal
  });
}
تو جدا کردن آید ضایع عمل کردم ولی کار میکنه :))
خب الان یه درخواست زدیم به سرور و اگه موفقیت آمیز بود و جواب گرفتیم اطلاعات رو میفرستیم به تابع showScoresModal :
تو این تابع دوتا کار داریم ... اول باید مادل رو مقدار دهی کنیم و دوم مادل رو نشون بدیم
واسه نشون دادن اسم کاربر تو مدل از کارت های بوت استرپ استفاده میکنم ک ظاهرشون بد نیس و بعدا میشه کاستومشون کرد
خب یادمونه ک سرور یسری آرایه واسمون فرستاده .... باید آیتم های اون آرایه رو وارد کارت ها کنیم ک این کارو واسه لایک و دیسلایک مستقل انجام میدم :
JavaScript:
function likeCard(value) {
  card =   `<div class="card border-success mb-3 text-center">
              <div class="card-body text-success ">
                <h5 class="card-title text-success"> <a style="color:#228B22" href="/user/${value}" >${value}</a> </h5>
              </div>
          </div>`
  window.result = window.result + card ;
}
function dislikeCard(value) {
  card =    `<div class="card border-danger mb-3 text-center">
                <div class="card-body" text-danger>
                  <h5 class="card-title text-danger"> <a style="color:#DC143C" href="/user/${value}" >${value}</a> </h5>
                </div>
            </div>`;
  window.result = window.result + card ;
}
نتیجه این دوتا تابع میشه یه متغیر به اسم result ک html همه کارت ها توشه
الان باید تو تابع اصلی این بخش این result رو نشون بدیم :
JavaScript:
function showScoresModal(data){
  window.result = ""
  var modal = $('#scoreModal');
  data.likes.forEach(likeCard)
  data.dislikes.forEach(dislikeCard)
  modal.find('.modal-scores').html(result);
  modal.modal('show');
}
کار تمومه دیگه .... بریم نتیجه رو تست کنیم :
مشاهده پیوست 1264
مث بقیه چیزایی ک درست میکنم زشته :)) .... ولی خب کار میکنه دیگه :P :P
یه پیشنهاد که اصن ربطی به متن نداره:
اسمش رو بزار تسمپاد

منم یه لوگویی طراحی کردم که حالا ببینین چطوره؟ Q شکل این دایره های نقل قول (اسمشو یادم نمیاد :/) شده talk هم توشه
یکمم اومدم رنگیش کنم:
و دیگه من با پیکس آرت و انگشت تو گوشی کشیدم بی سلیقه در اومد...
حباب حرف

ببخشید یکم بد شده با موس تو paint ساختم دیگه بهتر از این نمیشد.گوشیم رو هم گم کرده بودم وگرنه با قلم گوشی بهتر میشد
 

Ss_Zahra

کاربر نیمه‌حرفه‌ای
ارسال‌ها
218
امتیاز
1,613
نام مرکز سمپاد
فرزانگان۱
شهر
قم
سال فارغ التحصیلی
1401
یچیزی یادمون رفت دیشب :
ما یسری پست (جدول پست ها توی دیتابیس) داریم ک اون پست ها یسری کامنت (جدول کامنت ها توی دیتابیس) دارن
حالا فرض کنیم بخوایم یکی از پستهای دارای کامنت رو حذف کنیم:
کامنت میخواد بگرده ک پستش رو پیدا کنه ولی پستی وجود نداره ک مال اون کامنت باشه :
چی میشه :
مشاهده پیوست 1309
error پونصد -ـــ-
حالا ک میدونیم مشکل چیه درست کردنش آسونه :
Python:
    if post.comments:
        for comment in post.comments:
            db.session.delete(comment)
قبل اینکه بزنیم پست رو پاک کنیم -> چک میکنیم ک آیا اون پست کامنت داره یا نه -> اگه داشت کامنت هارو پاک میکنیم -> حالا میشه پست رو بی دردسر پاک کرد :)
خسته نباشی =D>
خیلی زحمت کشیدی
خیلی ماهریا...
خوش بحالت:(
 

karen.m

کاربر فوق‌حرفه‌ای
عضو کادر مدیریت
مدیر داخلی
عضو مدیران انجمن
ارسال‌ها
620
امتیاز
9,853
نام مرکز سمپاد
شهید بهشتی
شهر
خرم آباد
سال فارغ التحصیلی
1397
اینستاگرام
خسته نباشی =D>
خیلی زحمت کشیدی
خیلی ماهریا...
خوش بحالت:(
مرسی ... خیلی لطف داری :)
من اصن ماهر نیستم ... فقط سعی میکنم با چرخیدن تو نت کارمو راه بندازم :)‌))
----------------------
با یکم بی حوصلگی سلول شخصی رضا یزدانی رو پلی میکنم و همزمان با < موکت کردن کف افکارم :) > قسمت حذف کامنت رو مینوسیم
ولی تکلیف چند مورد رو باید مشخص کنیم :
اول :‌ به عنوان یک یوزر چه کامنت هایی رو اجازه داریم پاک کنیم :
۱.کامنت هایی ک خودمون مینویسیم (حق طبیعی مونه :))‌ )
۲.کامنت هایی ک زیر پست های ما نوشته شدن (اینم بدیهیه دیگه .... از نظر آزادی بیان فک نکنم مشکلی باشه ... ب هر حال من متخصص نیستم :)) )
دوم :‌ کامنت باید قابلیت ویرایش هم داشته باشه؟
خب واقعن حس نمیکنم لازم باشه .... تا جایی ک یادمه واسه اینستا هم این قابلیت رو نزاشتن
سوم : حذف کامنت رو شبیه حذف پست مینویسیم؟
نه ... موقعی ک حذف پست رو نوشتم جوون بودم و جاهل(تر) :)) ... هیچ ایده ای از javascript و jquery نداشتم و ب شکل احمقانه ای :)) ب تعداد پست ها modal درست کرده بودم و وقتی پست رو پاک میکنیم کل صفحه دوباره ریلود میشه و خیلی ضایع اس خلاصه
الان ک یکم بیشتر حالیمونه با <جادو جنبل> :))‌ های جاوااسکریپتی عمل پاک کردن کامنت رو روون تر و بهینه تر و تعاملی تر مینوسیم

مث قبلا تو فایل route هامون یه route جدید درست میکنیم:
Python:
@app.route("/delete_comment/<int:comment_id>", methods=['POST'])
@login_required
def delete_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    if comment.sender == current_user or comment.author.author == current_user :
        db.session.delete(comment)
        db.session.commit()
        return jsonify(status='ok', comment_id=comment.id)
       
    abort(403)
میگیم ک اگه یه نفر یه درخواست post فرستاد ک میخواست کامنتی رو پاک کنه :
اگه اجازه داشت -> کامنت رو پاک کن و در جواب یه json بفرست و بهش بگو کامنت با فلان آیدی پاک شد
اگه اجازه نداشت -> ارور ۴۰۳ بهش برگردون (ارور ۴۰۳ یعنی ممنوع شده :) )
بک اند ب همین سادگی جمع میشه
تجربه کاربری:
میخوایم پست هایی ک اجازه حذف داریم -> دکمه حذف داشته باشن -> اگه رو دکمه کلیک کردیم -> یه پنجره جدید باز بشه و بپرسه ک مطمینیم قراره پاکش کنیم (بخاطر اینکه اشتباهی دستمون نخوره و اینا) -> اگه تایید کردیم کامنت از دیتابیس حذف بشه -> پنجره جدید بسته بشه -> کامنت از صفحه حذف بشه بدون اینکه کل صفحه ریلود بشه
خب ب همین ترتیب جلو میریم :
میخوایم پست هایی ک اجازه حذف داریم -> دکمه حذف داشته باشن
HTML:
{% if post.author == current_user or comment.sender == current_user %}
    <div>
        <input class="btn btn-danger btn-sm m-1 delete_comment_modal" id="{{ comment.id }}" type="submit" value="حذف">
    </div>
  {% endif %}
-> اگه رو دکمه کلیک کردیم
به اسکریپتمون
JavaScript:
$('.delete_comment_modal').each(function (){
  $(this).click(deleteCommentModal);
});
اضافه میکنیم
یه پنجره جدید باز بشه و بپرسه ک مطمینیم قراره پاکش کنیم (بخاطر اینکه اشتباهی دستمون نخوره و اینا)
یه مدل خالی به html اضافه میکنیم :
HTML:
<div class="modal fade" id="deleteCommentModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="deleteModalLabel">از پاک شدن پست اطمینان داری؟</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-footer modal-delete-comment">
      </div>
    </div>
  </div>
</div>
حالا تو اسکریپتمون این مدل رو پر میکنیم و نشونش میدیم :
JavaScript:
function deleteCommentModal(e){
  var id = $(this).attr('id');
  var result = `
    <button type="button" class="btn btn-secondary" data-dismiss="modal">خروج</button>
    <button type="button" class="btn btn-danger" id="delete-comment-${id}" >حذف</button>
  `
  var modal = $('#deleteCommentModal');
  modal.find('.modal-delete-comment').html(result);
  modal.modal('show');
    }
اگه تایید کردیم کامنت از دیتابیس حذف بشه ->
اگه دکمه حذف تاچ شد با ajax اون درخواست حذف پستمونو میفرستیم :
JavaScript:
  var modal = $('#deleteCommentModal');
  modal.find('.modal-delete-comment').html(result);
  modal.modal('show');
  $( "#delete-comment-"+ id ).click(function() {
    $.ajax('/delete_comment/'+ id ,{
      type: 'POST',
      success: deleteComment
    });
  });
اینجا تو خط دوتا مونده به آخر میگیم ک تگه درخواستمون جواب گرفت -> تابع deleteComment صدا زده بشه
پنجره جدید بسته بشه -> کامنت از صفحه حذف بشه بدون اینکه کل صفحه ریلود بشه
اگه تابع deletecomment صدا زده بشه یعنی همه چی درسته پس کامنت رو پاک میکنیم و مادل رو میبندیم:
JavaScript:
function deleteComment(data){
  var comment_id = data.comment_id
  $('#comment-'+ comment_id).css('display', 'none')
  var modal = $('#deleteCommentModal');
  modal.modal('hide');
}
خب خیلی بد بنظر نمیرسه ... تست میکنیم و اگه مشکلی داشت حلش میکنیم :)
 

karen.m

کاربر فوق‌حرفه‌ای
عضو کادر مدیریت
مدیر داخلی
عضو مدیران انجمن
ارسال‌ها
620
امتیاز
9,853
نام مرکز سمپاد
شهید بهشتی
شهر
خرم آباد
سال فارغ التحصیلی
1397
اینستاگرام
یه کامیت نسبتا بزرگ داریم
قراره دایرکت/پ.خ اضافه کنیم
کار سختی بنظر نمیرسه ولی باگ عجیب غریب کم نداشتیم :)
اینکه فیچر دایرکت رو داشته باشیم ولی نوتیف نداشته باشیم خیلی رو مخه ... فعلا یسری نوتیف استاتیک مینویسم تا بعدا نوتیف داینامیک بنویسیم:)
خب مث سری های قبلی کارمون رو با تغیر تو دیتابیس شروع میکنیم:
خب ما یه جدول میخوایم ک دایرکت هامونو توش بریزیم:
Python:
class Direct(db.Model):
    id = db.Column(db.Integer, primary_key =True)
    sender_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    recipient_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    content = db.Column(db.Text, nullable=False)
    date_posted = db.Column(db.DateTime, default=datetime.now)

    def __repr__(self):
        return f'Direct -> "{self.content}" '
باید رابطه یوزر و دایرکت رو هم مشخص کنیم :
Python:
class User(db.Model, UserMixin):
  .......
    messages_sent = db.relationship('Direct',
                                        foreign_keys='Direct.sender_id',
                                        backref='direct_author', lazy='dynamic')
    messages_received = db.relationship('Direct',
                                        foreign_keys='Direct.recipient_id',
                                        backref='direct_recipient', lazy='dynamic')
.......
الان یجایی تو دیتابیسمون داریم ک میتونیم دایرکت هامونو توش ذخیره کنیم
ولی ب تنهایی بدردمون نمیخوره ... قسمت پ.خ سایت رو درنظر بگیرین:
۱- تاریخ شروع چت + شروع کننده + تعداد پیام ها + فرستنده آخرین پیام + تاریخ آخرین پیام رو نشون میده
۲- تاریخ seen خوردن هر گفتوگو توسط هرکدوم از یوزر ها هم یجایی سیو میشه ولی نشونش نمیده
واسه سیو کردن اینا یه جدول دیگه میسازیم :
Python:
class Conversation(db.Model):
    id = db.Column(db.Integer, primary_key =True)
    sender_id = db.Column(db.Integer)
    recipient_id = db.Column(db.Integer)
    date_created = db.Column(db.DateTime, default=datetime.now)
    date_midified = db.Column(db.DateTime, default=datetime.now)
    sender_last_seen = db.Column(db.DateTime, default=datetime(1900, 1, 1))
    recipient_last_seen = db.Column(db.DateTime, default=datetime(1900, 1, 1))

    def __repr__(self):
        return f'{self.sender_id} and {self.recipient_id}'
تو این تیبل خود پیام ها سیو نمیشن اطلاعاتی راجب دوطرف درگیر تو چت :))‌ یسری اطلاعات سیو میکنه
حالا رو این کلاس یسری متود هم تعریف میکنیم :
Python:
    def sender(self):
        user = User.query.filter_by(id=self.sender_id).first()
        return user

    def recipient(self):
        user = User.query.filter_by(id=self.recipient_id).first()
        return user
این دو متود واضح ان -> هرموقع صدا زده بشن شی یوزر فرستنده و گیرنده چت رو برمیگردونن
یسری اطلاعات دیگه هم هست ک مستقیما از مسیج های اون چت بدست میاد ک همه رو تو یه دیکشنری میریزم و با یه متود برمیگردونم (درواقع میخوام با یه query همه رو بدست بیارم) :
Python:
    def messages_info(self):
        messages = Direct.query.filter(
        and_(Direct.sender_id==self.sender_id, Direct.recipient_id==self.recipient_id)| \
        and_(Direct.sender_id==self.recipient_id ,Direct.recipient_id==self.sender_id)).order_by(
        Direct.date_posted.desc())
        count = messages.count()
        last_pm = messages.first()
        last_pm_sender_id = last_pm.sender_id
        last_pm_sender = User.query.filter_by(id=last_pm_sender_id).first()
        last_pm_sender_username = last_pm_sender.username
        last_pm_sender_date = last_pm.date_posted

        result = {'count':count, 'lastPmSender':last_pm_sender_username,
                'lastPmTime':last_pm_sender_date}
        return result
درنهایت میخوام یه متود بنویسم ک اگه روی چت صداش زدم بهم بگه چنتا پیام نخونده دارم :
Python:
    def new_messages(self, user):
        new_messages=0
        messages = Direct.query.filter(
        and_(Direct.sender_id==self.sender_id, Direct.recipient_id==self.recipient_id)| \
        and_(Direct.sender_id==self.recipient_id ,Direct.recipient_id==self.sender_id)).order_by(
        Direct.date_posted.desc()).all()
        if self.sender_id == user.id :
            last_seen = self.sender_last_seen or datetime(1900, 1, 1)
            for message in messages:
                if message.date_posted > last_seen:
                    new_messages +=1
                else:
                    break
        else:
            last_seen = self.recipient_last_seen or datetime(1900, 1, 1)
            for message in messages:
                if message.date_posted > last_seen:
                    new_messages +=1
                else:
                    break
        return new_messages
چنتا متود هم میخوام واسه کلاس یوزر بنویسم ک کارمونو راحت تر میکنه :
Python:
    def new_direct(self):
        result = 0
        conv = Conversation.query.filter(
        (Conversation.sender_id==self.id) | (Conversation.recipient_id==self.id)).all()
        for i in conv:
            result = result + i.new_messages(self)
        return result
اینجا کل تعداد کل پیام های نخونده رو پیدا میکنیم و برمیگردونیم
Python:
def conversations(self):
        conv = Conversation.query.filter(
        (Conversation.sender_id==self.id) | (Conversation.recipient_id==self.id))
        return conv
اینجا کل conversation های کاربر رو برمیگردونیم (هر سطر ازین جدول درواقع نشون دهنده وجود داشتن چت بین دو یوزره)
Python:
def check_conversation(self, user_id):
        conv = Conversation.query.filter(
        and_(Conversation.sender_id==self.id, Conversation.recipient_id==user_id)| \
        and_(Conversation.sender_id==user_id ,Conversation.recipient_id==self.id)).first()
        return conv
فک کنین من میخوام ب شما پیام بدم -> اول باید چک کنم ک عایا بین من و شما قبلا گفتوگویی صورت گرفته یا نه -> اگه گرفته پس باید همونو ادامه بدیم و اگه نه باید یکی جدید بسازیم
به هرحال این متود میخواد همینو بهمون بفهمونه:)
با دیتابیس دیگه کاری نداریم ... تو فایلی ک form هامونو نوشتیم یه form جدید درست میکنیم برای ارسال دایرکت :
Python:
class DirectForm(FlaskForm):
    content = TextAreaField('ارسال پیام', validators=[DataRequired()])
    submit = SubmitField('ارسال')
حالا بریم روی route هامون کار کنیم -> همه فعالیت های مربوط ب دایرکت ها ب لاگین نیاز دارن:
تو قدم اول باید یجوری بتونیم پیام جدید بنویسیم :
Python:
@app.route("/send_message/<recipient>", methods=['POST'])
@login_required
def send_direct(recipient):
    user = User.query.filter_by(username=recipient).first_or_404()
    form = DirectForm()
    conv = current_user.check_conversation(user.id)
    if conv :
        conv.date_midified = datetime.now()
    else:
        conv = Conversation(sender_id=current_user.id, recipient_id=user.id)
        db.session.add(conv)

    if conv.sender() == current_user:
        conv.sender_last_seen = datetime.now()
    else:
        conv.recipient_last_seen = datetime.now()

    if form.validate_on_submit():
        msg = Direct(direct_author=current_user, direct_recipient=user, content=form.content.data)
        db.session.add(msg)
        db.session.commit()
        flash('پیام ارسال شد','success')
        return redirect(url_for('conversation', username=recipient, unread=1))
فرایند اینطوره ک مقصد دایرکت رو مشخص میکنیم -> چک میکنیم ک قبلا مکالمه داشتن یا نه -> اگه داشتن ... همونو آپدیت میکنیم و اگه نداشتن یکی میسازیم -> باید تشخیص بدیم ما طرف اول مکالمه هستیم یا دوم تا بتونیم لست سین رو آپدیت کنیم -> یک شی از کلاس دایرکت درست میکنیم -> دایرکت رو سیو میکنیم -> کاربر رو هدایت میکنیم به صفحه چت

حالا ک میتونیم دایرکت بفرستیم باید بتونیم پاکش هم کنیم :
Python:
@app.route("/delete_message/<int:message_id>", methods=['POST'])
@login_required
def delete_message(message_id):
    message = Direct.query.get_or_404(message_id)
    if message.direct_author == current_user :
        conv = current_user.check_conversation(message.recipient_id)
        if conv.messages_info()['count'] == 1:
            db.session.delete(message)
            db.session.delete(conv)
        db.session.commit()
        return jsonify(status='ok', message_id=message.id)

    abort(403)
قسمت پاک کردن رو یکم جلو تر تو فرانت کامل میکنم
نکته اش اینه ک اگه یه وقت همه پیاما پاک شدن باید گفتوگوی مختص اون پیام هم پاک بشه وگرنه نمیتونه conv نمیتونه هیچ دایرکتی رو پیدا کنه و ارور میدیم
حالا صفحه پیام رو مینویسیم:
Python:
@app.route('/conversation/<username>')
@login_required
def conversation(username):
    form = DirectForm()
    user = User.query.filter_by(username=username).first_or_404()
    conv = current_user.check_conversation(user.id)
    if conv :
        new_messages = conv.new_messages(current_user)
        messages = Direct.query.filter(
        and_(Direct.sender_id==current_user.id, Direct.recipient_id==user.id)| \
        and_(Direct.sender_id==user.id ,Direct.recipient_id==current_user.id)).order_by(
        Direct.date_posted)
        page_count = int(len(messages.all())/25) if len(messages.all())%25==0 else int(len(messages.all())/25) +1
        option = request.args.get('unread', 0, type=int )
        if option == 0 :
            page = request.args.get('page', 1, type=int)
        else:
            if len(messages.all())%25 < new_messages:
                page = request.args.get('page', page_count-1, type=int)
            else:
                page = request.args.get('page', page_count, type=int)
        messages=messages.paginate(page=page, per_page=25)

        if conv.sender() == current_user:
            last_seen = conv.sender_last_seen or datetime(1900, 1, 1)
            if page == page_count:
                conv.sender_last_seen = datetime.now()
                db.session.commit()
            else:
                if messages.items[-1].date_posted > last_seen:
                    conv.sender_last_seen = messages.items[-1].date_posted
                    db.session.commit()
        else:
            last_seen = conv.recipient_last_seen or datetime(1900, 1, 1)
            if page == page_count:
                conv.recipient_last_seen = datetime.now()
                db.session.commit()
            else:
                if messages.items[-1].date_posted > last_seen:
                    conv.recipient_last_seen = messages.items[-1].date_posted
                    db.session.commit()
        return render_template('conversation.html', messages=messages,
        form=form, title=user.username, last_seen=last_seen)
    return render_template('conversation.html', form=form, title=user.username)
اینجا صفحه چت دوتا کاربر رو نشون میدیم و تعداد پیامای نخونده کاربر رو آپدیت میکنیم
امکان ارسال پیام جدید هم تو همین صفحه وجود داره
و در آخر باید صفحه ای روبسازیم ک لیست چت هامونو نشون بده :
Python:
@app.route('/messages')
@login_required
def messages():
    current_user.last_direct_read_time = datetime.now()
    db.session.commit()
    page = request.args.get('page', 1, type=int)
    messages = current_user.conversations().order_by(Conversation.date_midified.desc()).paginate(
                                                                                    page=page, per_page=25)

    return render_template('messages.html', messages=messages, title="پیام ها" )
چت هارو به تمپلیتمون فرستادیم و چت های جدیدتر میان بالاتر
نهایتا باید تمپلیت هارو بنویسیم
تمپلیت لیست چت ها :
HTML:
{% extends "base.html" %}
{% block content %}

<div style="direction:ltr;text-align: left" class="mb-4">
  {% for page_num in messages.iter_pages(left_edge=2, right_edge=2, left_current=1, right_current=2) %}
        {% if page_num %}
          {% if messages.page == page_num %}
            <a class="btn btn-info" href="{{ url_for('explore', page=page_num) }}">{{ page_num }}</a>
          {% else %}
            <a class="btn btn-outline-info" href="{{ url_for('explore', page=page_num) }}">{{ page_num }}</a>
          {% endif %}
        {% else %}
          ...
        {% endif %}
      {% endfor %}
    </div>
  {% for message in messages.items %}
    {% if message.recipient() != current_user %}
    <div class="card text-center ml-4 mr-4">
      <div class="card-body">
        <div class="row">
          <div class="col mt-2"  align="center">
          <img class="rounded-circle users-img" style="float:right;margin-top:0px" src="{{ url_for('static', filename='profile_pics/'+ message.recipient().image_file) }}">
          </div>
          <div class="col ml-4" align="center">
            <a href="{{url_for('conversation', username= message.recipient().username, unread=1)}}" class="btn btn-outline-info btn-lg" style="margin-top:25px">{{message.recipient().username}}</a>
          </div>
          <div class="col" align="center">
            {% if message.new_messages(current_user)!=0 %}
            <h3 style="margin-top:20px">
          <a href="{{url_for('conversation', username= message.recipient().username, unread=1)}}" class="btn btn btn-danger" style="border-radius:30px"> {{message.new_messages(current_user)}} </a>
          پیام جدید
        </h3>
        {% else %}
        <h4 style="margin-top:20px">شما پیام جدیدی ندارید :)  </h4>
        {% endif %}
          </div>
          <div class="col text-center" align="center">
            <div class="mt-2">
              آخرین پیام در تاریخ :
              <br>
              {{message.messages_info()['lastPmTime']}}
              <br>
              <br>
              توسط :
              {{message.messages_info()['lastPmSender']}}

            </div>
          </div>
        </div>
      </div>
      <div class="card-footer text-muted">
      اولین پیام در تاریخ :  {{message.date_created}}

      </div>
    </div>
    {% else %}
    <div class="card text-center ml-4 mr-4">
      <div class="card-body">
        <div class="row">
          <div class="col mt-2"  align="center">
          <img class="rounded-circle users-img" style="float:right;margin-top:0px" src="{{ url_for('static', filename='profile_pics/'+ message.sender().image_file) }}">
          </div>
          <div class="col ml-4" align="center">
            <a href="{{url_for('conversation', username= message.sender().username, unread=1)}}" class="btn btn-outline-info btn-lg" style="margin-top:25px">{{message.sender().username}}</a>
          </div>
          <div class="col" align="center">
            {% if message.new_messages(current_user)!=0 %}
            <h3 style="margin-top:20px">
          <a href="{{url_for('conversation', username= message.sender().username, unread=1)}}" class="btn btn btn-danger" style="border-radius:30px"> {{message.new_messages(current_user)}} </a>
          پیام جدید
        </h3>
        {% else %}
        <h4 style="margin-top:20px">شما پیام جدیدی ندارید :)  </h4>
        {% endif %}
          </div>
          <div class="col text-center" align="center">
            <div class="mt-2">
              آخرین پیام در تاریخ :
              <br>
              {{message.messages_info()['lastPmTime']}}
              <br>
              <br>
              توسط :
              {{message.messages_info()['lastPmSender']}}

            </div>
          </div>
        </div>
      </div>
      <div class="card-footer text-muted">
      اولین پیام در تاریخ :  {{message.date_created}}

      </div>
    </div>
    {% endif %}

  <br>
  {% endfor %}
  <div style="direction:ltr;text-align: center;margin-top:20px;">
    {% for page_num in messages.iter_pages(left_edge=2, right_edge=2, left_current=1, right_current=2) %}
          {% if page_num %}
            {% if messages.page == page_num %}
              <a class="btn btn-info" href="{{ url_for('explore', page=page_num) }}">{{ page_num }}</a>
            {% else %}
              <a class="btn btn-outline-info" href="{{ url_for('explore', page=page_num) }}">{{ page_num }}</a>
            {% endif %}
          {% else %}
            ...
          {% endif %}
        {% endfor %}
      </div>

{% endblock content%}
اطلاعات مختلفی ک قبلا پردازش کردیم رو نشون داریم
و تمپلیت مکالمه :
HTML:
{% extends "base.html" %}
{% block content %}
{% if messages %}
<div style="direction:ltr;text-align: left">
  {% for page_num in messages.iter_pages(left_edge=2, right_edge=2, left_current=1, right_current=2) %}
        {% if page_num %}
          {% if messages.page == page_num %}
            <a class="btn btn-info" href="{{ url_for('conversation', username=title, page=page_num) }}">{{ page_num }}</a>
          {% else %}
            <a class="btn btn-outline-info" href="{{ url_for('conversation', username=title, page=page_num) }}">{{ page_num }}</a>
          {% endif %}
        {% else %}
          ...
        {% endif %}
      {% endfor %}
    </div>
  {% for message in messages.items %}
  {% if message.direct_author == current_user %}
  <div class="card mr-4" id="message-{{message.id}}"style="margin-top:30px;">
    <div class="card-body">
      <img class="rounded-circle article-img float-right" src="{{ url_for('static', filename='profile_pics/'+ message.direct_author.image_file) }}">
  <h5 class="card-title">
    <a class="mr-4 float-right" href="{{url_for('user', username=message.direct_author.username)}}">{{ message.direct_author.username }}</a>
  </h5>
  <h6 class="card-subtitle mb-2 text-muted">{{ message.date_posted.strftime('%Y-%m-%d') }}</h6>
  {% if message.date_posted > last_seen %}
  <span class="badge badge-warning float-right" id="new">جدید</span>
  {% endif %}
  {% if message.direct_author == current_user %}
      <div>
          <input class="btn btn-danger m-1 delete_message_modal" id="dm-{{ message.id }}" type="submit" value="حذف">
      </div>
      {% else %}
      <br>
    {% endif %}
  <hr>
  <p class="card-text article-content " >{{ message.content|safe }}</p>
  </div>
  </div>
  {% else %}
  <div class="card ml-4" id="message-{{message.id}}"style="margin-top:30px;">
    <div class="card-body">
      <img class="rounded-circle article-img float-right" src="{{ url_for('static', filename='profile_pics/'+ message.direct_author.image_file) }}">
  <h5 class="card-title">
    <a class="mr-4 float-right" href="{{url_for('user', username=message.direct_author.username)}}">{{ message.direct_author.username }}</a>
  </h5>
  <h6 class="card-subtitle mb-2 text-muted">{{ message.date_posted.strftime('%Y-%m-%d') }}</h6>
  {% if message.date_posted > last_seen %}
  <span class="badge badge-warning float-right"id="new">جدید</span>
  {% endif %}
  {% if message.direct_author == current_user %}
      <div>
          <input class="btn btn-danger m-1 delete_message_modal" id="dm-{{ message.id }}" type="submit" value="حذف">
      </div>
      {% else %}
      <br>
    {% endif %}
  <hr>
  <p class="card-text article-content " >{{ message.content|safe }}</p>
  </div>
  </div>
  {% endif %}
  {% endfor %}
  {% endif %}
  <div id="last"></div>
  <div class="content-section mt-4">
    <legend class="border-bottom mb-4">ارسال پیام</legend>

    <form method="POST" action="{{ url_for('send_direct', recipient=title) }}">
        {{ form.hidden_tag() }}
        <filfieldset class="form-group ">
            <div class="form-group">
                {% if form.content.errors %}
                {{ form.content(id="summernote", class="form-control form-control-lg is-invalid") }}
                  <div class="invalid-feedback">
                      {% for error in form.content.errors %}
                        <span>{{ error }}</span>
                      {% endfor %}
                  </div>
                {% else %}
                    {{ form.content(id="summernote", class="form-control form-control-lg") }}
                {% endif %}
            </div>
            <div class="modal-footer">
                {{ form.submit(class="btn btn-outline-info") }}
            </div>
    </form>
      </div>
{% if messages %}
  <div style="direction:ltr;text-align: center;margin-top:20px;">
    {% for page_num in messages.iter_pages(left_edge=2, right_edge=2, left_current=1, right_current=2) %}
          {% if page_num %}
            {% if messages.page == page_num %}
              <a class="btn btn-info" href="{{ url_for('conversation', username=title, page=page_num) }}">{{ page_num }}</a>
            {% else %}
              <a class="btn btn-outline-info" href="{{ url_for('conversation', username=title, page=page_num) }}">{{ page_num }}</a>
            {% endif %}
          {% else %}
            ...
          {% endif %}
        {% endfor %}
      </div>
    {% endif %}
<script>
$('#new')[0].scrollIntoView(true);
</script>


<div class="modal fade" id="deleteMessageModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="deleteModalLabel">از پاک شدن پست اطمینان داری؟</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-footer modal-delete-message">
      </div>
    </div>
  </div>
</div>


{% endblock content%}
میخوام اگه پیام جدید داشتیم اتوماتیک تا خود پیام اسکرول بشه
پس یه اسکریپت اینترنال هم میزارم :
HTML:
<script>
$('#new')[0].scrollIntoView(true);
</script>
و قسمت پیام هارو میخوام ب navbar/همون منو بالا سایت اضافه کنم
پس به base.html ک همه صفحات ازش ارث بری دارن اضافه میکنم :
HTML:
          <li class="nav-item">
            <div class="row">
                  <a href="{{url_for('messages')}}" class="btn btn btn-danger" style="border-radius:30px"> {{current_user.new_direct()}} </a>
                <div class="col">
                  <a class="nav-link" href="{{ url_for('messages') }}">پیام ها</a>
                </div>
            </div>
          </li>
آها قسمت پاک کردن پیام داشت یادم میرفت :)
عمل پاک کردن رو ajax انجامم میده
تو فایل اسکریپتامون :
JavaScript:
$('.delete_message_modal').each(function (){
  $(this).click(deleteMessageModal);
});
اگه دکمه کلیک شد :
JavaScript:
function deleteMessageModal(e){
  var fid = $(this).attr('id');
  var id = fid.slice(3)
  var result = `
    <button type="button" class="btn btn-secondary" data-dismiss="modal">خروج</button>
    <button type="button" class="btn btn-danger" id="delete-message-${id}" >حذف</button>
  `
  var modal = $('#deleteMessageModal');
  modal.find('.modal-delete-message').html(result);
  modal.modal('show');
  $( "#delete-message-"+ id ).click(function() {
    $.ajax('/delete_message/'+ id ,{
      type: 'POST',
      success: deleteMessage
    });
  });
}
مادل مربوط ب حذف رو نشون بده و اگه تایید شد در خواست ajax رو بفرست :
JavaScript:
function deleteMessage(data){
  var message_id = data.message_id
  $('#message-'+ message_id).css('display', 'none')
  var modal = $('#deleteMessageModal');
  modal.modal('hide');
}
اگه اوکی بود پیام رو پاک کن بدون اینکه صفحه رو ریلود کنی + مادل رو هم پاک مخفی کن
--------

بنظر میاد همه چی درست کار کنه ..... ولی دهنم سرویس شد خدایی :))
نفسگیر بود
فک نمیکنم مشکلی باشه ... اگه بود بهم بگین
شب خوش :)
 

karen.m

کاربر فوق‌حرفه‌ای
عضو کادر مدیریت
مدیر داخلی
عضو مدیران انجمن
ارسال‌ها
620
امتیاز
9,853
نام مرکز سمپاد
شهید بهشتی
شهر
خرم آباد
سال فارغ التحصیلی
1397
اینستاگرام
امروز زهرا از اهمیت امکان تغییر پسوورد داشت میگفت و یهو یادم افتاد ک یادم رفته بود :)
کار آسونیه:
تو فرم هامون یه فرم جدید اضافه میکنیم (درواقع همون جاهایی ان ک پسوورد قدیم و جدید رو میدیم )
Python:
class ChangePwdForm(FlaskForm):
    old_password = PasswordField('کلمه عبور قدیمی',
            validators=[DataRequired()])
    new_password = PasswordField('کلمه عبور جدید',
            validators=[DataRequired()])
    confirm_new_password = PasswordField('تکرار کلمه عبور جدید',
            validators=[DataRequired(), EqualTo('new_password')])
    submit = SubmitField('تغیر کلمه عبور')
دارم از ماژول flask-wtf وسه ساخت این فرما استفاده میکنم
تو داکیومنت flask-wtf نوشته ک اینجوری میتونم custom_validation درست کنم:
Python:
class ChangePwdForm(FlaskForm):
..........
    def validate_old_password(self, old_password):
        if not(bcrypt.check_password_hash(current_user.password,old_password.data)):
            flash('کلمه عبور قدیمی شما اشتباه است !!!', 'danger')
            raise ValidationError('کلمه عبور قدیمی شما اشتباه است !!!')

    def validate_new_password(self, new_password):
        if not(self.confirm_new_password.data == new_password.data):
            flash('کلمه عبور جدید مطابقت ندارد !!!', 'danger')
            raise ValidationError('کلمه عبور جدید مطابقت ندارد !!!')
حالا تو روت هامون یه مسیر جدید واسه تغییر پسوورد درست میکنیم:
Python:
@app.route('/change_pwd', methods=['POST'])
def change_pwd():
    form = ChangePwdForm()
    if form.validate_on_submit():
        new_hashed_pass = bcrypt.generate_password_hash(form.new_password.data).decode('utf-8')
        current_user.password = new_hashed_pass
        db.session.commit()
        logout_user()
        flash('میتونید با رمزجدید login کنید :) ', 'success')
        return redirect(url_for('login'))
    else:
        return redirect(url_for('account'))
اینجور گفتیم ک اگه اون فرم valid بود --> پسورد جدید رو هش کن و جای پسورد قدیمی بنویسش تو دیتابیس
تو مرحله بعد لازمبه یه دکمه بزاریم ک اگه زدیمش بتونیم پسوورد جدید وارد کنیم :
من دکمه رو تو قسمت حساب کاربری درست میکنم:
HTML:
      <button type="button" class="btn btn-danger float-right" data-toggle="modal" data-target="#newPassModal">
        تغییر کلمه عبور
      </button>
      <br><br>
و این دکمه قراره یه مادل رو باز کنه :
HTML:
    <div class="modal fade" id="newPassModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLongTitle" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">تغیر کلمه عبور</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form method="POST" action="{{ url_for('change_pwd') }}">
            {{ form2.hidden_tag() }}
            <filfieldset class="form-group ">
                <div class="form-group">
                    {{ form2.old_password.label }}
                    {% if form2.old_password.errors %}
                    {{ form2.content(class="form-control form-control-lg is-invalid") }}
                      <div class="invalid-feedback">
                          {% for error in form2.old_password.errors %}
                            <span>{{ error }}</span>
                          {% endfor %}
                      </div>
                    {% else %}
                        {{ form2.old_password(class="form-control form-control-lg") }}
                    {% endif %}
                    {{ form2.new_password.label }}
                    {% if form2.new_password.errors %}
                    {{ form2.new_password(class="form-control form-control-lg is-invalid") }}
                      <div class="invalid-feedback">
                          {% for error in form2.new_password.errors %}
                            <span>{{ error }}</span>
                          {% endfor %}
                      </div>
                    {% else %}
                        {{ form2.new_password(class="form-control form-control-lg") }}
                    {% endif %}
                    {{ form2.confirm_new_password.label }}
                    {% if form2.confirm_new_password.errors %}
                    {{ form2.confirm_new_password(class="form-control form-control-lg is-invalid") }}
                      <div class="invalid-feedback">
                          {% for error in form2.confirm_new_password.errors %}
                            <span>{{ error }}</span>
                          {% endfor %}
                      </div>
                    {% else %}
                        {{ form2.confirm_new_password(class="form-control form-control-lg") }}
                    {% endif %}
                </div>
                <div class="modal-footer">
                  <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">انصراف</button>
                  {{ form2.submit(class="btn btn-outline-info") }}
                </div>
        </form>
      </div>
    </div>
  </div>
</div>
اینطور ک بنظر میرسه و باید کار کنه:)
 

Jomana

گربه بالدار!
ارسال‌ها
210
امتیاز
1,303
نام مرکز سمپاد
فرزانگان ۱
شهر
بندرعباس
سال فارغ التحصیلی
1403
بچه ها جمع کنین بیاین درمورد اهمیت نوتیفیکشن بگین :-"
+
خسته نباشی ^^
 

abb

Zahra
کنکوری ۹۹
ارسال‌ها
8
امتیاز
35
نام مرکز سمپاد
فرزانگان یک
شهر
مشهد
سال فارغ التحصیلی
1399
یک عدد لینک در صفحه ورود وجود دارد به نام:رمز عبورتان را فراموش کردید؟
و فکر کنم یا دکوریه:)) یا واسه گمراه کردن انسان ها از راه راست تعبیه شده
نمیشه یکمی کاربردی تر شه تا من که اشتباهی دکمه خروج رو زدم بتونم از یه سوراخی وارد شم؟:))
باتشکر
 

karen.m

کاربر فوق‌حرفه‌ای
عضو کادر مدیریت
مدیر داخلی
عضو مدیران انجمن
ارسال‌ها
620
امتیاز
9,853
نام مرکز سمپاد
شهید بهشتی
شهر
خرم آباد
سال فارغ التحصیلی
1397
اینستاگرام
و فکر کنم یا دکوریه:)) یا واسه گمراه کردن انسان ها از راه راست تعبیه شده
:)):)) :))
گمراه کردن انسان ها از راه راست عالی بود :))
واقعیتش اینه ک روز اول اون "رمز عبورتان را فراموش کردید؟" رو نوشتم و بعدش یادم رفت دیگه کامش کنم :)
نمیشه یکمی کاربردی تر شه تا من که اشتباهی دکمه خروج رو زدم بتونم از یه سوراخی وارد شم؟:))
بطور کلی واسه بازیابی با ایمیل دو راه تو ذهن من هست:
۱. یوزر ایمیلش رو بنویسه -> یه ایمیل حاوی رمز جدید رو دریافت کنه -> لاگین کنه و رمز رو عوض کنه
۲. یوزر ایمیلش رو بنویسه -> یه ایمیل حاوی لینک بازیابی دریافت کنه -> رو لینک کلیک کنه و رمز جدید رو وارد کنه

خیلی فرقی ندارن -> اولی یکم آسون تره و دومی یکم باحال تره
من روش دوم رو بیشتر دوس دارم
لاجیک قضیه اینه ک من باید واسه کسی ک رمزشو یادش نیست یه توکن درست کنم -> این توکن تا مدت مشخصی (تو این مورد من ۲ ساعت گذاشتم) معتبر باشه -> توکن رو ایمیل کنم -> کاربر با توکن وارد میشه(ینی رو لینک میزنه) -> وارد صفحه عوض کردن پسورد میشه و رمزش رو عوض میکنه
من باید واسه کسی ک رمزشو یادش نیست یه توکن درست کنم
ایده اولیه این بود ک من یه جدول جدید تو دیتابیس درست کنم و بعد تو این جدول توکن و تاریخ انقضا :) و ... رو سیو کنم
یکم تو نت گشتم و یه راه باحال تر یافتم :) :
Screenshot_20200808_025201.png
فهمیدین عجب چیز باحالی بود؟ :) :) یه دیتا رو از توکنی استخراج کردم ک ۱۰۰ ثانیه تاریخ مصرف:) داره
با همین شروع کنیم
من واسه راحت شدن واسه کلاس یوزر دوتا متود مینویسم :
Python:
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
class User(db.Model, UserMixin):
    .........
   
    def get_reset_token(self):
        s = Serializer(app_secret_key, 7200)
        return s.dumps({'user_id' : self.id})

    @staticmethod
    def verify_reset_token(token):
        s = Serializer(app_secret_key)
        try:
            user_id = s.loads(token)['user_id']
        except:
            return None
        return User.query.get(user_id)
متود اول روی یه یوزر اجرا میشه و یه توکن واسش میسازه که تا ۷۲۰۰ ثانیه اعتبار داره
متود دوم اعتبار توکن رو چک میکنه :
۱. دکوراتور staticmethod واسه این نوشتم چون به پراپرتی های یوزر احتیاج ندارم
۲.چون ممکنه توکنی ک کاربر وارد میکنه ولید نباشه از try-except استفاده کردم
حالا دو تا فرم درست میکنم -> یکی واسه درخواست توکن و یکی واسه تغیر پسوورد:
Python:
class RequestResetForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('درخواست ایمیل بازیابی !')

    def validate_email(self, email):
        user = User.query.filter_by(email=email.data).first()
        if user is None:
            flash('حسابی با ایمیل شما وجود ندارد. ابتدا باید ثبت‌نام کنید !', 'danger')
            raise ValidationError('حسابی با ایمیل شما وجود ندارد. ابتدا باید ثبت‌نام کنید !')

class ResetPasswordForm(FlaskForm):
    password = PasswordField('کلمه عبور جدید',
            validators=[DataRequired()])
    confirm_password = PasswordField('تکرار کلمه عبور جدید',
            validators=[DataRequired()])
    submit = SubmitField('تغیر کلمه عبور')

    def validate_password(self, password):
        if not(self.confirm_password.data == password.data):
            flash('کلمه عبور جدید مطابقت ندارد !!!', 'danger')
            raise ValidationError('کلمه عبور جدید مطابقت ندارد !!!')
خب قراره توکن رو ایمیل کنیم ولی چجوری؟
یه gmail به اسم qtalk.manage درست کردم و قراره با پروتکل smtp ایمیل بفرستیم :
از ماژول flask-mail استفاده میکنم :
Python:
from flask_mail import Mail
from Qtalk.config import (app_secret_key, db_password, db_name, db_server,
                                db_username, mail_password, mail_username)

app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = mail_username
app.config['MAIL_PASSWORD'] = mail_password
mail = Mail(app)
الان ایمیلمون کافیگ شد فقط واسه اینکه رمزم رو تو گیت نزارم اونو تو فایل کانفیگ تعریف کردم

الان تو روت ها یدونه تابع مینویسم ک یه یوزر بگیره و توکن رو واسش ایمیل کنه :
Python:
def send_reset_email(user):
    token = user.get_reset_token()
    msg = Message('درخواست بازیابی حساب کاربری',
                  sender='[email protected]',
                  recipients=[user.email])
    msg.body = f'''برای بازیابی حساب خود از لینک زیر استفاده کنید:
                        {url_for('reset_token', token=token, _external=True)}
                        این پیام تا ۲ ساعت معتبر است.
                        اگر شما درخواست بازیابی حساب خودرا نفرستاده اید -> این پیام را نادیده بگیرید!
                        '''
    mail.send(msg)
با روت ها بازم کار داریم :


Python:
@app.route("/reset_password", methods=['POST'])
def reset_request():
    if current_user.is_authenticated:
        return redirect(url_for('home'))
    form = RequestResetForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        send_reset_email(user)
        flash('ایمیل بازیابی حساب شما ارسال شده است !', 'info')
        return redirect(url_for('login'))
    return redirect(url_for('login'))
یوزر فرم بازیابی پسورد رو پر میکنه ->‌ ایمیلش چک میشه -> معتبر بود توکن واسش ایمیل میشه
حالا فرض کنیم یوزر ایمیلش رو باز میکنه و روی توکن کلیک میکنه .... بریم اونو بنویسیم:
Python:
@app.route("/reset_password/<token>", methods=['GET', 'POST'])
def reset_token(token):
    if current_user.is_authenticated:
        return redirect(url_for('home'))
    user = User.verify_reset_token(token)
    if user is None:
        flash('لینک بازیابی حساب شما معتبر نمیباشد !!!', 'warning')
        return redirect(url_for('login'))
    form = ResetPasswordForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user.password = hashed_password
        db.session.commit()
        flash('میتوانید با پسورد جدید لاگین کنید !!!', 'success')
        return redirect(url_for('login'))
    return render_template('reset_token.html', title='بازیابی کلمه عبور', form=form)
اگه متود get باشه -> صفحه وارد کردن پسورد جدید لود میشه
اگه متود پست باشه و فرم ولید باشه -> رمز عوض میشه و کاربر به سمت ورود هدایت میشه
اگه متود پست باشه و ولید نباشه -> خطاها رو باید بهشون نشون بدیم و تو همون صفحه نگهش دارییم
واضحه ک باشد reset_token.html رو به تمپلیت هامون اضافه کنیم:
HTML:
{% extends "base.html" %}
{% block content %}

  <div class="card">
  <div class="card-header">
    از اینجا میتونین اکانت خودتونو بازیابی کنین :)
  </div>
  <div class="card-body">
    <blockquote class="blockquote mb-0">
      <form method="POST" action="">
          {{ form.hidden_tag() }}
          <filfieldset class="form-group ">
              <div class="form-group">
                <p class="mb-4 mt-4">  کلمه عبور جدید :</p>

                  {% if form.password.errors %}
                  {{ form.password(class="form-control form-control-lg is-invalid") }}
                    <div class="invalid-feedback">
                        {% for error in form.password.errors %}
                          <span>{{ error }}</span>
                        {% endfor %}
                    </div>
                  {% else %}
                      {{ form.password(class="form-control form-control-lg ") }}
                  {% endif %}
                  <p class="mt-4 mb-4">  تکرار کلمه عبور جدید :‌</p>

                  {% if form.confirm_password.errors %}
                  {{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
                    <div class="invalid-feedback">
                        {% for error in form.confirm_password.errors %}
                          <span>{{ error }}</span>
                        {% endfor %}
                    </div>
                  {% else %}
                      {{ form.confirm_password(class="form-control form-control-lg") }}
                  {% endif %}
              </div>
              <div class="modal-footer">
                  {{ form.submit(class="btn btn-outline-info") }}
              </div>
      </form>
    </blockquote>
  </div>
</div>
{% endblock content%}
و نهایتا میخوام وقتی کسی رو دکمه فراموشی رمز کلیک کرد یه مادل باز بشه و ازش ایمیل بخواد :
به تمپلت لاگین و first_page اضافه میکنم :
HTML:
<a href="#" data-toggle="modal" data-target="#forgotPasswordModal">
رمز عبورتان را فراموش کردید؟
</a>
                     
                     
  <div class="modal fade" id="forgotPasswordModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog" role="document">
          <div class="modal-content">
            <div class="modal-header">
              <h5 class="modal-title" id="exampleModalLongTitle">  بازیابی حساب کاربری</h5>
              <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span>
              </button>
            </div>
            <div class="modal-body">
              <form method="POST" action="{{ url_for('reset_request') }}">
                  {{ form2.hidden_tag() }}
                  <filfieldset class="form-group ">
                      <div class="form-group">
                        ایمیل خودرا وارد کنید.
                          {% if form2.email.errors %}
                          {{ form2.email(class="form-control form-control-lg is-invalid mt-2") }}
                            <div class="invalid-feedback">
                                {% for error in form2.email.errors %}
                                  <span>{{ error }}</span>
                                {% endfor %}
                            </div>
                          {% else %}
                              {{ form2.email(class="form-control form-control-lg mt-2") }}
                          {% endif %}
                      </div>
                      <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">انصراف</button>
                        {{ form2.submit(class="btn btn-outline-info") }}
                      </div>
              </form>
            </div>
          </div>
        </div>
      </div>
بنظر میاد دیگه تمومه و باید کار کنه
شب خوش :)
 
بالا