Compare commits
	
		
			4 commits
		
	
	
		
			local-acco
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a0402feab0 | ||
|   | 596847ca0a | ||
|   | 228ff85e6a | ||
|   | 302fe4289a | 
					 13 changed files with 158 additions and 99 deletions
				
			
		|  | @ -8,7 +8,7 @@ from io import BytesIO | ||||||
| from PIL import Image | from PIL import Image | ||||||
| import base64 | import base64 | ||||||
| 
 | 
 | ||||||
| from acmsite.models import Link, Officer, User, Event | from acmsite.models import EventCheckin, Link, Officer, User, Event | ||||||
| from acmsite import models | from acmsite import models | ||||||
| 
 | 
 | ||||||
| from .forms import EventForm, LinkForm, OfficerForm | from .forms import EventForm, LinkForm, OfficerForm | ||||||
|  | @ -155,6 +155,28 @@ def update_create_event(id): | ||||||
| 
 | 
 | ||||||
|     return redirect(url_for("admin.events")) |     return redirect(url_for("admin.events")) | ||||||
| 
 | 
 | ||||||
|  | @bp.route("/event/<string:id>/checkins") | ||||||
|  | @login_required | ||||||
|  | def event_checkins(id): | ||||||
|  |     if not current_user.is_admin: | ||||||
|  |         flash("Unauthorized") | ||||||
|  |         return redirect(url_for("dashboard.home")) | ||||||
|  | 
 | ||||||
|  |     event = db.session.execute(db.select(Event).where(Event.id == id)).scalar_one_or_none() | ||||||
|  |     if event is None: | ||||||
|  |         flash("Invalid event") | ||||||
|  |         return redirect(url_for("admin.events")) | ||||||
|  |     checkins = db.session.execute(db.select(EventCheckin).where(EventCheckin.event == | ||||||
|  |                                                      id).join(User)).scalars()  | ||||||
|  | 
 | ||||||
