def duplicate(event_id=None): """Event duplication. This page does not duplicates the event but create a new event form with field prefilled with antoher event data. When user will click on "Submit", it will act as a duplication. :param int event_id: Primary key of the event to duplicate. """ if not current_user.can_create_events(): flash("Accès restreint, rôle insuffisant.", "error") return redirect(url_for("event.index")) event = Event.query.get(event_id) if event == None: flash("Pas d'événement à dupliquer", "error") return redirect(url_for("event.index")) form = EventForm(obj=event) form.setup_leader_actions() form.duplicate_photo.data = event_id return render_template( "editevent.html", form=form, event=event, action=url_for("event.manage_event"), )
def autocomplete_users(): """API endpoint to list users for autocomplete. At least 2 characters are required to make a name search. :param string q: Search string. :param int l: Maximum number of returned items. :return: A tuple: - JSON containing information describe in AutocompleteUserSchema - HTTP return code : 200 - additional header (content as JSON) :rtype: (string, int, dict) """ if not current_user.can_create_events(): abort(403) q = request.args.get("q") if q is None or (len(q) < 2): found_users = [] else: limit = request.args.get("l", type=int) or 8 found_users = find_users_by_fuzzy_name(q, limit) content = json.dumps(AutocompleteUserSchema(many=True).dump(found_users)) return content, 200, {"content-type": "application/json"}
def autocomplete_users(): if not current_user.can_create_events(): abort(403) q = request.args.get('q') if q is None or (len(q) < 2): found_users = [] else: found_users = find_users_by_fuzzy_name(q) content = json.dumps(AutocompleteUserSchema(many=True).dump(found_users)) return content, 200, {'content-type': 'application/json'}
def filter_hidden_events(query): """Update a SQLAlchemy query object with a filter that removes Event with a status that the current use is not allowed to see - Moderators can see all events - Normal users cannot see 'Pending' events - Activity supervisors can see 'Pending' events for the activities that they supervise - Leaders can see the events that they lead :param query: The original query :type query: :py:class:`sqlalchemy.orm.query.Query` :return: The filtered query :rtype: :py:class:`sqlalchemy.orm.query.Query` """ # Display pending events only to relevant persons if not current_user.is_authenticated: # Not logged users see no pending event query = query.filter(Event.status != EventStatus.Pending) elif current_user.is_moderator(): # Admin see all pending events (no filter) pass else: # Regular user can see non Pending query_filter = Event.status != EventStatus.Pending # If user is a supervisor, it can see Pending events of its activities if current_user.is_supervisor(): # Supervisors can see all sup activities = current_user.get_supervised_activities() activities_ids = [a.id for a in activities] supervised = Event.activity_types.any( ActivityType.id.in_(activities_ids)) query_filter = or_(query_filter, supervised) # If user can create event, it can see its pending events if current_user.can_create_events(): lead = Event.leaders.any(id=current_user.id) query_filter = or_(query_filter, lead) # After filter construction, it is applied to the query query = query.filter(query_filter) return query
def duplicate(event_id=None): if not current_user.can_create_events(): flash('Accès restreint, rôle insuffisant.', 'error') return redirect(url_for('event.index')) event = Event.query.get(event_id) if event == None: flash('Pas d\'évènement à dupliquer', 'error') return redirect(url_for('event.index')) choices = activity_choices(event.activity_types, event.leaders) form = EventForm(choices, obj=event) form.type.data = str(event.activity_types[0].id) form.duplicate_photo.data = event_id return render_template('editevent.html', conf=current_app.config, form=form, action=url_for('event.manage_event'))
def duplicate(event_id=None): if not current_user.can_create_events(): flash("Accès restreint, rôle insuffisant.", "error") return redirect(url_for("event.index")) event = Event.query.get(event_id) if event == None: flash("Pas d'évènement à dupliquer", "error") return redirect(url_for("event.index")) form = EventForm(event, obj=event) form.setup_leader_actions() form.duplicate_photo.data = event_id return render_template( "editevent.html", conf=current_app.config, form=form, event=event, action=url_for("event.manage_event"), )
def manage_event(event_id=None): if not current_user.can_create_events(): flash("Accès restreint, rôle insuffisant.", "error") return redirect(url_for("event.index")) event = Event.query.get(event_id) if event_id is not None else Event() form = EventForm(event, CombinedMultiDict((request.files, request.form))) if not form.is_submitted(): if event_id is None: form = EventForm(event) form.set_default_description() elif not form.is_submitted(): form = EventForm(event, obj=event) form.setup_leader_actions() return render_template("editevent.html", conf=current_app.config, event=event, form=form) # Fetch existing readers leaders minus removed ones previous_leaders = [] tentative_leaders = [] has_removed_leaders = False seen_ids = set() for action in form.leader_actions: leader_id = int(action.data["leader_id"]) seen_ids.add(leader_id) leader = User.query.get(leader_id) if leader is None or not leader.can_create_events(): flash("Encadrant invalide") continue previous_leaders.append(leader) if action.data["delete"]: has_removed_leaders = True else: tentative_leaders.append(leader) if event_id is None: # Event is not created yet, get current leaders from submitted form form.set_current_leaders(previous_leaders) form.update_choices(event) else: # Protect ourselves against form data manipulation # We should have a form entry for all existing leaders for existing_leader in event.leaders: if not existing_leader.id in seen_ids: flash("Données incohérentes.", "error") return redirect(url_for("event.index")) # Add new leader new_leader_id = int(form.add_leader.data) if new_leader_id > 0: leader = User.query.get(new_leader_id) if leader is None or not leader.can_create_events(): flash("Encadrant invalide") else: tentative_leaders.append(leader) # Check that the main leader still exists event.main_leader_id = int(form.main_leader_id.data) if not any(l.id == event.main_leader_id for l in tentative_leaders): flash("Un encadrant responsable doit être défini") return render_template("editevent.html", conf=current_app.config, event=event, form=form) # The 'Update leaders' button has been clicked # Do not process the remainder of the form if form.update_leaders.data: # Check that the set of leaders is valid for current activities if not has_removed_leaders or accept_event_leaders( event, tentative_leaders): form.set_current_leaders(tentative_leaders) form.update_choices(event) form.setup_leader_actions() if not event_id is None: event.leaders = tentative_leaders db.session.add(event) db.session.commit() return render_template("editevent.html", conf=current_app.config, event=event, form=form) if not form.validate(): return render_template("editevent.html", conf=current_app.config, event=event, form=form) form.populate_obj(event) # The 'Update event button has been clicked' # Custom validators valid = True if not event.starts_before_ends(): flash("La date de début doit être antérieure à la date de fin") valid = False if event.num_online_slots > 0: if not event.has_defined_registration_date(): flash( "Les date de début ou fin d'ouverture ou de fermeture d'inscription ne peuvent être nulles." ) valid = False else: if not event.opens_before_closes(): flash( "Les inscriptions internet doivent ouvrir avant de terminer" ) valid = False if not event.closes_before_starts(): # May need to be relaxed for special events. See #159 flash( "Les inscriptions internet doivent se terminer avant le début de l'événement" ) valid = False elif not event.opens_before_ends(): flash( "Les inscriptions internet doivent ouvrir avant la fin de l'événement" ) valid = False if event.num_slots < event.num_online_slots: flash( "Le nombre de places internet ne doit pas dépasser le nombre de places total" ) valid = False elif event.num_online_slots < 0: flash("Le nombre de places par internet ne peut être négatif") valid = False if not valid: return render_template("editevent.html", conf=current_app.config, event=event, form=form) event.set_rendered_description(event.description) # For now enforce single activity type activity_type = ActivityType.query.filter_by(id=event.type).first() has_new_activity = activity_type not in event.activity_types if has_new_activity: event.activity_types.clear() event.activity_types.append(activity_type) # We have changed activity or removed a leader # Check that there is a valid leader if has_new_activity or has_removed_leaders: if not accept_event_leaders(event, tentative_leaders): return render_template("editevent.html", conf=current_app.config, event=event, form=form) event.leaders = tentative_leaders # We have to save new event before add the photo, or id is not defined db.session.add(event) db.session.commit() # If no photo is sen, we don't do anything, especially if a photo is # already existing if form.photo_file.data is not None: event.save_photo(form.photo_file.data) db.session.add(event) db.session.commit() elif form.duplicate_photo.data != "": duplicated_event = Event.query.get(form.duplicate_photo.data) if duplicated_event != None: event.photo = duplicated_event.photo db.session.add(event) db.session.commit() if event_id is None: # This is a new event, send notification to supervisor send_new_event_notification(event) return redirect(url_for("event.view_event", event_id=event.id))
def events(): page = int(request.args.get('page')) size = int(request.args.get('size')) # Initialize query query = Event.query # Display pending events only to relevant persons if not current_user.is_authenticated: # Not logged users see no pending event query = query.filter(Event.status != EventStatus.Pending) elif current_user.is_admin(): # Admin see all pending events (no filter) pass else: # Regular user can see non Pending filter = Event.status != EventStatus.Pending # If user is a supervisor, it can see Pending events of its activities if current_user.is_supervisor(): # Supervisors can see all sup activities = current_user.get_supervised_activities() activities_ids = [a.id for a in activities] supervised = Event.activity_types.any( ActivityType.id.in_(activities_ids)) filter = or_(filter, supervised) # If user can create event, it can see its pending events if current_user.can_create_events(): lead = Event.leaders.any(id=current_user.id) filter = or_(filter, lead) # After filter construction, it is applied to the query query = query.filter(filter) # Process all filters. # All filter are added as AND i = 0 while f'filters[{i}][field]' in request.args: value = request.args.get(f'filters[{i}][value]') field = request.args.get(f'filters[{i}][field]') if field == 'activity_type': filter = Event.activity_types.any(short=value) if field == 'end': filter = Event.end >= value if field == 'status': filter = Event.status == value query = query.filter(filter) # Get next filter i += 1 # Process first sorter only if f'sorters[0][field]' in request.args: sort_field = request.args.get('sorters[0][field]') sort_dir = request.args.get('sorters[0][dir]') order = desc(sort_field) if sort_dir == 'desc' else sort_field query = query.order_by(order) paginated_events = query.paginate(page, size, False) data = EventSchema(many=True).dump(paginated_events.items) response = {'data': data, "last_page": paginated_events.pages} return json.dumps(response), 200, {'content-type': 'application/json'}
def manage_event(event_id=None): """Event creation and modification page. If an ``event_id`` is given, it is a modification of an existing event. :param int event_id: Primary key of the event to manage. """ if not current_user.can_create_events(): flash("Accès restreint, rôle insuffisant.", "error") return redirect(url_for("event.index")) event = Event.query.get(event_id) if event_id is not None else Event() if event is not None and not event.has_edit_rights(current_user): flash("Accès restreint.", "error") return redirect(url_for("event.index")) current_status = event.status form = EventForm(CombinedMultiDict((request.files, request.form))) if not form.is_submitted(): if event_id is None: form = EventForm() form.set_default_values() else: form = EventForm(obj=event) form.setup_leader_actions() return render_template("editevent.html", event=event, form=form) # Get current activites from form tentative_activities = form.current_activities() requires_activity = form.current_event_type().requires_activity if requires_activity and len(tentative_activities) == 0: flash( f"Un événement de type {form.current_event_type().name} requiert au moins une activité", "error", ) return render_template("editevent.html", event=event, form=form) # Fetch existing readers leaders minus removed ones previous_leaders = [] tentative_leaders = [] has_removed_leaders = False for action in form.leader_actions: leader_id = int(action.data["leader_id"]) leader = User.query.get(leader_id) if leader is None or not leader.can_create_events(): flash("Encadrant invalide") continue previous_leaders.append(leader) if action.data["delete"]: has_removed_leaders = True else: tentative_leaders.append(leader) # Set current leaders from submitted form form.set_current_leaders(previous_leaders) form.update_choices() # Update activities only # Do not process the remainder of the form if int(form.update_activity.data): # Check that the set of leaders is valid for current activities validate_event_leaders( tentative_activities, previous_leaders, form.multi_activities_mode.data ) return render_template("editevent.html", event=event, form=form) # Add new leader new_leader_id = int(form.add_leader.data) if new_leader_id > 0: leader = User.query.get(new_leader_id) if leader is None or not leader.can_create_events(): flash("Encadrant invalide") else: tentative_leaders.append(leader) # Check that the main leader still exists try: main_leader_id = int(form.main_leader_id.data) except (TypeError, ValueError): main_leader_id = None if not any(l.id == main_leader_id for l in tentative_leaders): flash("Un encadrant responsable doit être défini") return render_template("editevent.html", event=event, form=form) # Update leaders only # Do not process the remainder of the form if has_removed_leaders or int(form.update_leaders.data): # Check that the set of leaders is valid for current activities if validate_event_leaders( tentative_activities, tentative_leaders, form.multi_activities_mode.data, ): form.set_current_leaders(tentative_leaders) form.update_choices() form.setup_leader_actions() return render_template("editevent.html", event=event, form=form) # The 'Update event' button has been clicked # Populate object, run custom validators if not form.validate(): return render_template("editevent.html", event=event, form=form) # Do not populate the real event as errors may still be raised and we do not want # SQLAlchemy to flush the temp data trial_event = Event() form.populate_obj(trial_event) if not validate_dates_and_slots(trial_event): return render_template("editevent.html", event=event, form=form) has_new_activity = any(a not in event.activity_types for a in tentative_activities) tentative_leaders_set = set(tentative_leaders) existing_leaders_set = set(event.leaders) has_changed_leaders = any(tentative_leaders_set ^ existing_leaders_set) # We have added a new activity or added/removed leaders # Check that the leaders are still valid if has_new_activity or has_changed_leaders: if not validate_event_leaders( tentative_activities, tentative_leaders, form.multi_activities_mode.data, ): return render_template("editevent.html", event=event, form=form) # If event has not been created yet use current activities to check rights if event_id is None: event.activity_types = tentative_activities # Check that we have not removed leaders that we don't have the right to removed_leaders = existing_leaders_set - tentative_leaders_set for removed_leader in removed_leaders: if not event.can_remove_leader(current_user, removed_leader): flash( f"Impossible de supprimer l'encadrant: {removed_leader.full_name()}", "error", ) return render_template("editevent.html", event=event, form=form) # All good! Apply changes form.populate_obj(event) event.activity_types = tentative_activities event.leaders = tentative_leaders # Remove registration associated to leaders (#327) if has_changed_leaders: for leader in event.leaders: leader_registrations = event.existing_registrations(leader) if any(leader_registrations): flash( f"{leader.full_name()} a été désinscrit(e) de l'événement car il/elle a été ajouté(e) comme encadrant(e)." ) for registration in leader_registrations: event.registrations.remove(registration) event.set_rendered_description(event.description) # Update tags (brute option: purge all and create new) event.tag_refs.clear() for tag in form.tag_list.data: event.tag_refs.append(EventTag(tag)) # We have to save new event before add the photo, or id is not defined db.session.add(event) db.session.commit() # If no photo is sent, we don't do anything, especially if a photo is # already existing if form.photo_file.data is not None: event.save_photo(form.photo_file.data) db.session.add(event) db.session.commit() elif form.duplicate_photo.data != "": duplicated_event = Event.query.get(form.duplicate_photo.data) if duplicated_event != None: event.photo = duplicated_event.photo db.session.add(event) db.session.commit() if event_id is None: # This is a new event, send notification to supervisor send_new_event_notification(event) else: # This is a modified event and the status has changed from Confirmed to Cancelled. # then, a notification is sent to supervisors if ( current_status == EventStatus.Confirmed and event.status == EventStatus.Cancelled ): send_cancelled_event_notification(current_user.full_name(), event) return redirect(url_for("event.view_event", event_id=event.id))
def manage_event(event_id=None): if not current_user.can_create_events(): flash("Accès restreint, rôle insuffisant.", "error") return redirect(url_for("event.index")) event = Event.query.get(event_id) if event_id is not None else Event() form = EventForm(CombinedMultiDict((request.files, request.form))) if not form.is_submitted(): if event_id is None: form = EventForm() form.set_default_description() else: form = EventForm(obj=event) form.setup_leader_actions() return render_template("editevent.html", conf=current_app.config, event=event, form=form) # Get current activites from form tentative_activities = form.current_activities() # Fetch existing readers leaders minus removed ones previous_leaders = [] tentative_leaders = [] has_removed_leaders = False for action in form.leader_actions: leader_id = int(action.data["leader_id"]) leader = User.query.get(leader_id) if leader is None or not leader.can_create_events(): flash("Encadrant invalide") continue previous_leaders.append(leader) if action.data["delete"]: has_removed_leaders = True else: tentative_leaders.append(leader) # Set current leaders from submitted form form.set_current_leaders(previous_leaders) form.update_choices() # Update activities only # Do not process the remainder of the form if int(form.update_activity.data): # Check that the set of leaders is valid for current activities validate_event_leaders(tentative_activities, previous_leaders, form.multi_activities_mode.data) return render_template("editevent.html", conf=current_app.config, event=event, form=form) # Add new leader new_leader_id = int(form.add_leader.data) if new_leader_id > 0: leader = User.query.get(new_leader_id) if leader is None or not leader.can_create_events(): flash("Encadrant invalide") else: tentative_leaders.append(leader) # Check that the main leader still exists event.main_leader_id = int(form.main_leader_id.data) if not any(l.id == event.main_leader_id for l in tentative_leaders): flash("Un encadrant responsable doit être défini") return render_template("editevent.html", conf=current_app.config, event=event, form=form) # Update leaders only # Do not process the remainder of the form if has_removed_leaders or int(form.update_leaders.data): # Check that the set of leaders is valid for current activities if validate_event_leaders( tentative_activities, tentative_leaders, form.multi_activities_mode.data, ): form.set_current_leaders(tentative_leaders) form.update_choices() form.setup_leader_actions() return render_template("editevent.html", conf=current_app.config, event=event, form=form) # The 'Update event' button has been clicked # Populate object, run custom validators if not form.validate(): return render_template("editevent.html", conf=current_app.config, event=event, form=form) form.populate_obj(event) if not validate_dates_and_slots(event): return render_template("editevent.html", conf=current_app.config, event=event, form=form) has_new_activity = any(a not in event.activity_types for a in tentative_activities) tentative_leaders_set = set(tentative_leaders) existing_leaders_set = set(event.leaders) has_changed_leaders = any(tentative_leaders_set ^ existing_leaders_set) # We have added a new activity or added/removed leaders # Check that the leaders are still valid if has_new_activity or has_changed_leaders: if not validate_event_leaders( tentative_activities, tentative_leaders, form.multi_activities_mode.data, ): return render_template("editevent.html", conf=current_app.config, event=event, form=form) # If event has not been created yet user current activities to check rights if event_id is None: event.activity_types = tentative_activities # Check that we have not removed leaders that we don't have the right to removed_leaders = existing_leaders_set - tentative_leaders_set for removed_leader in removed_leaders: if not event.can_remove_leader(current_user, removed_leader): flash( "Impossible de supprimer l'encadrant: {}".format( removed_leader.full_name()), "error", ) return render_template("editevent.html", conf=current_app.config, event=event, form=form) # All good! Apply changes event.activity_types = tentative_activities event.leaders = tentative_leaders event.set_rendered_description(event.description) # We have to save new event before add the photo, or id is not defined db.session.add(event) db.session.commit() # If no photo is sent, we don't do anything, especially if a photo is # already existing if form.photo_file.data is not None: event.save_photo(form.photo_file.data) db.session.add(event) db.session.commit() elif form.duplicate_photo.data != "": duplicated_event = Event.query.get(form.duplicate_photo.data) if duplicated_event != None: event.photo = duplicated_event.photo db.session.add(event) db.session.commit() if event_id is None: # This is a new event, send notification to supervisor send_new_event_notification(event) return redirect(url_for("event.view_event", event_id=event.id))
def manage_event(event_id=None): if not current_user.can_create_events(): flash('Accès restreint, rôle insuffisant.', 'error') return redirect(url_for('event.index')) event = Event.query.get(event_id) if event_id is not None else Event() choices = activity_choices(event.activity_types, event.leaders) form = EventForm(choices, CombinedMultiDict((request.files, request.form))) if not form.is_submitted(): form = EventForm(choices, obj=event) if not event_id is None: form.type.data = str(event.activity_types[0].id) return render_template('editevent.html', conf=current_app.config, form=form) form.populate_obj(event) # Validate dates valid = True if not event.starts_before_ends(): flash('La date de début doit être antérieure à la date de fin') valid = False if event.num_online_slots > 0: if not event.has_defined_registration_date(): flash( "Les date de début ou fin d\'ouverture ou de fermeture d'inscription ne peuvent être nulles." ) valid = False else: if not event.opens_before_closes(): flash( 'Les inscriptions internet doivent ouvrir avant de terminer' ) valid = False if not event.opens_before_ends(): flash( 'Les inscriptions internet doivent ouvrir avant la fin de l\'événement' ) valid = False if event.num_slots < event.num_online_slots: flash( 'Le nombre de places internet ne doit pas dépasser le nombre de places total' ) valid = False elif event.num_online_slots < 0: flash('Le nombre de places par internet ne peut être négatif') valid = False if not valid: return render_template('editevent.html', conf=current_app.config, form=form) event.set_rendered_description(event.description) # Only set ourselves as leader if there weren't any if not any(event.leaders): event.leaders.append(current_user) # For now enforce single activity type activity_type = ActivityType.query.filter_by(id=event.type).first() if activity_type not in event.activity_types: event.activity_types.clear() event.activity_types.append(activity_type) # We are changing the activity, check that there is a valid leader if not current_user.is_moderator() and not event.has_valid_leaders(): flash('Encadrant invalide pour cette activité') return render_template('editevent.html', conf=current_app.config, form=form) # We have to save new event before add the photo, or id is not defined db.session.add(event) db.session.commit() # If no photo is sen, we don't do anything, especially if a photo is # already existing if (form.photo_file.data is not None): event.save_photo(form.photo_file.data) db.session.add(event) db.session.commit() elif form.duplicate_photo.data != "": duplicated_event = Event.query.get(form.duplicate_photo.data) if duplicated_event != None: event.photo = duplicated_event.photo db.session.add(event) db.session.commit() if event_id is None: # This is a new event, send notification to supervisor send_new_event_notification(event) return redirect(url_for('event.view_event', event_id=event.id))
def events(): page = int(request.args.get("page")) size = int(request.args.get("size")) # Initialize query query = Event.query # Display pending events only to relevant persons if not current_user.is_authenticated: # Not logged users see no pending event query = query.filter(Event.status != EventStatus.Pending) elif current_user.is_admin(): # Admin see all pending events (no filter) pass else: # Regular user can see non Pending query_filter = Event.status != EventStatus.Pending # If user is a supervisor, it can see Pending events of its activities if current_user.is_supervisor(): # Supervisors can see all sup activities = current_user.get_supervised_activities() activities_ids = [a.id for a in activities] supervised = Event.activity_types.any( ActivityType.id.in_(activities_ids)) query_filter = or_(query_filter, supervised) # If user can create event, it can see its pending events if current_user.can_create_events(): lead = Event.leaders.any(id=current_user.id) query_filter = or_(query_filter, lead) # After filter construction, it is applied to the query query = query.filter(query_filter) # Process all filters. # All filter are added as AND i = 0 while f"filters[{i}][field]" in request.args: value = request.args.get(f"filters[{i}][value]") filter_type = request.args.get(f"filters[{i}][type]") field = request.args.get(f"filters[{i}][field]") if field == "status": value = getattr(EventStatus, value) query_filter = None if field == "activity_type": query_filter = Event.activity_types.any(short=value) elif field == "end": if filter_type == ">=": query_filter = Event.end >= current_time() elif field == "status": if filter_type == "=": query_filter = Event.status == value elif filter_type == "!=": query_filter = Event.status != value if query_filter is not None: query = query.filter(query_filter) # Get next filter i += 1 # Process first sorter only if "sorters[0][field]" in request.args: sort_field = request.args.get("sorters[0][field]") sort_dir = request.args.get("sorters[0][dir]") order = desc(sort_field) if sort_dir == "desc" else sort_field query = query.order_by(order) paginated_events = query.paginate(page, size, False) data = EventSchema(many=True).dump(paginated_events.items) response = {"data": data, "last_page": paginated_events.pages} return json.dumps(response), 200, {"content-type": "application/json"}