Changeset 31adfa5 in OpenWorkouts-current


Ignore:
Timestamp:
Dec 21, 2018, 11:00:25 AM (5 years ago)
Author:
borja <borja@…>
Branches:
current, feature/docs, master
Children:
fe6089a
Parents:
d507f75
Message:

(#14) Timezones support:

  • Added pytz as a new dependency, please install it in your existing envs:

pip install pytz

  • Added a timezone attribute to users, to store in which timezone they are, defaults to 'UTC'. Ensure any users you could have in your database have such attribute. You can add it in pshell:

for user in root.users:

user.timezone = 'UTC'

request.tm.commit()

  • Modified schemas/templates/views to let users choose their timezone based on a list of "common" timezones provided by pytz
  • Added two methods to the Workout model so we can get the start and end dates formatted in the appropiate timezone (all datetime objects are stored in UTC)
  • Modified the templates where we show workout dates and times so the new timezone-formatting methods are used.
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • ow/models/user.py

    rd507f75 r31adfa5  
    3333        self.gender = kw.get('gender', 'female')
    3434        self.picture = kw.get('picture', None)  # blob
     35        self.timezone = kw.get('timezone', 'UTC')
    3536        self.__password = None
    3637        self.last_workout_id = 0
  • ow/models/workout.py

    rd507f75 r31adfa5  
    33from decimal import Decimal
    44
     5import pytz
    56import gpxpy
    67from repoze.folder import Folder
     
    7475        return self.start.strftime('%H:%M')
    7576
     77    def start_in_timezone(self, timezone):
     78        """
     79        Return a string representation of the start date and time,
     80        localized into the given timezone
     81        """
     82        _start = self.start.astimezone(pytz.timezone(timezone))
     83        return _start.strftime('%d/%m/%Y %H:%M (%Z)')
     84
     85    def end_in_timezone(self, timezone):
     86        """
     87        Return a string representation of the end date and time,
     88        localized into the given timezone
     89        """
     90        _end = self.end.astimezone(pytz.timezone(timezone))
     91        return _end.strftime('%d/%m/%Y %H:%M (%Z)')
     92
    7693    def split_duration(self):
    7794        hours, remainder = divmod(int(self.duration.total_seconds()), 3600)
  • ow/schemas/user.py

    rd507f75 r31adfa5  
    11from pyramid.i18n import TranslationStringFactory
    22from formencode import Schema, validators
     3from pytz import common_timezones
    34
    45from ow.schemas.blob import FieldStorageBlob
     
    7475    picture = FieldStorageBlob(if_emtpy=None, if_missing=None,
    7576                               whitelist=['jpg', 'jpeg', 'png', 'gif'])
     77    timezone = validators.OneOf(common_timezones, if_missing='UTC')
    7678
    7779
  • ow/templates/dashboard.pt

    rd507f75 r31adfa5  
    3838            <ul class="workout-info">
    3939              <li>
    40                 <tal:c tal:content="workout.start"></tal:c>
     40                <tal:c tal:content="workout.start_in_timezone(context.timezone)"></tal:c>
    4141              </li>
    4242              <li>
  • ow/templates/delete_workout.pt

    rd507f75 r31adfa5  
    2525        <input id="delete" name="delete" type="hidden" value="yes" />
    2626
    27       <div id="workout">
     27        <div id="workout"
     28             tal:define="timezone request.root[request.authenticated_userid].timezone">
    2829        <h3 tal:content="context.title"></h3>
    2930        <ul id="" tal:attributes="id 'workout-' + context.workout_id + '-details'">
    3031          <li>
    3132            <tal:t i18n:translate="">Start:</tal:t>
    32             <tal:c tal:content="context.start"></tal:c>
     33            <tal:c tal:content="context.start_in_timezone(timezone)"></tal:c>
    3334          </li>
    3435          <li>
    3536            <tal:t i18n:translate="">End:</tal:t>
    36             <tal:c tal:content="context.end"></tal:c>
     37            <tal:c tal:content="context.end_in_timezone(timezone)"></tal:c>
    3738          </li>
    3839          <li>
  • ow/templates/edit_profile.pt

    rd507f75 r31adfa5  
    9999        </fieldset>
    100100
     101        <fieldset>
     102            <p>
     103                <label for="timezone" i18n:translate="">Timezone:</label>
     104                <small i18n:translate="">
     105                    All dates and times will be formatted for this timezone
     106                </small>
     107                ${form.errorlist('timezone')}
     108                ${form.select('timezone', timezones)}
     109            </p>
     110        </fieldset>
     111
    101112        <p>
    102113            ${form.submit("submit", "Save",  **{'class':"button button-normal"})}
  • ow/templates/workout.pt

    rd507f75 r31adfa5  
    7878
    7979      <ul id="" tal:attributes="id 'workout-' + context.workout_id + '-details'"
    80           tal:define="hr context.hr; cad context.cad">
     80          tal:define="hr context.hr; cad context.cad; timezone context.owner.timezone">
    8181        <li>
    8282          <tal:t i18n:translate="">Start:</tal:t>
    83           <tal:c tal:content="context.start"></tal:c>
     83          <tal:c tal:content="context.start_in_timezone(timezone)"></tal:c>
    8484        </li>
    8585        <li>
    8686          <tal:t i18n:translate="">End:</tal:t>
    87           <tal:c tal:content="context.end"></tal:c>
     87          <tal:c tal:content="context.end_in_timezone(timezone)"></tal:c>
    8888        </li>
    8989        <li>
  • ow/tests/models/test_workout.py

    rd507f75 r31adfa5  
    7777        workout = Workout(start=start_date)
    7878        assert workout.start_time == start_date.strftime('%H:%M')
     79
     80    def test_start_in_timezone(self):
     81        start_date = datetime.now(tz=timezone.utc)
     82        str_start_date = start_date.strftime('%d/%m/%Y %H:%M (%Z)')
     83        workout = Workout(start=start_date)
     84        assert workout.start_in_timezone('UTC') == str_start_date
     85        assert workout.start_in_timezone('Europe/Madrid') != str_start_date
     86        assert workout.start_in_timezone('America/Vancouver') != str_start_date
     87
     88    def test_end_in_timezone(self):
     89        start_date = datetime.now(tz=timezone.utc)
     90        end_date = start_date + timedelta(minutes=60)
     91        str_end_date = end_date.strftime('%d/%m/%Y %H:%M (%Z)')
     92        workout = Workout(start=start_date, duration=timedelta(minutes=60))
     93        assert workout.end_in_timezone('UTC') == str_end_date
     94        assert workout.end_in_timezone('Europe/Madrid') != str_end_date
     95        assert workout.end_in_timezone('America/Vancouver') != str_end_date
    7996
    8097    def test_split_duration(self):
  • ow/tests/views/test_user.py

    rd507f75 r31adfa5  
    259259        # loaded user profile
    260260        data = ['firstname', 'lastname', 'email', 'nickname', 'bio',
    261                 'birth_date', 'height', 'weight', 'gender']
     261                'birth_date', 'height', 'weight', 'gender', 'timezone']
    262262        assert list(response['form'].data.keys()) == data
    263263        # and check the email to see data is properly loaded
  • ow/views/user.py

    rd507f75 r31adfa5  
    55from pyramid.i18n import TranslationStringFactory
    66from pyramid_simpleform import Form, State
     7from pytz import common_timezones
    78
    89from ..models.user import User
     
    167168        # Saved, send the user to the public view of her profile
    168169        return HTTPFound(location=request.resource_url(context, 'profile'))
     170
    169171    # prevent crashes on the form
    170172    if 'picture' in form.data:
    171173        del form.data['picture']
    172     return {'form': OWFormRenderer(form)}
     174
     175    return {'form': OWFormRenderer(form),
     176            'timezones': common_timezones}
    173177
    174178
  • setup.py

    rd507f75 r31adfa5  
    2727    'unidecode',
    2828    'gpxpy',
    29     'lxml'
     29    'lxml',
     30    'pytz'
    3031]
    3132
Note: See TracChangeset for help on using the changeset viewer.