|  |     processed_checkins = [] | ||||||
|  |     for c in checkins: | ||||||
|  |         user = db.session.execute(db.select(User).where(User.id == c.user)).scalar_one_or_none() | ||||||
|  |         processed_checkins.append({"name": f"{user.first_name} {user.last_name}", "email": user.email}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     return render_template("admin/checkins.html", checkins=processed_checkins,e=event) | ||||||
|  | 
 | ||||||
| @bp.route("/links") | @bp.route("/links") | ||||||
| @login_required | @login_required | ||||||
| def links(): | def links(): | ||||||
|  |  | ||||||
|  | @ -17,7 +17,8 @@ class LinkForm(FlaskForm): | ||||||
| 
 | 
 | ||||||
| class OfficerForm(FlaskForm): | class OfficerForm(FlaskForm): | ||||||
|     position = SelectField("Position", choices=["President", "Vice President", |     position = SelectField("Position", choices=["President", "Vice President", | ||||||
|                                                 "Treasurer", "Secretary", "PR Chair", "Hackathon Manager 1", |                                                 "Treasurer", "Secretary", "PR Chair",  | ||||||
|  |                                                 "Event Coordinator", "Hackathon Manager 1", | ||||||
|                                                 "Hackathon Manager 2", "System Administrator"], |                                                 "Hackathon Manager 2", "System Administrator"], | ||||||
|                            validators=[DataRequired()]) |                            validators=[DataRequired()]) | ||||||
|     term_start = DateField("Term Start", validators=[DataRequired()]) |     term_start = DateField("Term Start", validators=[DataRequired()]) | ||||||
|  |  | ||||||
|  | @ -10,12 +10,9 @@ bp = Blueprint('auth', __name__, url_prefix='/auth') | ||||||
| 
 | 
 | ||||||
| from acmsite import oauth | from acmsite import oauth | ||||||
| 
 | 
 | ||||||
| @bp.route("/login") |  | ||||||
| def login(): |  | ||||||
|     return render_template('login.html') |  | ||||||
| 
 | 
 | ||||||
| @bp.route('/oauth') | @bp.route('/login') | ||||||
| def oauth_redirect(): | def login(): | ||||||
|     return oauth.azure.authorize_redirect(url_for('auth.oauth2_callback', |     return oauth.azure.authorize_redirect(url_for('auth.oauth2_callback', | ||||||
|                                                   _external=True)) |                                                   _external=True)) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| from werkzeug.security import generate_password_hash, check_password_hash |  | ||||||
| from flask import Blueprint, render_template, request, flash, redirect, url_for |  | ||||||
| from flask_login import current_user, login_required |  | ||||||
| 
 | 
 | ||||||
| from acmsite.dashboard.forms import PasswordForm | from datetime import datetime | ||||||
|  | from flask import Blueprint, current_app, flash, redirect, render_template, url_for | ||||||
|  | from flask_login import login_required, current_user | ||||||
|  | from ulid import ulid | ||||||
|  | 
 | ||||||
|  | from acmsite.models import Event, EventCheckin | ||||||
| from acmsite import db | from acmsite import db | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -11,29 +13,35 @@ bp = Blueprint('dashboard', __name__, url_prefix='/dashboard') | ||||||
| @bp.route("/") | @bp.route("/") | ||||||
| @login_required | @login_required | ||||||
| def home(): | def home(): | ||||||
|     form = PasswordForm() |     now = datetime.now() | ||||||
|     return render_template('dashboard.html', form=form) |     events = db.session.execute(db.select(Event).where(Event.start_time < now, | ||||||
|  |                                                        Event.end_time > | ||||||
|  |                                                        now)).scalars() | ||||||
|  |     return render_template('dashboard.html', events=events) | ||||||
| 
 | 
 | ||||||
| @bp.route("/change_password", methods=["POST"]) | @bp.route("/checkin/<string:event_id>") | ||||||
| @login_required | @login_required | ||||||
| def change_password(): | def checkin(event_id): | ||||||
|     form = PasswordForm(request.form) |     # actually first check if the event even exists | ||||||
|  |     event = db.get_or_404(Event, event_id) | ||||||
|  |     # first check if this user has already checked in | ||||||
|  |     checkins = db.session.execute(db.select(EventCheckin).where(EventCheckin.user == | ||||||
|  |                                                      current_user.id, | ||||||
|  |                                                      EventCheckin.event | ||||||
|  |                                                                 ==event_id)).scalar_one_or_none() | ||||||
| 
 | 
 | ||||||
|     if form.validate_on_submit(): |     current_app.logger.debug(checkins) | ||||||
|         current_password = request.form.get("current_password") |     if checkins is None: | ||||||
|         new_password = request.form.get("new_password") |         # There's not a checkin already, let's create one! | ||||||
|         password_confirm = request.form.get("password_confirm") |         check = EventCheckin( | ||||||
| 
 |                 id = ulid(), | ||||||
|         if new_password == password_confirm: |                 user = current_user.id, | ||||||
|             if current_password == '' and current_user.password == '': |                 event = event_id) | ||||||
|                 current_user.password = generate_password_hash(new_password) |         db.session.add(check) | ||||||
|                 flash("Password set successfully.") |  | ||||||
|             elif check_password_hash(current_user.password, current_password): |  | ||||||
|                 current_user.password = generate_password_hash(new_password) |  | ||||||
|                 flash("Password updated successfully.") |  | ||||||
|             else: |  | ||||||
|                 flash("Incorrect password.") |  | ||||||
|         else: |  | ||||||
|             flash("Passwords do not match!") |  | ||||||
|         db.session.commit() |         db.session.commit() | ||||||
|  | 
 | ||||||
|  |         flash(f"Checked in to {event.name} successfully") | ||||||
|  |         return redirect(url_for("dashboard.home")) | ||||||
|  |     else: | ||||||
|  |         flash(f"You've already checked in to {event.name}") | ||||||
|         return redirect(url_for("dashboard.home")) |         return redirect(url_for("dashboard.home")) | ||||||
|  |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| from flask_wtf import FlaskForm |  | ||||||
| from wtforms.fields import PasswordField |  | ||||||
| from wtforms.validators import DataRequired |  | ||||||
| 
 |  | ||||||
| class PasswordForm(FlaskForm): |  | ||||||
|     current_password = PasswordField('Current Password') |  | ||||||
|     new_password = PasswordField('New Password', validators=[DataRequired()]) |  | ||||||
|     password_confirm = PasswordField('Confirm New Password', |  | ||||||
|                                      validators=[DataRequired()]) |  | ||||||
|  | @ -80,6 +80,12 @@ class Event(db.Model): | ||||||
|                 "end_time": self.end_time.isoformat(), |                 "end_time": self.end_time.isoformat(), | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  | class EventCheckin(db.Model): | ||||||
|  |     __tablename__ = "acm_checkins" | ||||||
|  |     id = Column(String, primary_key=True) | ||||||
|  |     user = Column(String, ForeignKey("acm_users.id"), nullable=False) | ||||||
|  |     event = Column(String, ForeignKey("acm_events.id"), nullable=False) | ||||||
|  | 
 | ||||||
| class Link(db.Model): | class Link(db.Model): | ||||||
|     __tablename__ = "acm_links" |     __tablename__ = "acm_links" | ||||||
|     id = Column(String, primary_key=True) |     id = Column(String, primary_key=True) | ||||||
|  |  | ||||||
|  | @ -74,12 +74,12 @@ very friendly. You can find a list of our upcoming events and meetings <a href=" | ||||||
|    </div>  |    </div>  | ||||||
|    <div class="col">  |    <div class="col">  | ||||||
|        <div class="card" id="events-coordinator"> |        <div class="card" id="events-coordinator"> | ||||||
|            <img class="card-img-top" id="Events Coordinator-img" src="{{ url_for('static', |            <img class="card-img-top" id="Event Coordinator-img" src="{{ url_for('static', | ||||||
|                                           filename='img/officers/placeholder.png') |                                           filename='img/officers/placeholder.png') | ||||||
|                                           }}" alt="Events Coordinator">  |                                           }}" alt="Event Coordinator">  | ||||||
|            <div class="card-body"> |            <div class="card-body"> | ||||||
|                 <h5 class="card-title">Events Coordinator</h5>  |                 <h5 class="card-title">Events Coordinator</h5>  | ||||||
|                 <p class="card-text" id="Events Coordinator-name">Unavailable</p> |                 <p class="card-text" id="Event Coordinator-name">Unavailable</p> | ||||||
|            </div> |            </div> | ||||||
|        </div> |        </div> | ||||||
|    </div>   |    </div>   | ||||||
|  |  | ||||||
							
								
								
									
										35
									
								
								acmsite/templates/admin/checkins.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								acmsite/templates/admin/checkins.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | {% extends "admin/admin-layout.html" %} | ||||||
