From 850ce4b6308434b7fe69f4a79b1ab999f44de899 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stefan=20K=C3=B6gl?= Date: Sat, 27 Apr 2013 19:50:32 +0200 Subject: [PATCH] update suggestions when subscriptions change this replaces the "update-suggestions" command that was previously run as management command --- mygpo/core/models.py | 7 ++- mygpo/core/signals.py | 6 ++ .../management/commands/update-suggestions.py | 68 ---------------------- mygpo/users/models.py | 2 + mygpo/users/signals.py | 22 +++++++ mygpo/users/tasks.py | 36 ++++++++++++ mygpo/web/views/__init__.py | 12 ++-- 7 files changed, 75 insertions(+), 78 deletions(-) delete mode 100644 mygpo/users/management/commands/update-suggestions.py create mode 100644 mygpo/users/signals.py diff --git a/mygpo/core/models.py b/mygpo/core/models.py index 81597804..6539a8c1 100644 --- a/mygpo/core/models.py +++ b/mygpo/core/models.py @@ -16,7 +16,6 @@ from mygpo.core.proxy import DocumentABCMeta from mygpo.core.slugs import SlugMixin from mygpo.core.oldid import OldIdMixin from mygpo.web.logo import CoverArt -from mygpo.users.tasks import sync_user # make sure this code is executed at startup from mygpo.core.signals import * @@ -321,7 +320,8 @@ class Podcast(Document, SlugMixin, OldIdMixin): state.subscribe(device) try: state.save() - sync_user.delay(user) + subscription_changed.send(sender=self, user=user, device=device, + subscribed=True) except Unauthorized as ex: raise SubscriptionException(ex) @@ -333,7 +333,8 @@ class Podcast(Document, SlugMixin, OldIdMixin): state.unsubscribe(device) try: state.save() - sync_user.delay(user) + subscription_changed.send(sender=self, user=user, device=device, + subscribed=False) except Unauthorized as ex: raise SubscriptionException(ex) diff --git a/mygpo/core/signals.py b/mygpo/core/signals.py index d7b38d23..9bdc1876 100644 --- a/mygpo/core/signals.py +++ b/mygpo/core/signals.py @@ -3,3 +3,9 @@ import django.dispatch # indicates that an incomplete object has been loaded from the database # the object needs to be updated from its source (eg the feed) incomplete_obj = django.dispatch.Signal() + + +# indicates that a podcast was subscribed or unsubscribed +# ``sender`` will equal the user. Additionally the parameters ``user`` and +# ``subscribed`` will be provided +subscription_changed = django.dispatch.Signal() diff --git a/mygpo/users/management/commands/update-suggestions.py b/mygpo/users/management/commands/update-suggestions.py deleted file mode 100644 index a389d48b..00000000 --- a/mygpo/users/management/commands/update-suggestions.py +++ /dev/null @@ -1,68 +0,0 @@ -from itertools import chain -from optparse import make_option -from operator import itemgetter -from collections import Counter - -from django.core.management.base import BaseCommand - -from mygpo.users.models import Suggestions, User -from mygpo.utils import progress -from mygpo.decorators import repeat_on_conflict -from mygpo.db.couchdb.user import suggestions_for_user - - -class Command(BaseCommand): - - option_list = BaseCommand.option_list + ( - make_option('--max', action='store', type='int', dest='max', default=15, help="Maximum number of suggested podcasts per user."), - make_option('--max-users', action='store', type='int', dest='max_users', default=15, help="Maximum number of users to update."), - make_option('--outdated-only', action='store_true', dest='outdated', default=False, help="Update only users where the suggestions are not up-to-date"), - make_option('--user', action='store', type='string', dest='username', default='', help="Update a specific user"), - ) - - - def handle(self, *args, **options): - - max_suggestions = options.get('max') - - if options.get('username'): - users = [User.get_user(options.get('username'))] - - else: - users = User.all_users() - users = filter(lambda u: u.is_active, users) - - if options.get('outdated'): - users = filter(lambda u: not u.suggestions_up_to_date, users) - - if options.get('max_users'): - users = users[:int(options.get('max_users'))] - - total = len(users) - - for n, user in enumerate(users): - suggestion = suggestions_for_user(user) - - subscribed_podcasts = list(set(user.get_subscribed_podcasts())) - subscribed_podcasts = filter(None, subscribed_podcasts) - - subscribed_podcasts = filter(None, subscribed_podcasts) - related = chain.from_iterable([p.related_podcasts for p in subscribed_podcasts]) - related = filter(lambda pid: not pid in suggestion.blacklist, related) - - counter = Counter(related) - get_podcast_id = itemgetter(0) - suggested = map(get_podcast_id, counter.most_common(max_suggestions)) - suggestion.podcasts = suggested - - suggestion.save() - - _update_user(user=user) - - progress(n+1, total) - - -@repeat_on_conflict(['user']) -def _update_user(user): - user.suggestions_up_to_date = True - user.save() diff --git a/mygpo/users/models.py b/mygpo/users/models.py index fed743da..428204e7 100644 --- a/mygpo/users/models.py +++ b/mygpo/users/models.py @@ -23,6 +23,8 @@ from mygpo.users.settings import FAV_FLAG, PUBLIC_SUB_PODCAST, SettingsMixin from mygpo.db.couchdb.podcast import podcasts_by_id, podcasts_to_dict from mygpo.db.couchdb.user import user_history, device_history +# make sure this code is executed at startup +from mygpo.users.signals import * RE_DEVICE_UID = re.compile(r'^[\w.-]+$') diff --git a/mygpo/users/signals.py b/mygpo/users/signals.py new file mode 100644 index 00000000..c3a9c0c8 --- /dev/null +++ b/mygpo/users/signals.py @@ -0,0 +1,22 @@ +from mygpo.core.signals import subscription_changed +from mygpo.users.tasks import sync_user, update_suggestions + + +def sync_user_on_subscription(sender, **kwargs): + """ synchronizes the user after one of his subscriptions has changed """ + user = kwargs['user'] + sync_user.delay(user) + + +subscription_changed.connect(sync_user_on_subscription, + dispatch_uid='sync_user_on_subscription') + + +def update_suggestions_on_subscription(sender, **kwargs): + """ update a user's suggestions after one of his subscriptions change """ + user = kwargs['user'] + update_suggestions.delay(user) + + +subscription_changed.connect(update_suggestions_on_subscription, + dispatch_uid='update_suggestions_on_subscription') diff --git a/mygpo/users/tasks.py b/mygpo/users/tasks.py index 78f22ec3..b9850323 100644 --- a/mygpo/users/tasks.py +++ b/mygpo/users/tasks.py @@ -1,4 +1,11 @@ +from itertools import chain +from operator import itemgetter +from collections import Counter + +from couchdbkit import ResourceConflict + from mygpo.cel import celery +from mygpo.db.couchdb.user import suggestions_for_user @celery.task(max_retries=5, default_retry_delay=60) @@ -9,3 +16,32 @@ def sync_user(user): if group.is_synced: device = group.devices[0] user.sync_group(device) + + +@celery.task(max_retries=5, default_retry_delay=60) +def update_suggestions(user, max_suggestions=15): + """ updates the suggestions of a user """ + + # get suggestions object + suggestion = suggestions_for_user(user) + + # calculate possible suggestions + subscribed_podcasts = list(set(user.get_subscribed_podcasts())) + subscribed_podcasts = filter(None, subscribed_podcasts) + related = chain.from_iterable([p.related_podcasts for p in subscribed_podcasts]) + + # filter out blacklisted podcasts + related = filter(lambda pid: not pid in suggestion.blacklist, related) + + # get most relevant + counter = Counter(related) + get_podcast_id = itemgetter(0) + suggested = map(get_podcast_id, counter.most_common(max_suggestions)) + suggestion.podcasts = suggested + + try: + # update suggestions object + suggestion.save() + + except ResourceConflict as ex: + raise update_suggestions.retry(exc=ex) diff --git a/mygpo/web/views/__init__.py b/mygpo/web/views/__init__.py index b0cb5a14..b13a3424 100644 --- a/mygpo/web/views/__init__.py +++ b/mygpo/web/views/__init__.py @@ -40,6 +40,7 @@ from mygpo.core.podcasts import PodcastSet from mygpo.directory.toplist import PodcastToplist from mygpo.users.models import Suggestions, History, HistoryEntry, \ DeviceDoesNotExist +from mygpo.users.tasks import update_suggestions from mygpo.web.utils import process_lang_params from mygpo.utils import parse_range from mygpo.web.views.podcast import slug_id_decorator @@ -181,20 +182,17 @@ def history(request, count=15, uid=None): @login_required @slug_id_decorator def blacklist(request, blacklisted_podcast): - suggestion = suggestions_for_user(request.user) + user = request.user + + suggestion = suggestions_for_user(user) @repeat_on_conflict(['suggestion']) def _update(suggestion, podcast_id): suggestion.blacklist.append(podcast_id) suggestion.save() - @repeat_on_conflict(['user']) - def _not_uptodate(user): - user.suggestions_up_to_date = False - user.save() - _update(suggestion=suggestion, podcast_id=blacklisted_podcast.get_id()) - _not_uptodate(user=request.user) + update_suggestions.delay(user) return HttpResponseRedirect(reverse('suggestions')) -- 2.11.4.GIT