Holy mother of commits. Finishing up Account system.

master
Nick Sergeant 2013-02-12 01:01:55 -05:00
parent f2b2b46aa4
commit 79cf90b8d7
11 changed files with 260 additions and 135 deletions

View File

@ -35,6 +35,23 @@
</li>
</ul>
</aside>
<section class="content" ng-view></section>
<section class="content" ng-class="{'with-message': message}">
<form ng-cloak class="ng-cloak" action="" method="get" accept-charset="utf-8" ng-submit="saveFields(route.current.scope.fields)">
<ng-view></ng-view>
<p class="alert ng-cloak"
ng-cloak
ng-class="{'alert-success': success, 'alert-error': !success}"
ng-show="message">
[[ message ]]
</p>
<div class="form-actions" ng-show="route.current.scope.fields">
<button type="submit"
ng-disabled="saveButtonText != 'Save'"
class="btn btn-success">
[[ saveButtonText ]]
</button>
</div>
</form>
</section>
</section>
{% endblock %}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1539,12 +1539,12 @@ body.account {
border-bottom: 1px solid #DDDDDD;
border-left: 4px solid #DDDDDD;
float: right;
min-height: 308px;
padding: 30px 0 0 0;
min-height: 272px;
width: 555px;
@include multi-border-radius(0, 0, 0, 10px);
div.def {
background: #FAFAFA;
border: 1px solid #DDDDDD;
margin: 15px;
padding: 30px 10px 10px;
@ -1565,7 +1565,8 @@ body.account {
}
}
p.alert {
margin: 0 15px;
line-height: 20px;
margin: 15px;
}
form {
span.help-block {
@ -1583,6 +1584,11 @@ body.account {
margin-top: 10px;
}
}
div.form-actions {
margin-top: 15px;
padding-bottom: 0;
@include vertical-gradient(#F5F5F5, white);
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -2,9 +2,9 @@
if (typeof angular !== 'undefined') {
// Globals.
var root = this;
var $ = root.jQuery;
var controllers = {};
// App definition.
var app = angular.module('Account', [], function($routeProvider, $locationProvider) {
@ -61,30 +61,147 @@
}
});
return promise;
},
saveAccount: function(user, fields) {
var promise = $http({
method: 'PUT',
url: '/api/private/profile/' + window.user_profile_id + '/',
headers: {
'Authorization': 'ApiKey ' + window.user + ':' + window.api_key
},
data: function() {
var userData = {};
for (var i = 0; i < fields.length; i++) {
userData[fields[i]] = user[fields[i]];
}
return userData;
}()
});
return promise;
}
};
});
// Controllers.
var controllers = {};
controllers.BillingController = function($scope) {
$scope.section = 'Billing';
};
controllers.BloggingController = function($scope) {
$scope.fields = [
'blog_title',
'blog_theme',
'blog_domain',
'gittip_username',
'disqus_shortname',
'google_analytics_tracking_id',
'gauges_site_id',
'google_ad_client',
'google_ad_slot',
'google_ad_width',
'google_ad_height'
];
$scope.section = 'Blogging';
$scope.blogThemeOptions = [
{ id: 'D', label: 'Default' },
{ id: 'A', label: 'Pro Adams' }
];
};
controllers.EditorController = function($scope) {
$scope.fields = ['default_editor', 'editor_theme'];
$scope.section = 'Editor';
$scope.editorOptions = [
{ id: 'C', label: 'CodeMirror' },
{ id: 'T', label: 'Textarea' }
];
$scope.editorThemeOptions = [
{ id: 'default', label: 'Default' },
{ id: 'ambiance', label: 'Ambiance' },
{ id: 'blackboard', label: 'Blackboard' },
{ id: 'cobalt', label: 'Cobalt' },
{ id: 'eclipse', label: 'Eclipse' },
{ id: 'elegant', label: 'Elegant' },
{ id: 'erlang-dark', label: 'Erlang Dark' },
{ id: 'lesser-dark', label: 'Lesser Dark' },
{ id: 'monokai', label: 'Monokai' },
{ id: 'neat', label: 'Neat' },
{ id: 'night', label: 'Night' },
{ id: 'rubyblue', label: 'Ruby Blue' },
{ id: 'solarized dark', label: 'Solarized Dark' },
{ id: 'solarized light', label: 'Solarized Light' },
{ id: 'twilight', label: 'Twilight' },
{ id: 'vibrant-ink', label: 'Vibrant Ink' },
{ id: 'xq-dark', label: 'XQ Dark' }
];
};
controllers.MainController = function($scope, $route, AccountStorage) {
$scope.errors = [];
$scope.saveButtonText = 'Save';
$scope.route = $route;
AccountStorage.getAccount().then(function(response) {
$scope.user = response.data;
});
};
controllers.BillingController = function($scope) {
$scope.section = 'Billing';
};
controllers.BloggingController = function($scope) {
$scope.section = 'Blogging';
};
controllers.EditorController = function($scope) {
$scope.section = 'Editor';
$scope.saveFields = function(fields) {
$scope.saveButtonText = 'Saving…';
AccountStorage.saveAccount($scope.user, fields).then(function onSuccess(response) {
// Save the new user object.
$scope.user = response.data;
// Signal that we have a successful response.
$scope.success = true;
// Success message.
$scope.message = $scope.route.current.scope.section + ' settings saved.';
// Reset the save button text.
$scope.saveButtonText = 'Save';
// Clear out any marked errors.
$scope.errors = [];
// Remove the success message after a while.
setTimeout(function() {
$scope.success = null;
$scope.message = '';
// We have to apply since we're outside of the scope context.
$scope.$apply();
}, 3000);
}, function onError(response) {
// Signal that we have an error.
$scope.success = false;
// Reset the save button text.
$scope.saveButtonText = 'Save';
// If we have a response, then it's probably a validation error.
if (response) {
// Set the errors on the scope.
$scope.errors = response.data.profile;
$scope.message = 'Only spaces, letters, numbers, underscores, dashes, periods, forward slashes, and "at sign" are valid.';
} else {
$scope.message = 'There was an error saving your settings.';
}
});
};
};
controllers.ProfileController = function($scope) {
$scope.section = 'Profile';

View File

@ -1,2 +1,3 @@
(function(){if(typeof angular!=='undefined'){var root=this;var $=root.jQuery;var app=angular.module('Account',[],function($routeProvider,$locationProvider){$locationProvider.html5Mode(true);$routeProvider.when('/account/',{templateUrl:'/media/js/src/modules/partials/profile.html',controller:controllers.ProfileController});$routeProvider.when('/account/billing/',{templateUrl:'/media/js/src/modules/partials/billing.html',controller:controllers.BillingController});$routeProvider.when('/account/blogging/',{templateUrl:'/media/js/src/modules/partials/blogging.html',controller:controllers.BloggingController});$routeProvider.when('/account/editor/',{templateUrl:'/media/js/src/modules/partials/editor.html',controller:controllers.EditorController});$routeProvider.otherwise({'redirectTo':function(routeParams,locationPath){window.location=locationPath;}});});app.config(function($interpolateProvider){$interpolateProvider.startSymbol('[[');$interpolateProvider.endSymbol(']]');});app.factory('AccountStorage',function($http){return{getAccount:function(){var promise=$http({method:'GET',url:'/api/private/profile/'+window.user_profile_id+'/',headers:{'Authorization':'ApiKey '+window.user+':'+window.api_key}});return promise;}};});var controllers={};controllers.MainController=function($scope,$route,AccountStorage){$scope.route=$route;AccountStorage.getAccount().then(function(response){$scope.user=response.data;});};controllers.BillingController=function($scope){$scope.section='Billing';};controllers.BloggingController=function($scope){$scope.section='Blogging';};controllers.EditorController=function($scope){$scope.section='Editor';};controllers.ProfileController=function($scope){$scope.section='Profile';};app.controller(controllers);}}).call(this);
(function(){if(typeof angular!=='undefined'){var root=this;var $=root.jQuery;var controllers={};var app=angular.module('Account',[],function($routeProvider,$locationProvider){$locationProvider.html5Mode(true);$routeProvider.when('/account/',{templateUrl:'/media/js/src/modules/partials/profile.html',controller:controllers.ProfileController});$routeProvider.when('/account/billing/',{templateUrl:'/media/js/src/modules/partials/billing.html',controller:controllers.BillingController});$routeProvider.when('/account/blogging/',{templateUrl:'/media/js/src/modules/partials/blogging.html',controller:controllers.BloggingController});$routeProvider.when('/account/editor/',{templateUrl:'/media/js/src/modules/partials/editor.html',controller:controllers.EditorController});$routeProvider.otherwise({'redirectTo':function(routeParams,locationPath){window.location=locationPath;}});});app.config(function($interpolateProvider){$interpolateProvider.startSymbol('[[');$interpolateProvider.endSymbol(']]');});app.factory('AccountStorage',function($http){return{getAccount:function(){var promise=$http({method:'GET',url:'/api/private/profile/'+window.user_profile_id+'/',headers:{'Authorization':'ApiKey '+window.user+':'+window.api_key}});return promise;},saveAccount:function(user,fields){var promise=$http({method:'PUT',url:'/api/private/profile/'+window.user_profile_id+'/',headers:{'Authorization':'ApiKey '+window.user+':'+window.api_key},data:function(){var userData={};for(var i=0;i<fields.length;i++){userData[fields[i]]=user[fields[i]];}
return userData;}()});return promise;}};});controllers.BillingController=function($scope){$scope.section='Billing';};controllers.BloggingController=function($scope){$scope.fields=['blog_title','blog_theme','blog_domain','gittip_username','disqus_shortname','google_analytics_tracking_id','gauges_site_id','google_ad_client','google_ad_slot','google_ad_width','google_ad_height'];$scope.section='Blogging';$scope.blogThemeOptions=[{id:'D',label:'Default'},{id:'A',label:'Pro Adams'}];};controllers.EditorController=function($scope){$scope.fields=['default_editor','editor_theme'];$scope.section='Editor';$scope.editorOptions=[{id:'C',label:'CodeMirror'},{id:'T',label:'Textarea'}];$scope.editorThemeOptions=[{id:'default',label:'Default'},{id:'ambiance',label:'Ambiance'},{id:'blackboard',label:'Blackboard'},{id:'cobalt',label:'Cobalt'},{id:'eclipse',label:'Eclipse'},{id:'elegant',label:'Elegant'},{id:'erlang-dark',label:'Erlang Dark'},{id:'lesser-dark',label:'Lesser Dark'},{id:'monokai',label:'Monokai'},{id:'neat',label:'Neat'},{id:'night',label:'Night'},{id:'rubyblue',label:'Ruby Blue'},{id:'solarized dark',label:'Solarized Dark'},{id:'solarized light',label:'Solarized Light'},{id:'twilight',label:'Twilight'},{id:'vibrant-ink',label:'Vibrant Ink'},{id:'xq-dark',label:'XQ Dark'}];};controllers.MainController=function($scope,$route,AccountStorage){$scope.errors=[];$scope.saveButtonText='Save';$scope.route=$route;AccountStorage.getAccount().then(function(response){$scope.user=response.data;});$scope.saveFields=function(fields){$scope.saveButtonText='Saving…';AccountStorage.saveAccount($scope.user,fields).then(function onSuccess(response){$scope.user=response.data;$scope.success=true;$scope.message=$scope.route.current.scope.section+' settings saved.';$scope.saveButtonText='Save';$scope.errors=[];setTimeout(function(){$scope.success=null;$scope.message='';$scope.$apply();},3000);},function onError(response){$scope.success=false;$scope.saveButtonText='Save';if(response){$scope.errors=response.data.profile;$scope.message='Only spaces, letters, numbers, underscores, dashes, periods, forward slashes, and "at sign" are valid.';}else{$scope.message='There was an error saving your settings.';}});};};controllers.ProfileController=function($scope){$scope.section='Profile';};app.controller(controllers);}}).call(this);

View File

@ -1,85 +1,83 @@
<form action="" method="get" accept-charset="utf-8">
<div class="def" data-title="Site settings">
<div class="control-group">
<label class="control-label" for="id_blog_title">Blog title:</label>
<div class="controls">
<input id="id_blog_title" type="text" name="blog_title" value="[[ user.blog_title ]]" maxlength="250">
</div>
</div>
<div class="control-group ">
<label class="control-label" for="id_blog_theme">Blog theme:</label>
<div class="controls">
<select name="blog_theme" id="id_blog_theme">
<option value="D" ng-selected="user.blog_theme == 'D'">Default</option>
<option value="A" ng-selected="user.blog_theme == 'A'">Pro Adams</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_blog_domain">Blog domain:</label>
<div class="controls">
<input id="id_blog_domain" type="text" name="blog_domain" maxlength="250" value="[[ user.blog_domain ]]">
<span class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME / A-record to point to 96.126.110.160</span>
</div>
<div class="def" data-title="Site settings">
<div class="control-group" ng-class="{error: errors.blog_title}">
<label class="control-label" for="id_blog_title">Blog title:</label>
<div class="controls">
<input id="id_blog_title" type="text" ng-model="user.blog_title" maxlength="250">
</div>
</div>
<div class="def" data-title="Services">
<div class="control-group">
<label class="control-label" for="id_gittip_username">Gittip username:</label>
<div class="controls">
<input id="id_gittip_username" type="text" name="gittip_username" value="[[ user.gittip_username ]]" maxlength="250">
<span class="help-block">Your <a href="https://www.gittip.com/">Gittip</a> username, if you have one.</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_disqus_shortname">Disqus shortname:</label>
<div class="controls">
<input id="id_disqus_shortname" type="text" name="disqus_shortname" maxlength="250" value="[[ user.disqus_shortname ]]">
<span class="help-block">If you have your own <a href="http://disqus.com/">Disqus</a> account that you'd like to use for your blog comments, enter your shortname here.</span>
</div>
<div class="control-group" ng-class="{error: errors.blog_theme}">
<label class="control-label" for="id_blog_theme">Blog theme:</label>
<div class="controls">
<select ng-model="user.blog_theme"
ng-options="theme.id as theme.label for theme in blogThemeOptions"
id="id_blog_theme">
</select>
</div>
</div>
<div class="def" data-title="Analytics">
<div class="control-group">
<label class="control-label" for="id_google_analytics_tracking_id">Google Analytics tracking ID:</label>
<div class="controls">
<input id="id_google_analytics_tracking_id" type="text" name="google_analytics_tracking_id" maxlength="250" value="[[ user.google_analytics_tracking_id ]]">
<span class="help-block">If you'd like to track visits to your blog site with <a href="http://analytics.google.com">Google Analytics</a>, enter your tracking ID here.</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_gauges_site_id">Gauges site ID:</label>
<div class="controls">
<input id="id_gauges_site_id" type="text" name="gauges_site_id" value="[[ user.gauges_site_id ]]" maxlength="250">
<span class="help-block">If you'd like to track visits to your blog site with <a href="http://get.gaug.es/">Gauges</a>, enter your site ID here.</span>
</div>
<div class="control-group" ng-class="{error: errors.blog_domain}">
<label class="control-label" for="id_blog_domain">Blog domain:</label>
<div class="controls">
<input id="id_blog_domain" type="text" ng-model="user.blog_domain" maxlength="250">
<span class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME / A-record to point to 96.126.110.160</span>
</div>
</div>
<div class="def" data-title="Google AdSense">
<div class="control-group">
<label class="control-label" for="id_google_ad_client">Google Ads: "google_ad_client"</label>
<div class="controls">
<input id="id_google_ad_client" type="text" name="google_ad_client" maxlength="250" value="[[ user.google_ad_client ]]">
<span class="help-block">If you'd like to run Google Ads in your blog's sidebar, enter your client ID here, as well as the following three fields.</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_google_ad_slot">Google Ads: "google_ad_slot"</label>
<div class="controls">
<input id="id_google_ad_slot" type="text" name="google_ad_slot" maxlength="250" value="[[ user.google_ad_slot ]]">
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_google_ad_width">Google Ads: "google_ad_width"</label>
<div class="controls">
<input id="id_google_ad_width" type="text" name="google_ad_width" maxlength="250" value="[[ user.google_ad_width ]]">
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_google_ad_height">Google Ads: "google_ad_height"</label>
<div class="controls">
<input id="id_google_ad_height" type="text" name="google_ad_height" maxlength="250" value="[[ user.google_ad_height ]]">
</div>
</div>
<div class="def" data-title="Services">
<div class="control-group" ng-class="{error: errors.gittip_username}">
<label class="control-label" for="id_gittip_username">Gittip username:</label>
<div class="controls">
<input id="id_gittip_username" type="text" ng-model="user.gittip_username" maxlength="250">
<span class="help-block">Your <a href="https://www.gittip.com/">Gittip</a> username, if you have one.</span>
</div>
</div>
</form>
<div class="control-group" ng-class="{error: errors.disqus_shortname}">
<label class="control-label" for="id_disqus_shortname">Disqus shortname:</label>
<div class="controls">
<input id="id_disqus_shortname" type="text" ng-model="user.disqus_shortname" maxlength="250">
<span class="help-block">If you have your own <a href="http://disqus.com/">Disqus</a> account that you'd like to use for your blog comments, enter your shortname here.</span>
</div>
</div>
</div>
<div class="def" data-title="Analytics">
<div class="control-group" ng-class="{error: errors.google_analytics_tracking_id}">
<label class="control-label" for="id_google_analytics_tracking_id">Google Analytics tracking ID:</label>
<div class="controls">
<input id="id_google_analytics_tracking_id" type="text" ng-model="user.google_analytics_tracking_id" maxlength="250">
<span class="help-block">If you'd like to track visits to your blog site with <a href="http://analytics.google.com">Google Analytics</a>, enter your tracking ID here.</span>
</div>
</div>
<div class="control-group" ng-class="{error: errors.gauges_site_id}">
<label class="control-label" for="id_gauges_site_id">Gauges site ID:</label>
<div class="controls">
<input id="id_gauges_site_id" type="text" ng-model="user.gauges_site_id" maxlength="250">
<span class="help-block">If you'd like to track visits to your blog site with <a href="http://get.gaug.es/">Gauges</a>, enter your site ID here.</span>
</div>
</div>
</div>
<div class="def" data-title="Google AdSense">
<div class="control-group" ng-class="{error: errors.google_ad_client}">
<label class="control-label" for="id_google_ad_client">Google Ads: "google_ad_client"</label>
<div class="controls">
<input id="id_google_ad_client" type="text" ng-model="user.google_ad_client" maxlength="250">
<span class="help-block">If you'd like to run Google Ads in your blog's sidebar, enter your client ID here, as well as the following three fields.</span>
</div>
</div>
<div class="control-group" ng-class="{error: errors.google_ad_slot}">
<label class="control-label" for="id_google_ad_slot">Google Ads: "google_ad_slot"</label>
<div class="controls">
<input id="id_google_ad_slot" type="text" ng-model="user.google_ad_slot" maxlength="250">
</div>
</div>
<div class="control-group" ng-class="{error: errors.google_ad_width}">
<label class="control-label" for="id_google_ad_width">Google Ads: "google_ad_width"</label>
<div class="controls">
<input id="id_google_ad_width" type="text" ng-model="user.google_ad_width" maxlength="250">
</div>
</div>
<div class="control-group" ng-class="{error: errors.google_ad_height}">
<label class="control-label" for="id_google_ad_height">Google Ads: "google_ad_height"</label>
<div class="controls">
<input id="id_google_ad_height" type="text" ng-model="user.google_ad_height" maxlength="250">
</div>
</div>
</div>

View File

@ -1,37 +1,20 @@
<form action="" method="get" accept-charset="utf-8">
<div class="def" data-title="Editor">
<div class="control-group">
<label class="control-label" for="id_default_editor">Default editor:</label>
<div class="controls">
<select name="default_editor" id="id_default_editor">
<option value="C" ng-selected="user.default_editor == 'C'">CodeMirror</option>
<option value="T" ng-selected="user.default_editor == 'T'">Textarea</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="id_editor_theme">Default editor theme:</label>
<div class="controls">
<select name="editor_theme" id="id_editor_theme">
<option value="default" ng-selected="user.editor_theme == 'default'">Default</option>
<option value="ambiance" ng-selected="user.editor_theme == 'ambiance'">Ambiance</option>
<option value="blackboard" ng-selected="user.editor_theme == 'blackboard'">Blackboard</option>
<option value="cobalt" ng-selected="user.editor_theme == 'cobalt'">Cobalt</option>
<option value="eclipse" ng-selected="user.editor_theme == 'eclipse'">Eclipse</option>
<option value="elegant" ng-selected="user.editor_theme == 'elegant'">Elegant</option>
<option value="erlang-dark" ng-selected="user.editor_theme == 'erlang-dark'">Erlang Dark</option>
<option value="lesser-dark" ng-selected="user.editor_theme == 'lesser-dark'">Lesser Dark</option>
<option value="monokai" ng-selected="user.editor_theme == 'monokai'">Monokai</option>
<option value="neat" ng-selected="user.editor_theme == 'neat'">Neat</option>
<option value="night" ng-selected="user.editor_theme == 'night'">Night</option>
<option value="rubyblue" ng-selected="user.editor_theme == 'rubyblue'">Ruby Blue</option>
<option value="solarized dark" ng-selected="user.editor_theme == 'solarized dark'">Solarized Dark</option>
<option value="solarized light" ng-selected="user.editor_theme == 'solarized light'">Solarized Light</option>
<option value="twilight" ng-selected="user.editor_theme == 'twilight'">Twilight</option>
<option value="vibrant-ink" ng-selected="user.editor_theme == 'vibrant-ink'">Vibrant Ink</option>
<option value="xq-dark" ng-selected="user.editor_theme == 'xq-dark'">XQ Dark</option>
</select>
</div>
<div class="def" data-title="Editor">
<div class="control-group" ng-class="{error: errors.default_editor}">
<label class="control-label" for="id_default_editor">Default editor:</label>
<div class="controls">
<select ng-model="user.default_editor"
ng-options="editor.id as editor.label for editor in editorOptions"
id="id_default_editor">
</select>
</div>
</div>
</form>
<div class="control-group" ng-class="{error: errors.editor_theme}">
<label class="control-label" for="id_editor_theme">Default editor theme:</label>
<div class="controls">
<select ng-model="user.editor_theme"
ng-options="theme.id as theme.label for theme in editorThemeOptions"
id="id_editor_theme">
</select>
</div>
</div>
</div>

View File

@ -4,9 +4,12 @@
<div class="def" data-title="User ID">
[[ user.user_id ]]
</div>
<div class="def" data-title="Profile ID">
[[ user.id ]]
</div>
<div class="def" data-title="Email">
[[ user.email ]]
</div>
<div class="def" data-title="API Key">
fdsaf809ds7a89gd7sa89g7ds09a7g0d9s8a
[[ user.api_key ]]
</div>

View File

@ -38,8 +38,8 @@ class UserProfileValidation(Validation):
errors = {}
for field in bundle.data:
if not re.match('^[ A-Za-z0-9\._-]*$', bundle.data[field]):
errors[field] = 'Only spaces, letters, numbers, underscores, dashes, and periods are valid.'
if not re.match('^[ A-Za-z0-9\/\@\._-]*$', bundle.data[field]):
errors[field] = 'Only spaces, letters, numbers, underscores, dashes, periods, forward slashes, and "at sign" are valid.'
return errors
@ -141,7 +141,6 @@ class PrivateUserProfileResource(ModelResource):
authorization = Authorization()
always_return_data = True
max_limit = 200
cache = SimpleCache()
def apply_authorization_limits(self, request, object_list):
return object_list.filter(user=request.user)