Changeset 42baca4 in OpenWorkouts-current for ow


Ignore:
Timestamp:
Apr 22, 2019, 6:14:53 PM (5 years ago)
Author:
Borja Lopez <borja@…>
Branches:
current
Children:
0dedfbe
Parents:
e52a502
Message:

(#39) Do not allow duplicated workouts by default when uploading track files.
We still allow users to add duplicates if they want, by checking a checkbox
we show in the upload workout form when we find a possible duplicate.

Location:
ow
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • ow/models/root.py

    re52a502 r42baca4  
    4343            'nickname': CatalogFieldIndex('nickname'),
    4444            'sport': CatalogFieldIndex('sport'),
     45            'hashed': CatalogFieldIndex('hashed'),
    4546        }
    4647        return indexes
     
    140141    def sports_json(self):
    141142        return json.dumps(self.sports)
     143
     144    def get_workout_by_hash(self, hashed):
     145        if hashed is not None:
     146            # for some reason, when searching for None
     147            # the catalog will return all workouts
     148            res = self.query(Eq('hashed', hashed))
     149            if res:
     150                return next(res)
     151        return None
  • ow/models/workout.py

    re52a502 r42baca4  
    136136
    137137    @property
     138    def hashed(self):
     139        """
     140        Return a unique hash that we use to look for duplicated workouts.
     141        The hash contains: owner's uid, start time, duration and distance
     142        """
     143        hashed = ''
     144        if self.owner is not None:
     145            hashed += str(self.owner.uid)
     146        hashed += self.start.strftime('%Y%m%d%H%M%S')
     147        hashed += str(self.duration.seconds)
     148        hashed += str(self.distance)
     149        return hashed
     150
     151    @property
    138152    def trimmed_notes(self):
    139153        """
  • ow/templates/add_workout.pt

    re52a502 r42baca4  
    2525        ${form.csrf_token()}
    2626
    27         <fieldset>
    28           <p>
    29               <label for="title" i18n:translate="">Title</label>
    30               ${form.errorlist('title')}
    31               ${form.text('title')}
    32           </p>
    33           <p>
    34               <label for="notes" i18n:translate="">Notes</label>
    35               ${form.errorlist('notes')}
    36               ${form.textarea('notes', rows=10, cols=50)}
    37           </p>
    38           <p>
    39             <label for="tracking_file" i18n:translate="">
    40               Workout file (gpx, fit)</label>
    41             ${form.errorlist('tracking_file')}
    42             ${form.file('tracking_file')}
    43           </p>
    44         </fieldset>
     27      <tal:is_duplicate tal:condition="duplicate is not None">
     28        <article class="workout-resume">
    4529
    46         <tal:with-localizer tal:define="localizer get_localizer(request)">
    47           ${form.submit("submit", localizer.translate(_('Save')),  **{'class':"button button-normal"})}
    48         </tal:with-localizer>
     30          <h2 class="workout-title">
     31            <tal:warning i18n:translate="">THIS MAY BE A DUPLICATE OF:</tal:warning>
     32            <a href="" tal:content="duplicate.title"
     33               tal:attributes="href request.resource_url(duplicate)"></a>
     34          </h2>
     35
     36          <ul class="workout-info">
     37            <li>
     38              <tal:c tal:content="duplicate.sport"></tal:c>
     39            </li>
     40            <li>
     41              <tal:c tal:content="duplicate.start_in_timezone(context.timezone)"></tal:c>
     42            </li>
     43            <li>
     44              <!--! use the properly formatted duration instead of the timedelta object -->
     45              <tal:c tal:content="duplicate._duration"></tal:c>
     46            </li>
     47            <li tal:condition="duplicate.distance">
     48              <tal:c tal:content="duplicate.rounded_distance"></tal:c> km
     49            </li>
     50            <li tal:condition="duplicate.uphill">
     51              +<tal:c tal:content="duplicate.uphill"></tal:c> m
     52            </li>
     53          </ul>
     54
     55          <div class="workout-map" tal:condition="duplicate.has_gpx">
     56            <a href="" tal:attributes="href request.resource_url(duplicate)">
     57              <tal:has-screenshot tal:condition="duplicate.map_screenshot is not None">
     58                <img src="" tal:attributes="src request.static_url(duplicate.map_screenshot);
     59                          alt duplicate.title; title duplicate.title">
     60              </tal:has-screenshot>
     61              <tal:has-not-screenshot tal:condition="duplicate.map_screenshot is None">
     62                <img src="" tal:attributes="src request.static_url('ow:static/media/img/no_map.gif');
     63                          alt duplicate.title; title duplicate.title; class 'js-needs-map'">
     64              </tal:has-not-screenshot>
     65            </a>
     66          </div>
     67
     68        </article>
     69
     70        <div>
     71      </tal:is_duplicate>
     72
     73      <fieldset>
     74
     75        <p>
     76          <label for="title" i18n:translate="">Title</label>
     77          ${form.errorlist('title')}
     78          ${form.text('title')}
     79        </p>
     80
     81        <p>
     82          <label for="notes" i18n:translate="">Notes</label>
     83          ${form.errorlist('notes')}
     84          ${form.textarea('notes', rows=10, cols=50)}
     85        </p>
     86
     87        <p>
     88          <label for="tracking_file" i18n:translate="">
     89            Workout file (gpx, fit)</label>
     90          ${form.errorlist('tracking_file')}
     91          ${form.file('tracking_file')}
     92        </p>
     93
     94        <p tal:condition="duplicate is not None">
     95          <label for="allow_duplicates" i18n:translate="">Allow duplicated workouts</label>
     96          <small i18n:translate="">
     97            Mark this checkbox if you want to add to override the mechanism not allowing duplicated workouts
     98          </small>
     99          <input type="checkbox" name="allow_duplicates">
     100        </p>
     101
     102      </fieldset>
     103
     104      <tal:with-localizer tal:define="localizer get_localizer(request)">
     105        ${form.submit("submit", localizer.translate(_('Save')),  **{'class':"button button-normal"})}
     106      </tal:with-localizer>
    49107
    50108      ${form.end()}
  • ow/tests/models/test_root.py

    re52a502 r42baca4  
    3535        # a new OpenWorkouts instance has a catalog created automatically
    3636        assert isinstance(root.catalog, Catalog)
    37         assert len(root.catalog) == 3
    38         for key in ['email', 'nickname', 'sport']:
     37        assert len(root.catalog) == 4
     38        for key in ['email', 'nickname', 'sport', 'hashed']:
    3939            assert key in root.catalog
    4040
    4141    def test_update_indexes(self, root):
    4242        indexes = sorted([i for i in root.catalog])
    43         assert indexes == ['email', 'nickname', 'sport']
     43        assert indexes == ['email', 'hashed', 'nickname', 'sport']
    4444        # remove one index
    4545        del root.catalog['email']
    4646        indexes = sorted([i for i in root.catalog])
    47         assert indexes == ['nickname', 'sport']
     47        assert indexes == ['hashed', 'nickname', 'sport']
    4848        # now update indexes, the index will be back there
    4949        root._update_indexes()
    5050        indexes = sorted([i for i in root.catalog])
    51         assert indexes == ['email', 'nickname', 'sport']
     51        assert indexes == ['email', 'hashed', 'nickname', 'sport']
    5252
    5353    def test_add_user_ok(self, root):
  • ow/tests/test_catalog.py

    re52a502 r42baca4  
    5656        changes = update_indexes(catalog, indexes)
    5757        assert changes['added'] == ['newindex']
    58         assert changes['removed'] == ['email', 'nickname', 'sport']
     58        assert changes['removed'] == ['email', 'nickname', 'sport', 'hashed']
    5959
    6060    def test_update_indexes_empty(self, root):
     
    6363        changes = update_indexes(catalog, indexes)
    6464        assert changes['added'] == []
    65         assert changes['removed'] == ['email', 'nickname', 'sport']
     65        assert changes['removed'] == ['email', 'nickname', 'sport', 'hashed']
    6666
    6767    def test_install_catalog(self):
  • ow/views/workout.py

    re52a502 r42baca4  
    8989    form = Form(request, schema=UploadedWorkoutSchema())
    9090
     91    duplicate = None
     92    allow_duplicates = request.POST.get('allow_duplicates') == 'on'
     93
    9194    if 'submit' in request.POST and form.validate():
    9295        # Grab some information from the tracking file
     
    98101        # Add basic info gathered from the file
    99102        workout.load_from_file()
    100         # Add the workout
    101         context.add_workout(workout)
    102         return HTTPFound(location=request.resource_url(workout))
     103        # Ensure this workout is not a duplicate of an existing workout.
     104        #
     105        # hashed is not "complete" for a workout that has not been added
     106        # yet, as it does not have the owner set, so we have to "build it"
     107        hashed = str(context.uid) + workout.hashed
     108        duplicate = request.root.get_workout_by_hash(hashed)
     109
     110        if duplicate and not allow_duplicates:
     111            form.errors['tracking_file'] = _(
     112                'This workout looks like a duplicate of another workout, '
     113                'please enable workout duplicates below to save it'
     114            )
     115        else:
     116            # Add the workout
     117            context.add_workout(workout)
     118            return HTTPFound(location=request.resource_url(workout))
    103119
    104120    return {
    105         'form': FormRenderer(form)
     121        'form': FormRenderer(form),
     122        'duplicate': duplicate
    106123    }
    107124
Note: See TracChangeset for help on using the changeset viewer.