diff --git a/apps/profile/forms.py b/apps/profile/forms.py index 30d208269..c26021805 100644 --- a/apps/profile/forms.py +++ b/apps/profile/forms.py @@ -5,7 +5,7 @@ from vendor.zebra.forms import StripePaymentForm from django.utils.safestring import mark_safe from django.contrib.auth import authenticate from django.contrib.auth.models import User -from apps.profile.models import change_password, blank_authenticate, MGiftCode +from apps.profile.models import change_password, blank_authenticate, MGiftCode, MCustomStyling from apps.social.models import MSocialProfile PLANS = [ @@ -113,7 +113,12 @@ class AccountSettingsForm(forms.Form): old_password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'NB-input'}), label='password', required=False) - # error_messages={'required': 'Please enter a password.'}) + custom_js = forms.CharField(widget=forms.TextInput(attrs={'class': 'NB-input'}), + label='custom_js', + required=False) + custom_css = forms.CharField(widget=forms.TextInput(attrs={'class': 'NB-input'}), + label='custom_css', + required=False) def __init__(self, user, *args, **kwargs): self.user = user @@ -161,6 +166,8 @@ class AccountSettingsForm(forms.Form): new_password = self.cleaned_data.get('new_password', None) old_password = self.cleaned_data.get('old_password', None) email = self.cleaned_data.get('email', None) + custom_css = self.cleaned_data.get('custom_css', None) + custom_js = self.cleaned_data.get('custom_js', None) if username and self.user.username != username: change_password(self.user, self.user.username, username) @@ -178,6 +185,8 @@ class AccountSettingsForm(forms.Form): if old_password or new_password: change_password(self.user, old_password, new_password) + MCustomStyling.save_user(self.user.pk, custom_css, custom_js) + class RedeemCodeForm(forms.Form): gift_code = forms.CharField(widget=forms.TextInput(), label="Gift code", diff --git a/apps/profile/models.py b/apps/profile/models.py index 5ab2cf453..bfd7bca96 100644 --- a/apps/profile/models.py +++ b/apps/profile/models.py @@ -7,7 +7,6 @@ import re import redis import uuid import mongoengine as mongo -from pprint import pprint from django.db import models from django.db import IntegrityError from django.db.utils import DatabaseError @@ -16,7 +15,6 @@ from django.db.models import Sum, Avg, Count from django.conf import settings from django.contrib.auth import authenticate from django.contrib.auth.models import User -from django.core.mail import mail_admins from django.core.mail import EmailMultiAlternatives from django.core.urlresolvers import reverse from django.template.loader import render_to_string @@ -515,7 +513,6 @@ class Profile(models.Model): @classmethod def count_all_feed_subscribers_for_user(self, user): - SUBSCRIBER_EXPIRE = datetime.datetime.now() - datetime.timedelta(days=settings.SUBSCRIBER_EXPIRE) r = redis.Redis(connection_pool=settings.REDIS_FEED_SUB_POOL) if not isinstance(user, User): user = User.objects.get(pk=user) @@ -604,12 +601,12 @@ class Profile(models.Model): params = dict(receiver_user_id=self.user.pk, email_type='first_share') try: - sent_email = MSentEmail.objects.get(**params) + MSentEmail.objects.get(**params) if not force: # Return if email already sent return except MSentEmail.DoesNotExist: - sent_email = MSentEmail.objects.create(**params) + MSentEmail.objects.create(**params) social_profile = MSocialProfile.objects.get(user_id=self.user.pk) params = { @@ -630,14 +627,14 @@ class Profile(models.Model): logging.user(self.user, "~BB~FM~SBSending first share to blurblog email to: %s" % self.user.email) def send_new_premium_email(self, force=False): - subs = UserSubscription.objects.filter(user=self.user) - message = """Woohoo! - -User: %(user)s -Feeds: %(feeds)s - -Sincerely, -NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} + # subs = UserSubscription.objects.filter(user=self.user) +# message = """Woohoo! +# +# User: %(user)s +# Feeds: %(feeds)s +# +# Sincerely, +# NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} # mail_admins('New premium account', message, fail_silently=True) if not self.user.email or not self.send_emails: @@ -645,12 +642,12 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} params = dict(receiver_user_id=self.user.pk, email_type='new_premium') try: - sent_email = MSentEmail.objects.get(**params) + MSentEmail.objects.get(**params) if not force: # Return if email already sent return except MSentEmail.DoesNotExist: - sent_email = MSentEmail.objects.create(**params) + MSentEmail.objects.create(**params) user = self.user text = render_to_string('mail/email_new_premium.txt', locals()) @@ -692,12 +689,12 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} params = dict(receiver_user_id=self.user.pk, email_type='new_user_queue') try: - sent_email = MSentEmail.objects.get(**params) + MSentEmail.objects.get(**params) if not force: # Return if email already sent return except MSentEmail.DoesNotExist: - sent_email = MSentEmail.objects.create(**params) + MSentEmail.objects.create(**params) user = self.user text = render_to_string('mail/email_new_user_queue.txt', locals()) @@ -769,13 +766,13 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} params = dict(receiver_user_id=self.user.pk, email_type='launch_social') try: - sent_email = MSentEmail.objects.get(**params) + MSentEmail.objects.get(**params) if not force: # Return if email already sent logging.user(self.user, "~FM~SB~FRNot~FM sending launch social email for user, sent already: %s" % self.user.email) return except MSentEmail.DoesNotExist: - sent_email = MSentEmail.objects.create(**params) + MSentEmail.objects.create(**params) delta = datetime.datetime.now() - self.last_seen_on months_ago = delta.days / 30 @@ -799,13 +796,13 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} params = dict(receiver_user_id=self.user.pk, email_type='launch_turntouch') try: - sent_email = MSentEmail.objects.get(**params) + MSentEmail.objects.get(**params) if not force: # Return if email already sent logging.user(self.user, "~FM~SB~FRNot~FM sending launch social email for user, sent already: %s" % self.user.email) return except MSentEmail.DoesNotExist: - sent_email = MSentEmail.objects.create(**params) + MSentEmail.objects.create(**params) delta = datetime.datetime.now() - self.last_seen_on months_ago = delta.days / 30 @@ -829,13 +826,13 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} params = dict(receiver_user_id=self.user.pk, email_type='launch_turntouch_end') try: - sent_email = MSentEmail.objects.get(**params) + MSentEmail.objects.get(**params) if not force: # Return if email already sent logging.user(self.user, "~FM~SB~FRNot~FM sending launch TT end email for user, sent already: %s" % self.user.email) return except MSentEmail.DoesNotExist: - sent_email = MSentEmail.objects.create(**params) + MSentEmail.objects.create(**params) delta = datetime.datetime.now() - self.last_seen_on months_ago = delta.days / 30 @@ -1278,6 +1275,52 @@ class MRedeemedCode(mongo.Document): logging.user(user, "~FG~BBRedeeming gift code: %s~FW" % gift_code) +class MCustomStyling(mongo.Document): + user_id = mongo.IntField(unique=True) + custom_css = mongo.StringField() + custom_js = mongo.StringField() + updated_date = mongo.DateTimeField(default=datetime.datetime.now) + + meta = { + 'collection': 'custom_styling', + 'allow_inheritance': False, + 'indexes': ['user_id'], + } + + def __unicode__(self): + return "%s custom style %s/%s %s" % (self.user_id, len(self.custom_css) if self.custom_css else "-", + len(self.custom_js) if self.custom_js else "-", self.updated_date) + + def canonical(self): + return { + 'css': self.custom_css, + 'js': self.custom_js, + } + + @classmethod + def get_user(cls, user_id): + try: + styling = cls.objects.get(user_id=user_id) + except cls.DoesNotExist: + return None + + return styling + + @classmethod + def save_user(cls, user_id, css, js): + styling = cls.get_user(user_id) + if not css and not js: + if styling: + styling.delete() + return + + if not styling: + styling = cls.objects.create(user_id=user_id) + + styling.custom_css = css + styling.custom_js = js + styling.save() + class RNewUserQueue: KEY = "new_user_queue" diff --git a/apps/reader/views.py b/apps/reader/views.py index f081e8793..032b78b00 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -32,7 +32,7 @@ from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifie from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds from apps.analyzer.models import apply_classifier_authors, apply_classifier_tags from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed -from apps.profile.models import Profile +from apps.profile.models import Profile, MCustomStyling from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory, Feature from apps.reader.forms import SignupForm, LoginForm, FeatureForm from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts, MSavedSearch @@ -102,6 +102,7 @@ def dashboard(request, **kwargs): ).select_related('feed')[:2] statistics = MStatistics.all() social_profile = MSocialProfile.get_user(user.pk) + custom_styling = MCustomStyling.get_user(user.pk) start_import_from_google_reader = request.session.get('import_from_google_reader', False) if start_import_from_google_reader: @@ -117,6 +118,7 @@ def dashboard(request, **kwargs): return { 'user_profile' : user.profile, 'feed_count' : feed_count, + 'custom_styling' : custom_styling, 'account_images' : range(1, 4), 'recommended_feeds' : recommended_feeds, 'unmoderated_feeds' : unmoderated_feeds, diff --git a/media/css/reader.css b/media/css/reader.css index 559b78985..646069e63 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -11249,12 +11249,14 @@ form.opml_import_form input { border: 4px solid #8F1F00; margin: 2px 3px; } -.NB-modal-profile-editor .NB-profile-blurblog-css { +.NB-modal-profile-editor .NB-profile-blurblog-css, +.NB-modal-account .NB-account-custom-css, +.NB-modal-account .NB-account-custom-javascript { width: 100%; font-family: Courier, monospace; font-size: 13px; padding: 6px; - height: 54px; + height: 94px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; diff --git a/media/js/newsblur/reader/reader_account.js b/media/js/newsblur/reader/reader_account.js index 464ef493d..64e3844d1 100644 --- a/media/js/newsblur/reader/reader_account.js +++ b/media/js/newsblur/reader/reader_account.js @@ -43,7 +43,8 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, { $.make('div', { className: 'NB-modal-loading' }), $.make('div', { className: 'NB-modal-tab NB-active NB-modal-tab-account' }, 'Account'), $.make('div', { className: 'NB-modal-tab NB-modal-tab-premium' }, 'Payments'), - $.make('div', { className: 'NB-modal-tab NB-modal-tab-emails' }, 'Emails') + $.make('div', { className: 'NB-modal-tab NB-modal-tab-emails' }, 'Emails'), + $.make('div', { className: 'NB-modal-tab NB-modal-tab-custom' }, 'Custom CSS/JavaScript') ]), $.make('h2', { className: 'NB-modal-title' }, [ $.make('div', { className: 'NB-icon' }), @@ -207,6 +208,20 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, { ]) ]) ]), + $.make('div', { className: 'NB-tab NB-tab-custom' }, [ + $.make('fieldset', [ + $.make('legend', 'Custom CSS'), + $.make('div', { className: 'NB-modal-section NB-profile-editor-blurblog-custom-css'}, [ + $.make('textarea', { 'className': 'NB-account-custom-css', name: 'custom_css' }, _.string.trim($("#NB-custom-css").text())) + ]) + ]), + $.make('fieldset', [ + $.make('legend', 'Custom JavaScript'), + $.make('div', { className: 'NB-modal-section NB-profile-editor-blurblog-custom-js'}, [ + $.make('textarea', { 'className': 'NB-account-custom-javascript', name: 'custom_js' }, _.string.trim($("#NB-custom-js").text())) + ]) + ]) + ]), $.make('div', { className: 'NB-modal-submit' }, [ $.make('input', { type: 'submit', disabled: 'true', className: 'NB-modal-submit-button NB-modal-submit-green NB-disabled', value: 'Change what you like above...' }) ]) @@ -387,7 +402,7 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, { serialize_preferences: function() { var preferences = {}; - $('input[type=radio]:checked, select, input[type=text], input[type=password]', this.$modal).each(function() { + $('input[type=radio]:checked, select, textarea, input[type=text], input[type=password]', this.$modal).each(function() { var name = $(this).attr('name'); var preference = preferences[name] = $(this).val(); if (preference == 'true') preferences[name] = true; @@ -464,6 +479,8 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, { newtab = 'premium'; } else if ($t.hasClass('NB-modal-tab-emails')) { newtab = 'emails'; + } else if ($t.hasClass('NB-modal-tab-custom')) { + newtab = 'custom'; } self.switch_tab(newtab); }); @@ -502,6 +519,8 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, { handle_change: function() { $('input[type=radio],input[type=checkbox],select,input', this.$modal).bind('change', _.bind(this.enable_save, this)); $('input', this.$modal).bind('keydown', _.bind(this.enable_save, this)); + $('.NB-tab-custom', this.$modal).delegate('input[type=text],textarea', 'keydown', _.bind(this.enable_save, this)); + $('.NB-tab-custom', this.$modal).delegate('input,textarea', 'change', _.bind(this.enable_save, this)); }, enable_save: function() { diff --git a/templates/reader/dashboard.xhtml b/templates/reader/dashboard.xhtml index 4905e8816..b0f537898 100644 --- a/templates/reader/dashboard.xhtml +++ b/templates/reader/dashboard.xhtml @@ -22,6 +22,18 @@ }); {% endif %} + + {% if custom_styling and custom_styling.custom_js %} + + {% endif %} + + {% if custom_styling and custom_styling.custom_css %} + + {% endif %} {% endblock %} {% block bodyclass %}NB-body-main{% endblock %}