Skip to content

Commit

Permalink
Merge pull request #351 from loleg/main
Browse files Browse the repository at this point in the history
User management
  • Loading branch information
loleg committed Jun 5, 2023
2 parents 113730b + 7a01b91 commit 349a459
Show file tree
Hide file tree
Showing 32 changed files with 706 additions and 529 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ nosetests.xml
.cache
tests/.cache
.pytest_cache/
.vscode/

# Translations
*.mo
Expand Down
31 changes: 16 additions & 15 deletions dribdat/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import random
import string

# from os import path


blueprint = Blueprint('admin', __name__, url_prefix='/admin')

Expand Down Expand Up @@ -172,13 +170,20 @@ def user_new():
if form.is_submitted() and form.validate():
del form.id
user.username = sanitize_input(form.username.data)
del form.username
form.populate_obj(user)
db.session.add(user)
db.session.commit()

flash('User added.', 'success')
return redirect(url_for("admin.users"))
if User.query.filter_by(email=form.email.data).first():
flash('E-mail must be unique.', 'danger')
else:
del form.username
form.populate_obj(user)
if form.password.data:
user.set_password(form.password.data)
else:
user.set_password(get_random_alphanumeric_string())
db.session.add(user)
db.session.commit()

flash('User added.', 'success')
return redirect(url_for("admin.users"))

return render_template('admin/usernew.html', form=form)

Expand All @@ -199,19 +204,15 @@ def user_delete(user_id):
return redirect(url_for("admin.users"))


""" Get a reasonably secure password """


def get_random_alphanumeric_string(length=24):
""" Get a reasonably secure password """
return ''.join(
(random.SystemRandom().choice(string.ascii_letters + string.digits)
for i in range(length)))


""" Retrieves a user by name """