|  | {% import "bootstrap5/form.html" as wtf %} | ||||||
|  | 
 | ||||||
|  | {% block app_content %} | ||||||
|  | <h1>Checkins for `{{ e.name }}`</h1>  | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | {% for c in checkins %} | ||||||
|  |     {{ c.__dict__ }} | ||||||
|  | {% endfor %} | ||||||
|  | 
 | ||||||
|  | <table class="table table-striped"> | ||||||
|  |     <thead> | ||||||
|  |        <tr> | ||||||
|  |             <th>Name</th> | ||||||
|  |             <th>Email</th> | ||||||
|  |        </tr>  | ||||||
|  |     </thead> | ||||||
|  |     <tbody> | ||||||
|  |         {% for c in checkins %} | ||||||
|  |         <tr> | ||||||
|  |             <td>{{ c.name }}</td> | ||||||
|  |             <td>{{ c.email }}</td> | ||||||
|  |         </tr> | ||||||
|  |         {% endfor %} | ||||||
|  |     </tbody>  | ||||||
|  | </table> | ||||||
|  | 
 | ||||||
|  | <h3>Copy-pastable list for CampusGroups</h3> | ||||||
|  | {% for c in checkins %} | ||||||
|  | {{ c.email }}, | ||||||
|  | {% endfor %} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | {% endblock %} | ||||||
|  | @ -40,6 +40,11 @@ | ||||||
|                             <li class="dropdown-item"> |                             <li class="dropdown-item"> | ||||||
|                                 <a href="#deleteModal" |                                 <a href="#deleteModal" | ||||||
|                                    data-bs-toggle="modal" data-id="{{ e.id }}">Delete Event</a> |                                    data-bs-toggle="modal" data-id="{{ e.id }}">Delete Event</a> | ||||||
|  |                             </li> | ||||||
|  |                             <li class="dropdown-item"> | ||||||
|  |                                 <a href="{{ url_for('admin.event_checkins', | ||||||
|  |                                          id=e.id) }}">Event Checkins</a> | ||||||
|  |                             </li> | ||||||
|                         </ul> |                         </ul> | ||||||
|                     </div> |                     </div> | ||||||
|                 </td> |                 </td> | ||||||
|  |  | ||||||
|  | @ -4,6 +4,19 @@ | ||||||
| 
 | 
 | ||||||
