Skip to content

Commit

Permalink
Extract user-specific data from music data
Browse files Browse the repository at this point in the history
Up to now, user-specific data such as the rating, favorite tracks / albums /
artists or the last play date was mixed with the music data (e.g. ID3 tags).

This commit aims to move the user-specific information to a dedicated table.
This will allow sharing the music library among multiple users.
  • Loading branch information
DocMarty84 committed Sep 16, 2018
1 parent 829c863 commit 66da87e
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 34 deletions.
52 changes: 36 additions & 16 deletions controllers/subsonic/listing.py
Expand Up @@ -76,11 +76,16 @@ def getAlbumList(self, **kwargs):
folders = FolderObj.search(domain, order='create_date desc')

elif list_type == 'recent':
q_select = ' SELECT folder_id FROM oomusic_track '
q_where = ' WHERE user_id = %s and last_play is not NULL ' % (request.env.user.id)
q_select = '''
SELECT t.folder_id FROM oomusic_track t
JOIN oomusic_preference AS p ON t.id = p.res_id
'''
q_where = '''
WHERE p.user_id = %s AND p.last_play IS NOT NULL AND p.res_model = \'oomusic.track\'
''' % (request.env.user.id)
if folderId:
q_where += ' and root_folder_id = %s ' % (folderId)
q_order = ' ORDER BY last_play desc;'
q_where += 'AND t.root_folder_id = %s ' % (folderId)
q_order = 'ORDER BY p.last_play desc;'
query = q_select + q_where + q_order
request.env.cr.execute(query)
res = request.env.cr.fetchall()
Expand All @@ -89,11 +94,16 @@ def getAlbumList(self, **kwargs):
folders = FolderObj.browse(folder_ids)

elif list_type == 'frequent':
q_select = ' SELECT folder_id FROM oomusic_track '
q_where = ' WHERE user_id = %s and play_count > 0 ' % (request.env.user.id)
q_select = '''
SELECT t.folder_id FROM oomusic_track t
JOIN oomusic_preference AS p ON t.id = p.res_id
'''
q_where = '''
WHERE p.user_id = %s AND p.play_count > 0 AND p.res_model = \'oomusic.track\'
''' % (request.env.user.id)
if folderId:
q_where += ' and root_folder_id = %s ' % (folderId)
q_order = ' ORDER BY play_count desc;'
q_where += 'AND t.root_folder_id = %s ' % (folderId)
q_order = 'ORDER BY p.play_count desc;'
query = q_select + q_where + q_order
request.env.cr.execute(query)
res = request.env.cr.fetchall()
Expand Down Expand Up @@ -191,11 +201,16 @@ def getAlbumList2(self, **kwargs):
albums = AlbumObj.search(domain, order='create_date desc')

elif list_type == 'recent':
q_select = ' SELECT album_id FROM oomusic_track '
q_where = ' WHERE user_id = %s and last_play is not NULL ' % (request.env.user.id)
q_select = '''
SELECT t.album_id FROM oomusic_track t
JOIN oomusic_preference AS p ON t.id = p.res_id
'''
q_where = '''
WHERE p.user_id = %s AND p.last_play IS NOT NULL AND p.res_model = \'oomusic.track\'
''' % (request.env.user.id)
if folderId:
q_where += ' and root_folder_id = %s ' % (folderId)
q_order = ' ORDER BY last_play desc;'
q_where += 'AND t.root_folder_id = %s ' % (folderId)
q_order = 'ORDER BY p.last_play desc;'
query = q_select + q_where + q_order
request.env.cr.execute(query)
res = request.env.cr.fetchall()
Expand All @@ -204,11 +219,16 @@ def getAlbumList2(self, **kwargs):
albums = AlbumObj.browse(album_ids)

