source: OpenWorkouts-current/ow/views/user.py @ 6993c72

currentfeature/docs
Last change on this file since 6993c72 was 6993c72, checked in by Segundo Fdez <segun.2@…>, 5 years ago

Merge branch 'master' into feature/ui

# Conflicts:
# ow/templates/profile.pt

  • Property mode set to 100644
File size: 11.1 KB
Line 
1import json
2from calendar import month_name
3from datetime import datetime, timezone
4
5from pyramid.httpexceptions import HTTPFound
6from pyramid.view import view_config
7from pyramid.security import remember, forget
8from pyramid.response import Response
9from pyramid.i18n import TranslationStringFactory
10from pyramid_simpleform import Form, State
11from pytz import common_timezones
12
13from ..models.user import User
14from ..schemas.user import (
15    UserProfileSchema,
16    ChangePasswordSchema,
17    SignUpSchema,
18)
19from ..models.root import OpenWorkouts
20from ..views.renderers import OWFormRenderer
21from ..utilities import timedelta_to_hms
22
23_ = TranslationStringFactory('OpenWorkouts')
24
25
26@view_config(context=OpenWorkouts)
27def dashboard_redirect(context, request):
28    """
29    Send the user to his dashboard when accesing the root object,
30    send to the login page if the user is not logged in.
31    """
32    if request.authenticated_userid:
33        user = request.root.get_user_by_uid(request.authenticated_userid)
34        if user:
35            return HTTPFound(location=request.resource_url(user))
36        else:
37            # an authenticated user session, for an user that does not exist
38            # anymore, logout!
39            return HTTPFound(location=request.resource_url(context, 'logout'))
40    return HTTPFound(location=request.resource_url(context, 'login'))
41
42
43@view_config(
44    context=OpenWorkouts,
45    name='login',
46    renderer='ow:templates/login.pt')
47def login(context, request):
48    message = ''
49    email = ''
50    password = ''
51    return_to = request.params.get('return_to')
52    redirect_url = return_to or request.resource_url(request.root)
53
54    if 'submit' in request.POST:
55        email = request.POST.get('email', None)
56        user = context.get_user_by_email(email)
57        if user:
58            password = request.POST.get('password', None)
59            if password is not None and user.check_password(password):
60                headers = remember(request, str(user.uid))
61                redirect_url = return_to or request.resource_url(user)
62                return HTTPFound(location=redirect_url, headers=headers)
63            else:
64                message = _('Wrong password')
65        else:
66            message = _('Wrong email address')
67
68    return {
69        'message': message,
70        'email': email,
71        'password': password,
72        'redirect_url': redirect_url
73    }
74
75
76@view_config(context=OpenWorkouts, name='logout')
77def logout(context, request):
78    headers = forget(request)
79    return HTTPFound(location=request.resource_url(context), headers=headers)
80
81
82@view_config(
83    context=OpenWorkouts,
84    name='signup',
85    renderer='ow:templates/signup.pt')
86def signup(context, request):
87    state = State(emails=context.lowercase_emails,
88                  names=context.lowercase_nicknames)
89    form = Form(request, schema=SignUpSchema(), state=state)
90
91    if 'submit' in request.POST and form.validate():
92        user = form.bind(User(), exclude=['password_confirm'])
93        context.add_user(user)
94        # Send to login
95        return HTTPFound(location=request.resource_url(context))
96
97    return {
98        'form': OWFormRenderer(form)
99    }
100
101
102@view_config(
103    context=OpenWorkouts,
104    name='forgot-password',
105    renderer='ow:templates/forgot_password.pt')
106def recover_password(context, request):  # pragma: no cover
107    # WIP
108    Form(request)
109
110
111@view_config(
112    context=User,
113    permission='view',
114    renderer='ow:templates/dashboard.pt')
115def dashboard(context, request):
116    """
117    Render a dashboard for the current user
118    """
119    # Look at the year we are viewing, if none is passed in the request,
120    # pick up the latest/newer available with activity
121    viewing_year = request.GET.get('year', None)
122    if viewing_year is None:
123        available_years = context.activity_years
124        if available_years:
125            viewing_year = available_years[0]
126    else:
127        # ensure this is an integer
128        viewing_year = int(viewing_year)
129
130    # Same for the month, if there is a year set
131    viewing_month = None
132    if viewing_year:
133        viewing_month = request.GET.get('month', None)
134        if viewing_month is None:
135            available_months = context.activity_months(viewing_year)
136            if available_months:
137                # we pick up the latest month available for the year,
138                # which means the current month in the current year
139                viewing_month = available_months[-1]
140        else:
141            # ensure this is an integer
142            viewing_month = int(viewing_month)
143
144    # pick up the workouts to be shown in the dashboard
145    workouts = context.workouts(viewing_year, viewing_month)
146
147    return {
148        'current_year': datetime.now(timezone.utc).year,
149        'current_day_name': datetime.now(timezone.utc).strftime('%a'),
150        'month_name': month_name,
151        'viewing_year': viewing_year,
152        'viewing_month': viewing_month,
153        'workouts': workouts
154    }
155
156
157@view_config(
158    context=User,
159    permission='view',
160    name='profile',
161    renderer='ow:templates/profile.pt')
162def profile(context, request):
163    """
164    "public" profile view, showing some workouts from this user, her
165    basic info, stats, etc
166    """
167    now = datetime.now(timezone.utc)
168    year = int(request.GET.get('year', now.year))
169    month = int(request.GET.get('month', now.month))
170    week = request.GET.get('week', None)
171    return {
172        'workouts': context.workouts(year, month, week),
173        'current_month': '{year}-{month}'.format(
174            year=str(year), month=str(month).zfill(2)),
175        'current_week': week
176    }
177
178
179@view_config(
180    context=User,
181    name='picture',
182    permission='view')
183def profile_picture(context, request):
184    return Response(
185        content_type='image',
186        body_file=context.picture.open())
187
188
189@view_config(
190    context=User,
191    permission='edit',
192    name='edit',
193    renderer='ow:templates/edit_profile.pt')
194def edit_profile(context, request):
195    # if not given a file there is an empty byte in POST, which breaks
196    # our blob storage validator.
197    # dirty fix until formencode fixes its api.is_empty method
198    if isinstance(request.POST.get('picture', None), bytes):
199        request.POST['picture'] = ''
200
201    nicknames = request.root.lowercase_nicknames
202    if context.nickname:
203        # remove the current user nickname from the list, preventing form
204        # validation error
205        nicknames.remove(context.nickname.lower())
206    state = State(emails=request.root.lowercase_emails, names=nicknames)
207    form = Form(request, schema=UserProfileSchema(), state=state, obj=context)
208
209    if 'submit' in request.POST and form.validate():
210        # No picture? do not override it
211        if not form.data['picture']:
212            del form.data['picture']
213        form.bind(context)
214        # reindex
215        request.root.reindex(context)
216        # Saved, send the user to the public view of her profile
217        return HTTPFound(location=request.resource_url(context, 'profile'))
218
219    # prevent crashes on the form
220    if 'picture' in form.data:
221        del form.data['picture']
222
223    return {'form': OWFormRenderer(form),
224            'timezones': common_timezones}
225
226
227@view_config(
228    context=User,
229    permission='edit',
230    name='passwd',
231    renderer='ow:templates/change_password.pt')
232def change_password(context, request):
233    form = Form(request, schema=ChangePasswordSchema(),
234                state=State(user=context))
235    if 'submit' in request.POST and form.validate():
236        context.password = form.data['password']
237        return HTTPFound(location=request.resource_url(context, 'profile'))
238    return {'form': OWFormRenderer(form)}
239
240
241@view_config(
242    context=User,
243    permission='view',
244    name='week')
245def week_stats(context, request):
246    stats = context.week_stats
247    json_stats = []
248    for day in stats:
249        hms = timedelta_to_hms(stats[day]['time'])
250        day_stats = {
251            'name': day.strftime('%a'),
252            'time': str(hms[0]).zfill(2),
253            'distance': int(round(stats[day]['distance'])),
254            'elevation': int(stats[day]['elevation']),
255            'workouts': stats[day]['workouts']
256        }
257        json_stats.append(day_stats)
258    return Response(content_type='application/json',
259                    charset='utf-8',
260                    body=json.dumps(json_stats))
261
262
263@view_config(
264    context=User,
265    permission='view',
266    name='monthly')
267def last_months_stats(context, request):
268    """
269    Return a json-encoded stream with statistics for the last 12 months
270    """
271    stats = context.yearly_stats
272    # this sets which month is 2 times in the stats, once this year, once
273    # the previous year. We will show it a bit different in the UI (showing
274    # the year too to prevent confusion)
275    repeated_month = datetime.now(timezone.utc).date().month
276    json_stats = []
277    for month in stats:
278        hms = timedelta_to_hms(stats[month]['time'])
279        name = month_name[month[1]][:3]
280        if month[1] == repeated_month:
281            name += ' ' + str(month[0])
282        month_stats = {
283            'id': str(month[0]) + '-' + str(month[1]).zfill(2),
284            'name': name,
285            'time': str(hms[0]).zfill(2),
286            'distance': int(round(stats[month]['distance'])),
287            'elevation': int(stats[month]['elevation']),
288            'workouts': stats[month]['workouts'],
289            'url': request.resource_url(
290                context, 'profile',
291                query={'year': str(month[0]), 'month': str(month[1])},
292                anchor='workouts')
293        }
294        json_stats.append(month_stats)
295    return Response(content_type='application/json',
296                    charset='utf-8',
297                    body=json.dumps(json_stats))
298
299
300@view_config(
301    context=User,
302    permission='view',
303    name='weekly')
304def last_weeks_stats(context, request):
305    """
306    Return a json-encoded stream with statistics for the last 12-months, but
307    in a per-week basis
308    """
309    stats = context.weekly_year_stats
310    # this sets which month is 2 times in the stats, once this year, once
311    # the previous year. We will show it a bit different in the UI (showing
312    # the year too to prevent confusion)
313    repeated_month = datetime.now(timezone.utc).date().month
314    json_stats = []
315    for week in stats:
316        hms = timedelta_to_hms(stats[week]['time'])
317        name = month_name[week[1]][:3]
318        if week[1] == repeated_month:
319            name += ' ' + str(week[0])
320        week_stats = {
321            'id': '-'.join(
322                [str(week[0]), str(week[1]).zfill(2), str(week[2])]),
323            'week': str(week[3]),  # the number of week in the current month
324            'name': name,
325            'time': str(hms[0]).zfill(2),
326            'distance': int(round(stats[week]['distance'])),
327            'elevation': int(stats[week]['elevation']),
328            'workouts': stats[week]['workouts'],
329            'url': request.resource_url(
330                context, 'profile',
331                query={'year': str(week[0]),
332                       'month': str(week[1]),
333                       'week': str(week[2])})
334        }
335        json_stats.append(week_stats)
336    return Response(content_type='application/json',
337                    charset='utf-8',
338                    body=json.dumps(json_stats))
Note: See TracBrowser for help on using the repository browser.