Private
Public Access
1
0
This commit is contained in:
Sander Roosendaal
2022-02-16 13:21:58 +01:00
parent 4f48901c55
commit 984198e63c
2 changed files with 263 additions and 201 deletions

View File

@@ -1,3 +1,5 @@
from datetime import date
from django import template
from django.utils.safestring import mark_safe
from time import strftime
@@ -7,44 +9,47 @@ import json
import math
import datetime
import re
register = template.Library()
from rowers.utils import calculate_age
from rowers.models import (
course_length,WorkoutComment,
TrainingMacroCycle,TrainingMesoCycle, TrainingMicroCycle,
Rower,Workout,SiteAnnouncement, TeamInvite, TeamRequest, CoachOffer,CoachRequest,
VirtualRaceFollower,VirtualRace,favanalysischoices
)
course_length, WorkoutComment,
TrainingMacroCycle, TrainingMesoCycle, TrainingMicroCycle,
Rower, Workout, SiteAnnouncement, TeamInvite, TeamRequest, CoachOffer, CoachRequest,
VirtualRaceFollower, VirtualRace, favanalysischoices,
Team, TrainingPlan, TrainingTarget)
from rowers.plannedsessions import (
race_can_register, race_can_submit,race_rower_status
)
race_can_register, race_can_submit, race_rower_status)
from rowers.models import PlannedSession
from rowers.teams import coach_getcoachees
from rowers import credits
from rowers import c2stuff
from rowers.c2stuff import c2_open
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember, ispromember
from rowers.mytypes import (
otwtypes,adaptivetypes,sexcategories,weightcategories,workouttypes,
workouttypes_icons,
)
otwtypes, adaptivetypes, sexcategories, weightcategories, workouttypes,
workouttypes_icons)
from rowers.utils import NoTokenError, step_to_string, landingpages2
from rowers.teams import rower_get_managers
import rowers.payments as payments
from rowsandall_app.settings import NK_VIEWER_LOCATION
from rowers.opaque import encoder
from rowers.plannedsessions import ps_dict_get_description_html
import arrow
from django.utils.safestring import mark_safe
from django.utils.html import urlize as urlize_impl
from django.template.defaultfilters import stringfilter
from six import string_types
register = template.Library()
@register.filter
def workoutdate(id): # pragma: no cover
try:
@@ -53,8 +58,9 @@ def workoutdate(id): # pragma: no cover
except Workout.DoesNotExist:
return 'unknown'
@register.filter
def isfollower(user,id):
def isfollower(user, id):
if user.is_anonymous: # pragma: no cover
return True
@@ -63,13 +69,13 @@ def isfollower(user,id):
except VirtualRace.DoesNotExist: # pragma: no cover
return False
followers = VirtualRaceFollower.objects.filter(race=race,user=user)
followers = VirtualRaceFollower.objects.filter(race=race, user=user)
return followers.count() == 0
return followers.count()==0
favanalysisdict = {}
for key,value in favanalysischoices:
for key, value in favanalysischoices:
favanalysisdict[key] = value
landingpagedict = {}
@@ -77,17 +83,18 @@ for key, value in landingpages2:
landingpagedict[key] = value
landingpageicons = {
'workout_view':'fas fa-search fa-fw',
'workout_edit_view':'fas fa-pencil-alt fa-fw',
'workout_workflow_view':'fas fa-tachometer-alt fa-fw',
'workout_stats_view':'fal fa-table fa-fw',
'workout_data_view':'fal fa-table fa-fw',
'workout_summary_edit_view':'fas fa-pause fa-fw',
'workout_flexchart_stacked_view':'fas fa-align-justify fa-fw',
'workout_flexchart3_view':'fas fa-chart-line fa-fw',
'workout_delete':'fas fa-trash-alt fa-fw'
'workout_view': 'fas fa-search fa-fw',
'workout_edit_view': 'fas fa-pencil-alt fa-fw',
'workout_workflow_view': 'fas fa-tachometer-alt fa-fw',
'workout_stats_view': 'fal fa-table fa-fw',
'workout_data_view': 'fal fa-table fa-fw',
'workout_summary_edit_view': 'fas fa-pause fa-fw',
'workout_flexchart_stacked_view': 'fas fa-align-justify fa-fw',
'workout_flexchart3_view': 'fas fa-chart-line fa-fw',
'workout_delete': 'fas fa-trash-alt fa-fw'
}
@register.filter
def landingicon(landingpage):
try:
@@ -95,11 +102,13 @@ def landingicon(landingpage):
except KeyError: # pragma: no cover
return 'fas fa-search fa-fw'
@register.filter
def steptostring(steps):
res = ps_dict_get_description_html(steps,short=True)
res = ps_dict_get_description_html(steps, short=True)
return res
# for verbose version of fav analysis
@register.filter
def verbose(s):
@@ -108,21 +117,24 @@ def verbose(s):
except KeyError: # pragma: no cover
return 'Details'
@register.filter
def datarows(data): # pragma: no cover
return range(len(data))
@register.filter
def adaptive(s):
u = s
for e,v in adaptivetypes:
for e, v in adaptivetypes:
if e.lower() == u.lower():
u = v
continue
return u
@register.filter
def nkviewerlink(workout): # pragma: no cover
url = "{nkviewer}{nkid}".format(
@@ -136,63 +148,69 @@ def nkviewerlink(workout): # pragma: no cover
def boatclass(s):
u = s
for e,v in workouttypes:
for e, v in workouttypes:
if e.lower() == u.lower():
u = v
continue
return u
@register.filter
def sex(s):
u = s
for e,v in sexcategories:
for e, v in sexcategories:
if e.lower() == u.lower():
u = v
continue
return u
@register.filter
def weekbegin(nr):
week, day = divmod(nr,7)
week, day = divmod(nr, 7)
if day == 1:
return True
return False
@register.filter
def discount(amount,rower): # pragma: no cover
return credits.discount(amount,rower)
def discount(amount, rower): # pragma: no cover
return credits.discount(amount, rower)
@register.filter
def discounted(amount,rower):
return credits.discounted(amount,rower)
def discounted(amount, rower):
return credits.discounted(amount, rower)
@register.filter
def weekend(nr):
week, day = divmod(nr,7)
week, day = divmod(nr, 7)
if day == 0:
return True
return False
@register.filter
def weight(s):
u = s
for e,v in weightcategories:
for e, v in weightcategories:
if e.lower() == u.lower():
u = v
continue
return u
@register.filter
def sigdig(value, digits = 3):
def sigdig(value, digits=3):
try:
order = int(math.floor(math.log10(math.fabs(value))))
except (ValueError,TypeError): # pragma: no cover
except (ValueError, TypeError): # pragma: no cover
return value
# return integers as is
@@ -207,6 +225,7 @@ def sigdig(value, digits = 3):
fmtstr = "%.0f"
return fmtstr % (round(value, places)) # pragma: no cover
@register.filter
def pickle(dc): # pragma: no cover
s = dict()
@@ -215,75 +234,83 @@ def pickle(dc): # pragma: no cover
return s
@register.filter(is_safe=True, needs_autoescape=True)
@stringfilter
def urlshorten(value, limit,autoescape=None):
def urlshorten(value, limit, autoescape=None):
return mark_safe(
urlize_impl(
value, trim_url_limit=int(limit),
nofollow=True, autoescape=autoescape).replace('<a', '<a target="_blank"'))
@register.filter
def nogoals(user):
targets = TrainingTarget.objects.filter(rowers=user.rower,
date__gte=datetime.date.today())
return len(targets)==0
return len(targets) == 0
@register.filter
def notfree(rower):
return rower.rowerplan not in ['basic','freecoach']
return rower.rowerplan not in ['basic', 'freecoach']
def strfdelta(tdelta):
minutes,seconds = divmod(tdelta.seconds,60)
minutes, seconds = divmod(tdelta.seconds, 60)
tenths = int(tdelta.microseconds/1e5)
res = "{minutes:0>1}:{seconds:0>2}.{tenths:0>1}".format(
minutes=minutes,
seconds=seconds,
tenths=tenths,
)
tenths=tenths)
return res
from rowers.teams import rower_get_managers
@register.filter
def alertstatspercentage(list,i): # pragma: no cover
def alertstatspercentage(list, i): # pragma: no cover
alertstats = list[i-1]
return alertstats["percentage"]
@register.filter
def alertstartdate(list,i): # pragma: no cover
def alertstartdate(list, i): # pragma: no cover
alertstats = list[i-1]
return alertstats["startdate"]
@register.filter
def alertnperiod(list,i): # pragma: no cover
def alertnperiod(list, i): # pragma: no cover
alertstats = list[i-1]
return alertstats["nperiod"]
@register.filter
def alertenddate(list,i): # pragma: no cover
def alertenddate(list, i): # pragma: no cover
alertstats = list[i-1]
return alertstats["enddate"]
@register.filter
def is_coach(rower,rowers):
def is_coach(rower, rowers):
for r in rowers:
if rower not in rower_get_managers(r):
return False
return True
@register.filter
def waterpower(x,rower): # pragma: no cover
def waterpower(x, rower): # pragma: no cover
if rower is not None:
return int(x*(100-rower.otwslack)/100.)
return int(x)
@register.filter
def round20(x): # pragma: no cover
try:
@@ -291,6 +318,7 @@ def round20(x): # pragma: no cover
except ValueError:
return 20
@register.filter
def round100(x): # pragma: no cover
try:
@@ -298,49 +326,52 @@ def round100(x): # pragma: no cover
except ValueError:
return 100
@register.filter
def majorticks(maxval): # pragma: no cover
ticks = range(1+int(maxval/100.))
newticks =[]
newticks = []
for t in ticks:
newticks.append(t*100)
return newticks
@register.filter
def hrmajorticks(maxval,minval): # pragma: no cover
def hrmajorticks(maxval, minval): # pragma: no cover
ticks = range(int((maxval-minval)/20.)-1)
newticks =[]
newticks = []
for t in ticks:
newticks.append(100+t*20)
return newticks
def strfdeltah(tdelta):
hours, rest = divmod(tdelta.seconds,3600)
minutes,seconds = divmod(rest,60)
hours, rest = divmod(tdelta.seconds, 3600)
minutes, seconds = divmod(rest, 60)
tenths = int(tdelta.microseconds/1e5)
res = "{hours:0>2}:{minutes:0>2}:{seconds:0>2}.{tenths:0>1}".format(
hours=hours,
minutes=minutes,
seconds=seconds,
tenths=tenths,
)
tenths=tenths)
return res
@register.filter
def secondstotimestring(tdelta):
hours, rest = divmod(tdelta,3600)
minutes,seconds = divmod(rest,60)
hours, rest = divmod(tdelta, 3600)
minutes, seconds = divmod(rest, 60)
res = "{hours:0>2}:{minutes:0>2}:{seconds:0>2}".format(
hours=hours,
minutes=minutes,
seconds=seconds,
)
seconds=seconds)
return res
@register.filter
def existing_customer(user):
if user.is_anonymous: # pragma: no cover
@@ -348,6 +379,7 @@ def existing_customer(user):
else:
return payments.is_existing_customer(user.rower)
@register.filter
def aantalcomments(workout):
try:
@@ -359,6 +391,7 @@ def aantalcomments(workout):
return aantalcomments
@register.filter
def encode(id):
try:
@@ -367,6 +400,7 @@ def encode(id):
result = ''
return result
@register.filter
def water(workout):
try:
@@ -374,25 +408,29 @@ def water(workout):
except AttributeError: # pragma: no cover
return False
@register.filter
def ddays(ddelta):
return ddelta.days+1
@register.filter
def spacetohtml(t):
return t.replace(" ","%20")
return t.replace(" ", "%20")
@register.filter
def distanceprint(d):
if d<10000:
if d < 10000:
return "{d} m".format(d=d)
d2 = d/1000. # pragma: no cover
d2 = d / 1000. # pragma: no cover
return "%.2f km" % d2 # pragma: no cover
@register.filter
def durationprint(d,dstring):
if (d == None): # pragma: no cover
def durationprint(d, dstring):
if (d is None): # pragma: no cover
return d
else:
try:
@@ -400,16 +438,18 @@ def durationprint(d,dstring):
except AttributeError:
return None
def getstartenddate(timeperiod):
s,e = timeperiod.split('/')
s, e = timeperiod.split('/')
startdate = arrow.get(s).date()
enddate = arrow.get(e).date()
return startdate,enddate
return startdate, enddate
@register.filter
def nextperiodend(timeperiod):
startdate,enddate = getstartenddate(timeperiod)
startdate, enddate = getstartenddate(timeperiod)
timedelta = enddate-startdate
newstartdate = enddate+datetime.timedelta(days=1)
newenddate = newstartdate+timedelta
@@ -419,52 +459,53 @@ def nextperiodend(timeperiod):
@register.filter
def nextperiodstart(timeperiod):
startdate,enddate = getstartenddate(timeperiod)
timedelta = enddate-startdate
startdate, enddate = getstartenddate(timeperiod)
newstartdate = enddate+datetime.timedelta(days=1)
newenddate = newstartdate+timedelta
return newstartdate.strftime("%Y-%m-%d")
@register.filter
def previousperiodend(timeperiod):
startdate,enddate = getstartenddate(timeperiod)
timedelta = enddate-startdate
startdate, enddate = getstartenddate(timeperiod)
newenddate = startdate-datetime.timedelta(days=1)
newstartdate = startdate-timedelta-datetime.timedelta(days=1)
return newenddate.strftime("%Y-%m-%d")
@register.filter
def timedeltadays(timeperiod):
startdate,enddate = getstartenddate(timeperiod)
startdate, enddate = getstartenddate(timeperiod)
timedelta = enddate-startdate
return timedelta.days+1
@register.filter
def previousperiodstart(timeperiod):
startdate,enddate = getstartenddate(timeperiod)
startdate, enddate = getstartenddate(timeperiod)
timedelta = enddate-startdate
newenddate = startdate-datetime.timedelta(days=1)
newstartdate = startdate-timedelta-datetime.timedelta(days=1)
return newstartdate.strftime("%Y-%m-%d")
@register.filter
def paceprint(d):
if (d == None): # pragma: no cover
if (d is None): # pragma: no cover
return d
else:
return strfdelta(d)
@register.filter
def deltatimeprint(d): # pragma: no cover
if (d == None):
if (d is None):
return d
else:
return strfdeltah(d)
@register.filter
def c2userid(user): # pragma: no cover
try:
@@ -476,6 +517,7 @@ def c2userid(user): # pragma: no cover
return c2userid
@register.filter
def currency(word):
try:
@@ -490,11 +532,13 @@ def currency(word):
def courselength(course): # pragma: no cover
return course_length(course)
@register.filter(is_safe=True)
def jsdict(dict,key): # pragma: no cover
def jsdict(dict, key): # pragma: no cover
s = dict.get(key)
return mark_safe(json.dumps(s))
@register.filter
def icon(workouttype):
try:
@@ -514,10 +558,11 @@ def lookup(dict, key):
except KeyError: # pragma: no cover
return None
if isinstance(s,string_types) and len(s) > 22:
if isinstance(s, string_types) and len(s) > 22:
s = s[:22]
return s
@register.filter
def lookuplong(dict, key):
try:
@@ -528,9 +573,8 @@ def lookuplong(dict, key):
return s
from rowers.models import PlannedSession
@register.filter
def is_session_manager(id,user):
def is_session_manager(id, user):
try:
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist: # pragma: no cover
@@ -540,61 +584,62 @@ def is_session_manager(id,user):
@register.filter
def may_edit(workout,request):
def may_edit(workout, request):
mayedit = 0
if request.user == workout.user.user:
mayedit = True
if is_workout_user(request.user,workout):
if is_workout_user(request.user, workout):
mayedit = True
return mayedit
@register.filter
def mayeditplan(obj,request):
def mayeditplan(obj, request):
if obj is None: # pragma: no cover
return False
if hasattr(obj,'plan'):
return mayeditplan(obj.plan,request)
if hasattr(obj, 'plan'):
return mayeditplan(obj.plan, request)
if hasattr(obj,'manager'):
if hasattr(obj, 'manager'):
if obj.manager is not None:
return request.user == obj.manager.user
rr = Rower.objects.get(user=request.user) # pragma: no cover
if is_coach_user(request.user,obj.rower) and rr.rowerplan not in ['basic','pro']: # pragma: no cover
if is_coach_user(request.user, obj.rower) and rr.rowerplan not in ['basic', 'pro']: # pragma: no cover
mayedit = True
return mayedit # pragma: no cover
@register.filter
def iterrows(df): # pragma: no cover
return df.iterrows()
@register.filter(name='times')
def times(number): # pragma: no cover
return range(number)
@register.simple_tag
def get_df_iloc(data,i,j): # pragma: no cover
return data.iloc(i,j)
@register.simple_tag
def get_field_id(id,s,form): # pragma: no cover
def get_df_iloc(data, i, j): # pragma: no cover
return data.iloc(i, j)
@register.simple_tag
def get_field_id(id, s, form): # pragma: no cover
field_name = s+str(id)
return form.__getitem__(field_name)
from rowers.models import Rower,Team,TrainingPlan,TrainingTarget
@register.filter
def is_promember(user):
return ispromember(user)
@register.filter
def is_manager(user):
r = Rower.objects.get(user=user)
@@ -605,6 +650,7 @@ def is_manager(user):
def is_planmember(user):
return isplanmember(user)
@register.filter
def get_age(r): # pragma: no cover
return calculate_age(r.birthdate)
@@ -622,6 +668,7 @@ def user_teams(user):
return teams
@register.filter
def user_team1(user):
try:
@@ -634,29 +681,31 @@ def user_team1(user):
return teams[0].id
@register.filter
def announcements(request):
announcements = SiteAnnouncement.objects.filter(
expires__gte=datetime.date.today()).order_by(
"-created",
"-id"
)
"-id")
return announcements[0:4]
@register.filter
def has_teams(user): # pragma: no cover
try:
therower = Rower.objects.get(user=user)
teams1 = therower.team.all()
teams2 = Team.objects.filter(manager=user)
teams = list(set(teams1).union(set(teams2)))
_ = list(set(teams1).union(set(teams2)))
return True
except TypeError:
return False
return False
@register.filter
def team_members(user):
try:
@@ -665,16 +714,16 @@ def team_members(user):
return []
teams = Team.objects.filter(manager=user)
members = Rower.objects.filter(
team__in=teams
).distinct().order_by(
"user__last_name","user__first_name"
).exclude(rowerplan='freecoach')
team__in=teams).distinct().order_by(
"user__last_name", "user__first_name").exclude(
rowerplan='freecoach')
return [rower.user for rower in members]
except TypeError: # pragma: no cover
return []
return [] # pragma: no cover
@register.filter
def openactions(user):
myteams = Team.objects.filter(manager=user)
@@ -698,16 +747,16 @@ def team_rowers(user): # pragma: no cover
if therower.rowerplan == 'basic':
return []
teams = Team.objects.filter(manager=user)
members = Rower.objects.filter(team__in=teams).distinct().order_by(
"user__last_name","user__last_name"
).exclude(rowerplan='freecoach')
members = Rower.objects.filter(
team__in=teams).distinct().order_by(
"user__last_name", "user__last_name").exclude(
rowerplan='freecoach')
return members
except TypeError:
return []
return []
from rowers.teams import coach_getcoachees
@register.filter
def coach_rowers(user):
@@ -721,7 +770,7 @@ def coach_rowers(user):
@register.filter
def verbosetimeperiod(timeperiod): # pragma: no cover
table = {
'today':'Today',
'today': 'Today',
'thisweek': 'This Week',
'thismonth': 'This Month',
'lastmonth': 'Last Month',
@@ -737,7 +786,6 @@ def verbosetimeperiod(timeperiod): # pragma: no cover
return verbose
from datetime import date
@ register.filter
def future_date_only(the_date): # pragma: no cover
@@ -751,16 +799,16 @@ def future_date_only(the_date): # pragma: no cover
def is_future_date(the_date):
return the_date >= date.today()
@register.filter
def amount(value):
vs = '{v}.00'.format(v=int(value))
return vs
@register.filter
def date_dif(the_date):
if the_date == date(1970,1,1):
if the_date == date(1970, 1, 1):
return 1
if the_date:
return the_date - date.today()
@@ -769,94 +817,105 @@ def date_dif(the_date):
@register.filter
def can_register(race,r):
return race_can_register(r,race)
def can_register(race, r):
return race_can_register(r, race)
@register.filter
def can_submit(race,r): # pragma: no cover
return race_can_submit(r,race)
def can_submit(race, r): # pragma: no cover
return race_can_submit(r, race)
@register.filter
def race_complete(race,r): # pragma: no cover
is_complete,has_registered = race_rower_status(r,race)
def race_complete(race, r): # pragma: no cover
is_complete, has_registered = race_rower_status(r, race)
return is_complete
@register.filter
def past_not_registered(race,r): # pragma: no cover
is_complete,has_registered = race_rower_status(r,race)
return not has_registered
@register.filter
def future_registered(race,r): # pragma: no cover
is_complete, has_registered = race_rower_status(r,race)
def past_not_registered(race, r): # pragma: no cover
is_complete, has_registered = race_rower_status(r, race)
return not has_registered
@register.filter
def future_registered(race, r): # pragma: no cover
is_complete, has_registered = race_rower_status(r, race)
is_open = race.evaluation_closure > timezone.now()
return has_registered and not is_complete and is_open
@property
def is_past_due(self): # pragma: no cover
return datetime.date.today() > self.date
@property
def is_not_past_due(self): # pragma: no cover
return datetime.date.today() <= self.date
@register.filter
def is_closed(race): # pragma: no cover
return race.evaluation_closure < timezone.now()
@register.filter
def is_final(race): # pragma: no cover
return race.evaluation_closure < timezone.now()-datetime.timedelta(hours=1)
@register.filter
def userurl(path,member):
def userurl(path, member):
pattern = re.compile('user\/\d+/')
userstring = 'user/%s/' % member.id
# remove team
tpattern = re.compile('team\/\d+/')
if tpattern.search(path) is not None:
path = tpattern.sub('',path)
path = tpattern.sub('', path)
if pattern.search(path) is not None:
replaced = pattern.sub(userstring,path)
replaced = pattern.sub(userstring, path)
else:
replaced = path+userstring
return replaced
@register.filter
def teamurl(path,team):
def teamurl(path, team):
pattern = re.compile('team\/\d+/')
teamstring = 'team/%s/' % team.id
# remove user
upattern = re.compile('\/user\/\d+/')
if upattern.search(path) is not None:
path = upattern.sub('/',path)
path = upattern.sub('/', path)
if pattern.search(path) is not None:
replaced = pattern.sub(teamstring,path)
replaced = pattern.sub(teamstring, path)
else:
replaced = path+teamstring
return replaced
@register.filter
def timeurl(path,timestring): # pragma: no cover
def timeurl(path, timestring): # pragma: no cover
pattern = re.compile('\?when=w.*')
timeurl = '?when=%s' % timestring
replaced = ''
if pattern.search(path) is not None:
replaced = pattern.sub(timeurl,path)
replaced = pattern.sub(timeurl, path)
if not replaced:
replaced = path+timeurl
return replaced
@register.filter
def trainingplans(rower):
today = datetime.date.today()
@@ -868,6 +927,7 @@ def trainingplans(rower):
return plans
@register.filter
def mesomacroid(id): # pragma: no cover
try:
@@ -879,6 +939,7 @@ def mesomacroid(id): # pragma: no cover
return str(theid)
@register.filter
def micromesoid(id): # pragma: no cover
try:
@@ -902,8 +963,9 @@ def micromacroid(id): # pragma: no cover
return str(theid)
@register.filter
def nextworkout(workout,user):
def nextworkout(workout, user):
if user.rower == workout.user:
try:
ws = Workout.objects.filter(
@@ -918,7 +980,7 @@ def nextworkout(workout,user):
try:
ws = Workout.objects.filter(
startdatetime__gt=workout.startdatetime,
user=workout.user,privacy='visible'
user=workout.user, privacy='visible'
).order_by(
"startdatetime"
).exclude(id=workout.id)
@@ -931,9 +993,8 @@ def nextworkout(workout,user):
return 0
@register.filter
def previousworkout(workout,user):
def previousworkout(workout, user):
if user.rower == workout.user:
try:
ws = Workout.objects.filter(
@@ -948,7 +1009,7 @@ def previousworkout(workout,user):
try:
ws = Workout.objects.filter(
startdatetime__lt=workout.startdatetime,
user=workout.user,privacy='visible'
user=workout.user, privacy='visible'
).order_by(
"-startdatetime"
).exclude(id=workout.id)

View File

@@ -1,3 +1,4 @@
[flake8]
ignore = F405, F403, E722, E226, W504, F401, W605
max-line-length = 120
exclude = .git, rowers/migrations