elif list_type == 'frequent':
q_select = ' SELECT album_id FROM oomusic_track '
q_where = ' WHERE user_id = %s and play_count > 0 ' % (request.env.user.id)
q_select = '''
SELECT t.album_id FROM oomusic_track t
JOIN oomusic_preference AS p ON t.id = p.res_id
'''
q_where = '''
WHERE p.user_id = %s AND p.play_count > 0 AND p.res_model = \'oomusic.track\'
''' % (request.env.user.id)
if folderId:
q_where += ' and root_folder_id = %s ' % (folderId)
q_order = ' ORDER BY play_count desc;'
q_where += 'AND t.root_folder_id = %s ' % (folderId)
q_order = 'ORDER BY p.play_count desc;'
query = q_select + q_where + q_order
request.env.cr.execute(query)
res = request.env.cr.fetchall()
Expand Down
1 change: 1 addition & 0 deletions models/__init__.py
Expand Up @@ -2,6 +2,7 @@

# oomusic_download must be first since will inherit from it
from . import oomusic_download
from . import oomusic_preference
from . import oomusic_album
from . import oomusic_artist
from . import oomusic_config_settings
Expand Down
11 changes: 7 additions & 4 deletions models/oomusic_album.py
Expand Up @@ -10,7 +10,7 @@ class MusicAlbum(models.Model):
_name = 'oomusic.album'
_description = 'Music Album'
_order = 'year desc, name'
_inherit = ['oomusic.download.mixin']
_inherit = ['oomusic.download.mixin', 'oomusic.preference.mixin']

create_date = fields.Datetime(index=True)

Expand All @@ -25,13 +25,16 @@ class MusicAlbum(models.Model):
'res.users', string='User', index=True, required=True, ondelete='cascade',
default=lambda self: self.env.user
)
in_playlist = fields.Boolean('In Current Playlist')
in_playlist = fields.Boolean(
'In Current Playlist', compute='_compute_in_playlist', inverse='_inverse_in_playlist',
search='_search_in_playlist')

star = fields.Selection(
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite', default='0')
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite',
compute='_compute_star', inverse='_inverse_star', search='_search_star')
rating = fields.Selection(
[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')],
'Rating', default='0',
'Rating', compute='_compute_rating', inverse='_inverse_rating', search='_search_rating'
)

image_folder = fields.Binary('Folder Image', related='folder_id.image_folder')
Expand Down
7 changes: 4 additions & 3 deletions models/oomusic_artist.py
Expand Up @@ -17,7 +17,7 @@ class MusicArtist(models.Model):
_name = 'oomusic.artist'
_description = 'Music Artist'
_order = 'name'
_inherit = ['oomusic.download.mixin']
_inherit = ['oomusic.download.mixin', 'oomusic.preference.mixin']

name = fields.Char('Artist', index=True)
track_ids = fields.One2many('oomusic.track', 'artist_id', string='Tracks', readonly=True)
Expand All @@ -28,10 +28,11 @@ class MusicArtist(models.Model):
)

