Skip to content

Commit

Permalink
Merge pull request #355 from loleg/main
Browse files Browse the repository at this point in the history
Participant search and event performance
  • Loading branch information
loleg committed Jun 27, 2023
2 parents 873d9e2 + 195e519 commit ce488f7
Show file tree
Hide file tree
Showing 20 changed files with 288 additions and 95 deletions.
24 changes: 9 additions & 15 deletions dribdat/aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ def GetEventUsers(event):
users = []
userlist = []
projects = set([p.id for p in event.projects])
# TODO: slow; how about actual membership?
activities = Activity.query.filter(and_(
Activity.name=='star',
Activity.project_id.in_(projects)
Expand All @@ -200,22 +201,15 @@ def ProjectActivity(project, of_type, user, action=None, comments=None):
"""Generate an activity of a certain type in the project."""
activity = Activity(
name=of_type,
user_id=user.id,
project_id=project.id,
project_progress=project.progress,
project_version=project.versions.count(),
action=action
)
activity.user_id = user.id
# Regular posts are 1 star
score = 1
# Booster stars give projects double points
if of_type == 'boost':
score = 2
# Post comments are activity contents
if comments is not None and len(comments) > 3:
activity.content = comments
if project.score is None:
project.score = 0
# Check for existing stars
allstars = Activity.query.filter_by(
name='star',
Expand All @@ -227,15 +221,15 @@ def ProjectActivity(project, of_type, user, action=None, comments=None):
return # One star per user
elif of_type == 'unstar':
if allstars.count() > 0:
allstars[0].delete()
project.score = project.score - score
project.save()
return
allstars.first().delete()
activity = None
# Save current project score
project.score = project.score + score
activity.project_score = project.score
project.update()
project.save()
db.session.add(activity)
db.session.add(project)
if activity is not None:
activity.project_score = project.score
db.session.add(activity)
db.session.commit()


Expand Down
12 changes: 12 additions & 0 deletions dribdat/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def init_app(config_object=ProdConfig):
register_loggers(app)
register_shellcontext(app)
register_commands(app)
register_caching(app)
return app


Expand Down Expand Up @@ -189,3 +190,14 @@ def register_loggers(app):
stream_handler = logging.StreamHandler()
app.logger.addHandler(stream_handler)
app.logger.setLevel(logging.INFO)


def register_caching(app):
"""Prevent cached responses in debug."""
if 'DEBUG' in app.config and app.config['DEBUG']:
@app.after_request
def after_request(response):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, public, max-age=0"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
17 changes: 17 additions & 0 deletions dribdat/public/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ def project_list_current_csv():
return project_list_csv(event.id, event.name)


@blueprint.route('/events.<as_format>')
def list_all_events(as_format='json'):
"""Output basic data of all public events."""
eventlist = []
for event in Event.query \
.filter_by(is_hidden=False, lock_resources=False) \
.order_by(Event.starts_at.desc()).all():
eventlist.append(event.data)
if as_format == 'json':
return jsonify(events=eventlist)
headers = {'Content-Disposition': 'attachment; filename=events.csv'}
csvlist = gen_csv(eventlist)
return Response(stream_with_context(csvlist),
mimetype='text/csv',
headers=headers)


@blueprint.route('/events/projects.csv')
def project_list_all_events_csv():
"""Output CSV of projects and challenges in all events."""
Expand Down
59 changes: 57 additions & 2 deletions dribdat/public/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,70 @@ def event_participants(event_id):
event = Event.query.filter_by(id=event_id).first_or_404()
users = GetEventUsers(event)
cert_path = None
search_by = request.args.get('q')
# Quick (actually, rather slow..) search filter
if search_by and len(search_by) > 2:
usearch = []
if '@' in search_by:
qq = search_by.replace('@', '').lower()
for u in users:
if qq in u.username.lower() or qq in u.email.lower():
usearch.append(u)
else:
qq = search_by.lower()
for u in users:
if (u.my_story and qq in u.my_story.lower()) or \
(u.my_goals and qq in u.my_goals.lower()):
usearch.append(u)
else:
usearch = users
search_by = ''
# Provide certificate if available
if current_user and not current_user.is_anonymous:
cert_path = current_user.get_cert_path(event)
usercount = len(users) if users else 0
usercount = len(usearch) if usearch else 0
return render_template("public/eventusers.html",
q=search_by,
cert_path=cert_path,
current_event=event, participants=users,
current_event=event, participants=usearch,
usercount=usercount, active="participants")



@blueprint.route("/participants")
def all_participants():
"""Show list of participants of an event."""
users = User.query.filter_by(active=True)
search_by = request.args.get('q')
if search_by and len(search_by) > 2:
q = search_by.replace('@', '').lower()
q = "%%%s%%" % q
if '@' in search_by:
users = users.filter(or_(
User.email.ilike(q),
User.username.ilike(q)
))
else:
users = users.filter(or_(
User.my_story.ilike(q),
User.my_goals.ilike(q),
))
else:
users = users.limit(50).all()
search_by = ''
# Provide certificate if available
if users:
users = sorted(users, key=lambda x: x.username)
usercount = len(users)
else:
usercount = 0
return render_template("public/eventusers.html",
q=search_by,
participants=users,
usercount=usercount,
active="participants")


@blueprint.route("/event/<int:event_id>/stages")
def event_stages(event_id):
"""Show projects by stage for an event."""
Expand Down
3 changes: 2 additions & 1 deletion dribdat/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ class DevConfig(Config):
# Put the db file in project root
DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME)
SQLALCHEMY_DATABASE_URI = 'sqlite:///{0}'.format(DB_PATH)
DEBUG_TB_ENABLED = True
CACHE_TYPE = 'NullCache' # Do not cache
DEBUG_TB_ENABLED = True # Enable the Debug Toolbar
ASSETS_DEBUG = True # Don't bundle/minify static assets
WTF_CSRF_ENABLED = False # Allows form testing

Expand Down
10 changes: 10 additions & 0 deletions dribdat/static/js/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,22 @@ document.getElementById('next').addEventListener('click', onNextPage);

// Keyboard navigation
$("body").keydown(function(e) {
//console.log(e.keyCode);
if (e.keyCode == 37) { // left
onPrevPage();
}
else if(e.keyCode == 39) { // right
onNextPage();
}
else if(e.keyCode == 33) { // page up
onPrevPage();
}
else if(e.keyCode == 34) { // page down
onNextPage();
}
else if(e.keyCode == 32) { // spacebar
onNextPage();
}
});

/**
Expand Down
7 changes: 6 additions & 1 deletion dribdat/templates/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ <h2> Administer dribdat </h2>
{% endif %}
</div>
<div class="btn-group mb-2 text-center">
<a href="{{ url_for('admin.categories') }}" class="btn btn-lg btn-info">
<a href="{{ url_for('admin.presets') }}" class="btn btn-lg btn-info">
<i class="fa fa-pencil" aria-hidden="true"></i>
Roles</a>
<a href="{{ url_for('admin.categories') }}" class="btn btn-lg btn-info">
<i class="fa fa-pencil" aria-hidden="true"></i>
Categories</a>
</div>
<div class="btn-group mb-2 text-center">
<a href="{{ url_for('public.all_participants') }}" class="btn btn-lg btn-light">
<i class="fa fa-child" aria-hidden="true"></i>
Users</a>
</div>
<div class="container">
<div id="activities"></div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions dribdat/templates/includes/nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@
<div data-action="/api/project/search.json" id="search">
<div class="content input-group">
<div class="input-group-prepend">
<span class="input-group-text">Search dribdat</span>
<span class="input-group-text">Search</span>
</div>
<input type="text" class="form-control" name="q" aria-label="Search term" placeholder="Start typing ..." style="width:20em">
<input type="text" class="form-control" name="q" aria-label="Search projects" placeholder="Start typing ..." style="width:20em">
</div>
</div>
<div class="row flex-row flex-nowrap pb-2" id="search-results"></div>
Expand Down
11 changes: 5 additions & 6 deletions dribdat/templates/macros/_event.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,13 @@
</div>
{% endif %}
</div>
{% if project.team_count > 0 %}
<div class="team-counter"
title="{{ project.team_count }} supporting">
{% if project.team_count == 1 %}
{% if project.is_challenge %}
<div class="team-counter" title="Team size ({{ project.score }})">
{% if project.score == 1 %}
<i class="fa fa-user"></i>
{% elif project.team_count == 2 %}
{% elif project.score == 2 %}
<i class="fa fa-user"></i><i class="fa fa-user"></i>
{% elif project.team_count > 2 %}
{% elif project.score > 2 %}
<i class="fa fa-users"></i>
{% endif %}
</div>
Expand Down
21 changes: 9 additions & 12 deletions dribdat/templates/public/about.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,22 @@ <h1 style="font-weight:bold; font-family:monospace">
<div class="content">
<h3>Get the data</h3>
<p>
You can use the <b>My Data</b> button on your profile to download a summary of your contributions. There is a simple open API for accessing data from this site in machine-readable form over the Web.
You can use the <b>My Data</b> button on your profile to download a summary of your contributions. There is a simple open API for accessing data from this site in machine-readable form over the Web. See details in the <a href="https://dribdat.cc/contribute.html#api-guide">Dribdat API Guide</a>.
</p>
<p>Download data on the featured or past (replace <tt>current</tt> by <tt>id</tt>) events</p>
<ul class="pl-3">
<li><a href="/hackathon.json">/hackathon.json</a> (JSON <a href="https://schema.org/Hackathon">Schema</a>)</li>
<li><a href="/api/event/current/info.json">/api/event/current/info.json</a> (JSON basic info)</li>
<li><a href="/api/event/current/datapackage.json">/api/event/current/datapackage.json</a> (Frictionless Data Package)</li>
<li><a href="/api/event/current/datapackage.zip">/api/event/current/datapackage.zip</a> (Full data when authenticated)</li>
</ul>
<p>Retrieve project data from the <tt>current</tt> (or <tt>id</tt>) event</p>
<ul class="pl-3">
<li><a href="/api/events.json">/api/events.json</a> (JSON list of events)</li>
<li><a href="/api/events.csv">/api/events.csv</a> (CSV list of events)</li>
<li><a href="/api/event/current/info.json">/api/event/current/info.json</a> (JSON for current event)</li>
<li><a href="/api/event/current/datapackage.json">/api/event/current/datapackage.json</a> (<a href="https://frictionlessdata.io">Frictionless Data</a> Package)</li>
<li><a href="/api/event/current/projects.json">/api/event/current/projects</a> (JSON)</li>
<li><a href="/api/event/current/projects.csv">/api/event/current/projects</a> (CSV)</li>
</ul>
<p>Search the projects, recent activity, and posts (use <tt>q=...</tt> for query, set <tt>limit=10</tt>)</p>
<p>Search projects, recent activity, and posts (use <tt>q=...</tt> for query, set <tt>limit=10</tt>)</p>
<ul class="pl-3">
<li><a href="/api/project/search.json">/api/project/search.json</a> (JSON)</li>
<li><a href="/api/project/activity.json">/api/project/activity.json</a> (JSON)</li>
<li><a href="/api/project/search.json">/api/project/search.json</a> (JSON)</li>
<li><a href="/api/project/posts.json">/api/project/posts.json</a> (JSON)</li>
</ul>
</div>
Expand All @@ -86,11 +84,10 @@ <h3>Get the data</h3>
<a href="https://en.wikipedia.org/wiki/MIT_License" target="_blank">MIT license</a>
-
source code available on
<a href="https://github.com/dribdat/dribdat" target="_blank">GitHub</a>,
<a href="https://gitlab.com/dribdat/dribdat" target="_blank">GitLab</a> and
<a href="https://github.com/dribdat/dribdat" target="_blank">GitHub</a> and
<a href="https://codeberg.org/dribdat/dribdat" target="_blank">Codeberg</a>
-
visit <a href="https://app.fossa.com/projects/git%2Bgithub.com%2Floleg%2Fdribdat/refs/branch/main/3296a528a7ac2c09486c7cfae513d64d263cc238/preview">FOSSA</a>
visit <a href="https://app.fossa.com/projects/git%2Bgithub.com%2Floleg%2Fdribdat?utm_source=share_link">FOSSA</a>
for a list of bundled code works.
</center>
</div>
Expand Down
35 changes: 25 additions & 10 deletions dribdat/templates/public/eventusers.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% import "macros/_misc.html" as misc %}

{% block page_title %}{{current_event.name}} / Participants{% endblock %}
{% block page_title %}Participants{% endblock %}
{% block body_class %}eventusers{% endblock %}

{% block content %}
Expand All @@ -16,10 +16,23 @@
</div>
{% endif %}

{% cache 300, 'eventusers-%d' % current_event.id %}

<h1 class="huge">Contributors</h1>

<div data-action="/api/user/search.json" id="search-users" style="margin-bottom:1em; width:400px">
<form method="get" action="participants">
<div class="content input-group">
<div class="input-group-prepend">
<span class="input-group-text">Search</span>
</div>
<input type="text" class="form-control" name="q" value="{{ q }}"
aria-label="Search users"
placeholder="@user or free text in profile ..."
style="width:20em; max-width:80%" />
</div>
</form>
</div>

<div class="event-participants">
{% if participants %}

Expand All @@ -30,24 +43,27 @@ <h1 class="huge">Contributors</h1>
</div>

{% else %}
<h4>This event does not yet have any projects or challenges with a team.
<!-- Or perhaps nobody has joined any that exist. -->
Now is a good time to start some, and encourage your participants to Join one.</h4>
<blockquote><i><!-- This event does not yet have any projects or challenges with a team.-->
<!-- Or perhaps nobody has joined any that exist.
Now is a good time to start some, and -->
Encourage your participants to create a profile and Join a team.</i></blockquote>
{% endif %}
</div><!-- /event-participants -->

<div category-id="infobox">
{% if current_event.has_started and not current_event.has_finished %}
{% if current_event and current_event.has_started and not current_event.has_finished %}
<center><h4><i class="fa fa-lightbulb-o"></i> &nbsp;Join a project or
challenge to be listed here!</h4>
challenge to be listed here</h4>
<p>As
<span class="user-score">{{ usercount }}</span> have done in
<a href="{{ url_for('public.event', event_id=current_event.id) }}">
this event</a> already.</p></center>
this event</a> already</p></center>
{% elif not usercount %}
<center>No profiles matching, please try again.</center>
{% else %}
<center>
<span class="user-score">{{ usercount }}</span>
users who have joined a challenge on this platform.</center>
users are here</center>
{% endif %}
</div>

Expand All @@ -59,5 +75,4 @@ <h4>This event does not yet have any projects or challenges with a team.
</center>
{% endif %}

{% endcache %}
{% endblock %}
5 changes: 3 additions & 2 deletions dribdat/templates/public/project.html
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,9 @@ <h5 class="modal-title" id="addUserLabel">Add user to team</h5>
</div>
<div class="modal-body">
<p class="text-small text-left">
Use the <a href="{{ url_for('admin.users')}}">admin</a>
to look up a user, and place the exact username here to add to the team.
Use the <a href="{{ url_for('public.all_participants')}}">participant list</a>
or <a href="{{ url_for('admin.users')}}">admin</a>
to look up a user. Then place the exact username here to add them to the team.
You can also remove a user with the <b>X</b> button next to their profile.
</p>
<form action="{{ url_for('project.project_star_user', project_id=project.id)}}" method="post">
Expand Down

0 comments on commit ce488f7

Please sign in to comment.