| <h1>Welcome back, {{ current_user.first_name }}!</h1> | <h1>Welcome back, {{ current_user.first_name }}!</h1> | ||||||
| 
 | 
 | ||||||
|  | {% if events %} | ||||||
|  | 
 | ||||||
|  | <p>The following events are available for check-in:</p> | ||||||
|  | {% for e in events %} | ||||||
|  | <h5>{{ e.name }} <a class="btn btn-primary" href="{{ url_for('dashboard.checkin', event_id=e.id) | ||||||
|  |         }}">Check in</a></h5> | ||||||
|  | {% endfor %} | ||||||
|  | {% else %} | ||||||
|  | <p>There are no events available for check-in.</p> | ||||||
|  | {% endif %} | ||||||
|  | 
 | ||||||
|  | <hr/> | ||||||
|  | 
 | ||||||
| <p>For a list of upcoming events, take a look at our <a href="{{ | <p>For a list of upcoming events, take a look at our <a href="{{ | ||||||
|                                                               url_for('main.events') |                                                               url_for('main.events') | ||||||
|                                                               }}">events |                                                               }}">events | ||||||
|  | @ -13,46 +26,4 @@ | ||||||
|                                                           unless you're an |                                                           unless you're an | ||||||
|                                                           officer!</p> |                                                           officer!</p> | ||||||
|      |      | ||||||
| <button type="button" class="btn btn-secondary" data-bs-toggle="modal" |  | ||||||
|                                   data-bs-target="#passwordModal">Change or Set |  | ||||||
|                                   Local Password</button> |  | ||||||
| <!-- Modals --> |  | ||||||
| <div class="modal" id="passwordModal" tabindex="-1" aria-labelledby="passwordModalLabel" |  | ||||||
|                                                 aria-hidden="true"> |  | ||||||
|       <div class="modal-dialog"> |  | ||||||
|     <div class="modal-content"> |  | ||||||
|       <div class="modal-header"> |  | ||||||
|         <h1 class="modal-title fs-5" id="passwordModalLabel">Change Password</h1> |  | ||||||
|         <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |  | ||||||
|       </div> |  | ||||||
|       <form class="form" id="edit-form" action="/dashboard/change_password" role="form" method="post"> |  | ||||||
|           <div class="modal-body"> |  | ||||||
|           {{ form.csrf_token }} |  | ||||||
|             <div class="form-floating mb-3 required"> |  | ||||||
|                 {{ form.current_password(class="form-control") }} |  | ||||||
|                 {{ form.current_password.label() }} |  | ||||||
|             </div> |  | ||||||
|            <div class="row"> |  | ||||||
|                 <div class="col"> |  | ||||||
|                     <div class="form-floating mb-3 required"> |  | ||||||
|                         {{ form.new_password(class="form-control") }} |  | ||||||
|                         {{ form.new_password.label() }} |  | ||||||
|                     </div>  |  | ||||||
|                 </div> |  | ||||||
|                 <div class="col"> |  | ||||||
|                     <div class="form-floating mb-3 required"> |  | ||||||
|                         {{ form.password_confirm(class="form-control") }} |  | ||||||
|                         {{ form.password_confirm.label() }} |  | ||||||
|                     </div>                     |  | ||||||
|                 </div>  |  | ||||||
|             </div>  |  | ||||||
|          </div> |  | ||||||
|           <div class="modal-footer"> |  | ||||||
|             <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |  | ||||||
|             <button type="submit" class="btn btn-primary" id="edit-save">Save changes</button> |  | ||||||
|           </div> |  | ||||||
|       </form> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
| {% endblock app_content %} | {% endblock app_content %} | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ | ||||||
|                     {% endif %}   |                     {% endif %}   | ||||||
|                     {{ render_nav_item('auth.logout', 'Logout') }} |                     {{ render_nav_item('auth.logout', 'Logout') }} | ||||||
|                 {% else %} |                 {% else %} | ||||||
|                     {{ render_nav_item('auth.login', 'Login') }} |                     {{ render_nav_item('auth.login', 'Login with WPI') }} | ||||||
|                 {% endif %} |                 {% endif %} | ||||||
|             </ul> |             </ul> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -1,12 +0,0 @@ | ||||||
| {% extends "layout.html" %} |  | ||||||
| 
 |  | ||||||