star = fields.Selection(
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite', default='0')
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite',
compute='_compute_star', inverse='_inverse_star', search='_search_star')
rating = fields.Selection(
[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')],
'Rating', default='0',
'Rating', compute='_compute_rating', inverse='_inverse_rating', search='_search_rating'
)

fm_image = fields.Binary(
Expand Down
6 changes: 4 additions & 2 deletions models/oomusic_playlist.py
Expand Up @@ -123,14 +123,16 @@ def action_current(self):

# Reset 'in_playlist' field. Done with SQL for faster execution.
self.env.cr.execute('''
UPDATE oomusic_track SET in_playlist = false
UPDATE oomusic_preference SET in_playlist = false
WHERE user_id = %s
AND in_playlist = true
AND res_model = 'oomusic.track'
''', (self.env.uid, ))
self.env.cr.execute('''
UPDATE oomusic_album SET in_playlist = false
UPDATE oomusic_preference SET in_playlist = false
WHERE user_id = %s
AND in_playlist = true
AND res_model = 'oomusic.album'
''', (self.env.uid, ))

# Recompute 'in_playlist' field
Expand Down
139 changes: 139 additions & 0 deletions models/oomusic_preference.py
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-

from odoo import api, fields, models


class MusicPreference(models.Model):
_name = 'oomusic.preference'
_rec_name = 'res_model'
_order = 'id'
_description = 'User preference'

res_model_id = fields.Many2one(
'ir.model', 'Related Document Model', ondelete='cascade',
help='Model of the preference resource')
res_model = fields.Char(
string='Document Model', related='res_model_id.model', store=True, readonly=True)
res_id = fields.Integer(
string='Document', required=True, help='Identifier of the preference object')
res_user_id = fields.Many2one(
'res.users', string='Document User', index=True, required=True, ondelete='cascade'
)
user_id = fields.Many2one(
'res.users', string='User', index=True, required=True, ondelete='cascade',
default=lambda self: self.env.user
)
play_count = fields.Integer('Play Count', default=0, readonly=True)
last_play = fields.Datetime('Last Played', index=True, readonly=True)
in_playlist = fields.Boolean('In Current Playlist')
star = fields.Selection(
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite', default='0')
rating = fields.Selection(
[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')],
'Rating', default='0',
)


class MusicPreferenceMixin(models.AbstractModel):
_name = 'oomusic.preference.mixin'
_description = "Download Mixin"

pref_ids = fields.One2many(
'oomusic.preference', 'res_id', string='User Preferences',
domain=lambda self: [('res_model', '=', self._name), ('user_id', '=', self.env.user.id)],
auto_join=True)

@api.depends('pref_ids')
def _compute_play_count(self):
for obj in self:
obj.play_count = obj._get_pref('play_count')

def _inverse_play_count(self):
for obj in self:
obj._set_pref({'play_count': obj.play_count})

def _search_play_count(self, operator, value):
return self._search_pref('play_count', operator, value)

@api.depends('pref_ids')
def _compute_last_play(self):
for obj in self:
obj.last_play = obj._get_pref('last_play')

def _inverse_last_play(self):
for obj in self:
obj._set_pref({'last_play': obj.last_play})

def _search_last_play(self, operator, value):
return self._search_pref('last_play', operator, value)

@api.depends('pref_ids')
def _compute_in_playlist(self):
for obj in self:
obj.in_playlist = obj._get_pref('in_playlist')

def _inverse_in_playlist(self):
for obj in self:
obj._set_pref({'in_playlist': obj.in_playlist})

def _search_in_playlist(self, operator, value):
return self._search_pref('in_playlist', operator, value)

@api.depends('pref_ids')
def _compute_star(self):
for obj in self:
obj.star = obj._get_pref('star')

def _inverse_star(self):
for obj in self:
obj._set_pref({'star': obj.star})

def _search_star(self, operator, value):
return self._search_pref('star', operator, value)

@api.depends('pref_ids')
def _compute_rating(self):
for obj in self:
obj.rating = obj._get_pref('rating')

def _inverse_rating(self):
for obj in self:
obj._set_pref({'rating': obj.rating})

def _search_rating(self, operator, value):
return self._search_pref('rating', operator, value)

def _get_pref(self, field):
return self.pref_ids[field]

def _set_pref(self, vals):
invalidate_cache = False
for obj in self:
if not obj.pref_ids:
vals['res_model_id'] = self.env['ir.model'].sudo().search(
[('model', '=', obj._name)], limit=1).id
vals['res_id'] = obj.id
vals['res_user_id'] = obj.user_id.id
self.env['oomusic.preference'].create(vals)
# In case no preference entry exist and we write on more than one preference field,
# we create as many preference entry as preference fields. We force cache
# invalidation to prevent this.
invalidate_cache = True
else:
obj.pref_ids.write(vals)
if invalidate_cache:
self.invalidate_cache()

def _search_pref(self, field, operator, value):
pref = self.env['oomusic.preference'].search([
(field, operator, value), ('res_model', '=', self._name)
])
return [('id', 'in', pref.mapped('res_id'))]

def unlink(self):
""" When removing a record, its preferences should be deleted too. """
rec_ids = self.ids
res = super(MusicPreferenceMixin, self).unlink()
self.env['oomusic.preference'].sudo().search(
[('res_model', '=', self._name), ('res_id', 'in', rec_ids)]).unlink()
return res
5 changes: 3 additions & 2 deletions models/oomusic_suggestion.py
Expand Up @@ -26,8 +26,9 @@ class MusicSuggestion(models.TransientModel):

@api.depends('name_tracks')
def _compute_track_last_played(self):
self.track_last_played = self.env['oomusic.track'].search(
[('play_count', '>', 0)], order='last_play desc', limit=10)
self.track_last_played = self.env['oomusic.preference'].search(
[('play_count', '>', 0), ('res_model', '=', 'oomusic.track')],
order='last_play desc', limit=10).mapped('res_id')

@api.depends('name_tracks')
def _compute_track_recently_added(self):
Expand Down
20 changes: 13 additions & 7 deletions models/oomusic_track.py
Expand Up @@ -17,7 +17,7 @@ class MusicTrack(models.Model):
_name = 'oomusic.track'
_description = 'Music Track'
_order = 'album_id, disc, track_number_int, track_number, path'
_inherit = ['oomusic.download.mixin']
_inherit = ['oomusic.download.mixin', 'oomusic.preference.mixin']

create_date = fields.Datetime(index=True)

Expand Down Expand Up @@ -45,8 +45,12 @@ class MusicTrack(models.Model):
bitrate = fields.Integer('Bitrate (kbps)', readonly=True)
path = fields.Char('Path', required=True, index=True, readonly=True)
size = fields.Float('File Size (MiB)', readonly=True)
play_count = fields.Integer('Play Count', default=0, readonly=True)
last_play = fields.Datetime('Last Played', index=True, readonly=True)
play_count = fields.Integer(
'Play Count', readonly=True,
compute='_compute_play_count', inverse='_inverse_play_count', search='_search_play_count')
last_play = fields.Datetime(
'Last Played', readonly=True,
compute='_compute_last_play', inverse='_inverse_last_play', search='_search_last_play')
last_modification = fields.Integer('Last Modification', readonly=True)
root_folder_id = fields.Many2one(
'oomusic.folder', string='Root Folder', index=True, required=True, ondelete='cascade')
Expand All @@ -57,13 +61,15 @@ class MusicTrack(models.Model):
'res.users', string='User', index=True, required=True, ondelete='cascade',
default=lambda self: self.env.user
)
playlist_line_ids = fields.One2many('oomusic.playlist.line', 'track_id', 'Playlist Line')
in_playlist = fields.Boolean('In Current Playlist')
in_playlist = fields.Boolean(
'In Current Playlist', compute='_compute_in_playlist', inverse='_inverse_in_playlist',
search='_search_in_playlist')
star = fields.Selection(
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite', default='0')
[('0', 'Normal'), ('1', 'I Like It!')], 'Favorite',
compute='_compute_star', inverse='_inverse_star', search='_search_star')
rating = fields.Selection(
[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')],
'Rating', default='0',
'Rating', compute='_compute_rating', inverse='_inverse_rating', search='_search_rating'
)
dummy_field = fields.Boolean('Dummy field')

Expand Down
1 change: 1 addition & 0 deletions security/ir.model.access.csv
Expand Up @@ -8,6 +8,7 @@ access_oomusic_genre,oomusic.genre,model_oomusic_genre,base.group_user,1,1,0,0
access_oomusic_lastfm,oomusic.lastfm,model_oomusic_lastfm,base.group_user,1,1,1,1
access_oomusic_playlist,oomusic.playlist,model_oomusic_playlist,base.group_user,1,1,1,1
access_oomusic_playlist_line,oomusic.playlist.line,model_oomusic_playlist_line,base.group_user,1,1,1,1
access_oomusic_preference,oomusic.preference,model_oomusic_preference,base.group_user,1,1,1,1
access_oomusic_converter,oomusic.converter,model_oomusic_converter,base.group_user,1,1,1,1
access_oomusic_converter_line,oomusic.converter.line,model_oomusic_converter_line,base.group_user,1,1,1,1
access_oomusic_track,oomusic.track,model_oomusic_track,base.group_user,1,1,0,0
Expand Down
6 changes: 6 additions & 0 deletions security/oomusic_security.xml
Expand Up @@ -36,6 +36,12 @@
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="oomusic_preference" model="ir.rule">
<field name="name">oomusic.preference: see own preferences</field>
<field name="model_id" ref="model_oomusic_preference"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="oomusic_track" model="ir.rule">
<field name="name">oomusic.track: see own tracks</field>
<field name="model_id" ref="model_oomusic_track"/>
Expand Down

0 comments on commit 66da87e

Please sign in to comment.