def get_user_by_name(username):
""" Retrieves a user by name """
if not username:
return None
username = username.strip()
Expand Down
10 changes: 5 additions & 5 deletions dribdat/apievents.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ def fetch_commits_gitea(full_name, limit=10):
full_name, limit)
data = requests.get(apiurl, timeout=REQUEST_TIMEOUT)
if data.status_code != 200:
logging.warn("Could not sync Gitea commits on %s" % full_name)
logging.warning("Could not sync Gitea commits on %s" % full_name)
return []
json = data.json()
if 'message' in json:
logging.warn("Could not sync Gitea commits on %s: %s"
logging.warning("Could not sync Gitea commits on %s: %s"
% (full_name, json['message']))
return []
commitlog = []
Expand Down Expand Up @@ -52,11 +52,11 @@ def fetch_commits_github(full_name, since=None, until=None):
apiurl += "&until=%s" % until.replace(microsecond=0).isoformat()
data = requests.get(apiurl, timeout=REQUEST_TIMEOUT)
if data.status_code != 200:
logging.warn("Could not sync GitHub commits on %s" % full_name)
logging.warning("Could not sync GitHub commits on %s" % full_name)
return []
json = data.json()
if 'message' in json:
logging.warn("Could not sync GitHub commits on %s: %s"
logging.warning("Could not sync GitHub commits on %s: %s"
% (full_name, json['message']))
return []
return parse_github_commits(json, full_name)
Expand Down Expand Up @@ -103,7 +103,7 @@ def fetch_commits_gitlab(project_id: int, since=None, until=None):
return []
json = data.json()
if 'message' in json:
logging.warn("Could not sync GitLab commits", json['message'])
logging.warning("Could not sync GitLab commits", json['message'])
return []
commitlog = []
for commit in json:
Expand Down
2 changes: 1 addition & 1 deletion dribdat/apifetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def RequestRemoteContent(project_url):
data = requests.get(project_url, timeout=REQUEST_TIMEOUT)
return data.text or None
except requests.exceptions.RequestException:
logging.warn("Could not connect to %s" % project_url)
logging.warning("Could not connect to %s" % project_url)
return None


Expand Down
8 changes: 4 additions & 4 deletions dribdat/apipackage.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def import_project_data(data, dry_run=False):
event_name = pjt['event_name']
event = Event.query.filter_by(name=event_name).first()
if not event:
logging.warn('Error: event not found: %s' % event_name)
logging.warning('Error: event not found: %s' % event_name)
continue
# Search for project
name = pjt['name']
Expand Down Expand Up @@ -214,7 +214,7 @@ def import_activities(data, dry_run=False):
# TODO: unreliable; rather use a map of project_id to new id
proj = Project.query.filter_by(name=pname).first()
if not proj:
logging.warn('Error! Project not found: %s' % pname)
logging.warning('Error! Project not found: %s' % pname)
continue
activity = Activity(aname, proj.id)
activity.set_from_data(act)
Expand Down Expand Up @@ -268,7 +268,7 @@ def fetch_datapackage(url, dry_run=False, all_data=False):
return {}


def import_datapackage(filedata, dry_run, all_data):
def import_datapackage(filedata, dry_run=True, all_data=False):
"""Save a temporary file and provide details."""
ext = filedata.filename.split('.')[-1].lower()
if ext not in ['json']:
Expand All @@ -279,7 +279,7 @@ def import_datapackage(filedata, dry_run, all_data):
return load_file_datapackage(filepath, dry_run, all_data)


def load_file_datapackage(filepath, dry_run, all_data):
def load_file_datapackage(filepath, dry_run=True, all_data=False):
"""Get event data from a file."""
try:
with open(filepath, mode='rb') as file:
Expand Down
2 changes: 1 addition & 1 deletion dribdat/boxout/datapackage.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def box_datapackage(line, cache=None):
logging.info("Fetching Data Package: <%s>" % url)
package = Package(url)
except Exception: # noqa: B902
logging.warn("Data Package not parsed: <%s>" % url)
logging.warning("Data Package not parsed: <%s>" % url)
return None
if package.created:
dt = datetime.fromisoformat(package.created).strftime("%d.%m.%Y")
Expand Down
11 changes: 7 additions & 4 deletions dribdat/mailer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Helper for sending mail."""
from flask import url_for
from flask import url_for, current_app
from flask_mailman import EmailMessage
from dribdat.utils import random_password # noqa: I005
import logging
Expand All @@ -17,14 +17,17 @@ def user_activation(user):
userid=user.id,
userhash=act_hash,
_external=True)
if not 'mailman' in current_app.extensions:
logging.warning('E-mail extension has not been configured')
return act_hash
msg = EmailMessage()
msg.subject = 'Your dribdat account'
msg.body = \
"Thanks for signing up at %s\n\n" % base_url \
"Hello %s,\n" % user.username \
+ "Thanks for signing up at %s\n\n" % base_url \
+ "Tap here to activate your account:\n\n%s" % act_url
msg.to = [user.email]
logging.info('Sending activation mail to user %d' % user.id)
logging.debug(act_url)
msg.send(fail_silently=True)
return True

return act_hash
8 changes: 6 additions & 2 deletions dribdat/public/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,12 @@ def activate(userid, userhash):
login_user(a_user, remember=True)
flash("Welcome! Your user account has been activated.", 'success')
return redirect(url_for('auth.user_profile'))
flash("Activation not found. Try again, or ask an organizer.", 'warning')
logout_user()
elif a_user.active:
flash("Your user account is active.", 'success')
else:
flash("Activation not found, or has expired." \
+ "Please try again or ask an organizer.", 'warning')
logout_user()
return redirect(url_for('public.home'))


Expand Down
7 changes: 5 additions & 2 deletions dribdat/public/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ def project_post(project_id):
allow_post = starred
if not allow_post:
flash('You do not have access to post to this project.', 'warning')
return project_action(project_id, None)
return redirect(url_for('project.project_view', project_id=project.id))
if event.lock_resources:
flash('Comments are not available in the resource area. Please join a project.', 'info')
return redirect(url_for('project.project_view', project_id=project.id))

# Evaluate project progress
stage, all_valid = validateProjectData(project)
Expand Down Expand Up @@ -392,7 +395,7 @@ def project_autoupdate(project_id):

# Check user permissions
starred = IsProjectStarred(project, current_user)
allow_edit = starred or (
allow_edit = starred or project.event.lock_resources or (
not current_user.is_anonymous and current_user.is_admin)
if not allow_edit or project.is_hidden:
flash('You may not sync this project.', 'warning')
Expand Down
5 changes: 3 additions & 2 deletions dribdat/public/projhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def project_edit_action(project_id, detail_view=False):
project = Project.query.filter_by(id=project_id).first_or_404()
starred = IsProjectStarred(project, current_user)

allow_edit = starred or (isUserActive(current_user)
and current_user.is_admin)
allow_edit = starred or project.event.lock_resources \
or (isUserActive(current_user) and current_user.is_admin)
if not allow_edit:
flash('You do not have access to edit this project.', 'warning')
return project_action(project_id, None)
Expand Down Expand Up @@ -122,6 +122,7 @@ def project_action(project_id, of_type=None, as_view=True, then_redirect=False,
allow_edit = not current_user.is_anonymous and current_user.is_admin
lock_editing = event.lock_editing
allow_post = starred and not event.lock_resources
allow_edit = allow_edit or event.lock_resources
allow_edit = (starred or allow_edit) and not lock_editing

# Check type of project
Expand Down
12 changes: 11 additions & 1 deletion dribdat/public/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from dribdat.user import getProjectStages, isUserActive
from urllib.parse import quote, quote_plus, urlparse
from datetime import datetime
from sqlalchemy import or_
from sqlalchemy import and_, or_
import re

blueprint = Blueprint('public', __name__, static_folder="../static")
Expand Down Expand Up @@ -143,8 +143,18 @@ def user(username):
submissions = user.posted_challenges()
projects = user.joined_projects(True)
posts = user.latest_posts(20)
today = datetime.utcnow()
events_next = Event.query.filter(and_(
Event.is_hidden.isnot(True),
Event.lock_resources.isnot(True),
Event.ends_at > today
))
events_next = events_next.order_by(Event.starts_at.desc())
if events_next.count() == 0: events_next = None
# Filter out by today's date
return render_template("public/userprofile.html", active="profile",
user=user, projects=projects, posts=posts,
events_next=events_next,
score=user.get_score(),
submissions=submissions,
may_certify=user.may_certify()[0])
Expand Down
1 change: 1 addition & 0 deletions dribdat/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,4 @@ class TestConfig(Config):
# SERVER_NAME = 'localhost.localdomain' #results in 404 errors
WTF_CSRF_ENABLED = False # Allows form testing
PRESERVE_CONTEXT_ON_EXCEPTION = False
DRIBDAT_ALLOW_EVENTS = True # Allows anyone to create an event

0 comments on commit 349a459

Please sign in to comment.