| {% block app_content %} |  | ||||||
| <h1>Login Methods</h1> |  | ||||||
| 
 |  | ||||||
| <div> |  | ||||||
| <a class="btn btn-primary" href="{{ url_for('auth.oauth_redirect') }}">Login with WPI</a> |  | ||||||
| </div> |  | ||||||
| <div class="mt-1 mb-3"> |  | ||||||
| <a class="btn btn-secondary" href="">Login with Local Account</a> |  | ||||||
| </div> |  | ||||||
| {% endblock app_content %} |  | ||||||
							
								
								
									
										35
									
								
								migrations/versions/53a76e988b5a_add_event_checkins.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								migrations/versions/53a76e988b5a_add_event_checkins.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | """add event checkins | ||||||
|  | 
 | ||||||
|  | Revision ID: 53a76e988b5a | ||||||
|  | Revises: 300f24071c14 | ||||||
|  | Create Date: 2024-08-25 15:18:22.451548 | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | from alembic import op | ||||||
|  | import sqlalchemy as sa | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # revision identifiers, used by Alembic. | ||||||
|  | revision = '53a76e988b5a' | ||||||
|  | down_revision = '300f24071c14' | ||||||
|  | branch_labels = None | ||||||
|  | depends_on = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def upgrade(): | ||||||
|  |     # ### commands auto generated by Alembic - please adjust! ### | ||||||
|  |     op.create_table('acm_checkins', | ||||||
|  |     sa.Column('id', sa.String(), nullable=False), | ||||||
|  |     sa.Column('user', sa.String(), nullable=False), | ||||||
|  |     sa.Column('event', sa.String(), nullable=False), | ||||||
|  |     sa.ForeignKeyConstraint(['event'], ['acm_events.id'], ), | ||||||
|  |     sa.ForeignKeyConstraint(['user'], ['acm_users.id'], ), | ||||||
|  |     sa.PrimaryKeyConstraint('id') | ||||||
|  |     ) | ||||||
|  |     # ### end Alembic commands ### | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def downgrade(): | ||||||
|  |     # ### commands auto generated by Alembic - please adjust! ### | ||||||
|  |     op.drop_table('acm_checkins') | ||||||
|  |     # ### end Alembic commands ### | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue