Skip to content
Browse files

merge

  • Loading branch information...
2 parents b90e893 + a01bdda commit 32012f9a28d443d579490ebb4ed82c0a4273ca71 @tomyedwab tomyedwab committed Apr 25, 2012
View
2 accuracy_model/accuracy_model.py
@@ -2,4 +2,4 @@
# This is here for backwards compatibility with old pickled data.
# Remove this when we have migrated all data from hard-coding in
# the old location, to coding in the new one.
-from exercises.accuracy_model import AccuracyModel
+from exercises.accuracy_model import AccuracyModel # @Nolint
View
2 api/v1.py
@@ -1192,7 +1192,7 @@ def get_user_profile():
current_user_data = user_models.UserData.current() or user_models.UserData.pre_phantom()
# TODO(marcia): This uses user_id, as opposed to email...
# which means that the GET and POST are not symmetric...
- user_data = request.request_user_data_by_user_id()
+ user_data = request.request_student_user_data()
return util_profile.UserProfile.from_user(user_data, current_user_data)
@route("/api/v1/user/profile", methods=["POST", "PUT"])
View
24 coaches.py
@@ -4,20 +4,13 @@
except ImportError:
import simplejson as json
-from app import App
-import app
import custom_exceptions
-import facebook_util
import util
import user_util
import request_handler
from user_models import UserData, StudentList
from coach_resources.coach_request_model import CoachRequest
-from badges import util_badges
-
-from profiles.util_profile import ExercisesOverTimeGraph, ExerciseProblemsGraph
-from profiles.util_profile import ClassProgressReportGraph, ClassEnergyPointsPerMinuteGraph, ClassTimeGraph
import profiles.util_profile as util_profile
@@ -139,7 +132,7 @@ class RequestStudent(request_handler.RequestHandler):
def post(self):
user_data = UserData.current()
- user_data_student = self.request_user_data("student_email")
+ user_data_student = self.request_student_user_data()
if user_data_student:
if not user_data_student.is_coached_by(user_data):
coach_request = CoachRequest.get_or_insert_for(user_data, user_data_student)
@@ -162,18 +155,13 @@ def get(self):
user_data = UserData.current()
accept_coach = self.request_bool("accept", default = False)
- user_data_coach = self.request_user_data("coach_email")
- user_data_student = self.request_user_data('student_email')
-
- if bool(user_data_coach) == bool(user_data_student):
- raise Exception('must provide coach_email xor student_email')
+ user_data_student = self.request_student_user_data()
- if user_data_coach:
- user_data_student = user_data
- elif user_data_student:
+ if user_data_student:
user_data_coach = user_data
- if user_data_coach and not user_data_student.is_coached_by(user_data_coach):
+ if (user_data_coach and
+ not user_data_student.is_coached_by(user_data_coach)):
coach_request = CoachRequest.get_for(user_data_coach, user_data_student)
if coach_request:
coach_request.delete()
@@ -219,7 +207,7 @@ class UnregisterStudent(UnregisterStudentCoach):
@user_util.login_required_and(phantom_user_allowed=False)
def get(self):
return self.do_request(
- self.request_user_data("student_email"),
+ self.request_student_user_data(),
UserData.current(),
"/students"
)
View
2 exercises/handlers.py
@@ -173,7 +173,7 @@ def problem_history_values(self, user_data, user_exercise):
problem_number = self.request_int('problem_number', default=(user_exercise.total_done + 1))
- user_data_student = self.request_student_user_data(legacy=True) or user_data
+ user_data_student = self.request_student_user_data() or user_data
if user_data_student.user_id != user_data.user_id and not user_data_student.is_visible_to(user_data):
user_data_student = user_data
View
3 javascript/profile-package/activity-graph.js
@@ -320,7 +320,8 @@ var ActivityGraph = {
}
var bucket = this.chart.options.xAxis.categories[ix];
if (bucket) {
- Profile.loadGraph("/profile/graph/activity?student_email=" +
+ // TODO(benkomalo): should this use username if possible?
+ Profile.loadGraph("/profile/graph/activity?email=" +
this.bucketData.studentEmail + "&dt_start=" + bucket);
}
},
View
8 javascript/profile-package/coaches.js
@@ -9,8 +9,7 @@ var Coaches = {
deferred;
if (isSelf && !isPhantom) {
- var email = Profile.profile.get("email"),
- template = Templates.get("profile.coaches");
+ var template = Templates.get("profile.coaches");
$("#tab-content-coaches").html(template(Profile.profile.toJSON()));
this.delegateEvents_();
@@ -19,7 +18,6 @@ var Coaches = {
type: "GET",
url: this.url,
data: {
- email: email,
casing: "camel"
},
dataType: "json",
@@ -150,7 +148,7 @@ Coaches.Coach = ProfileModel.extend({
delete json["id"];
return json;
}
-})
+});
Coaches.CoachCollection = Backbone.Collection.extend({
model: Coaches.Coach,
@@ -318,7 +316,7 @@ Coaches.CoachCollectionView = Backbone.View.extend({
this.coachViews_ = _.without(this.coachViews_, viewToRemove);
- if (this.rendered_){
+ if (this.rendered_) {
$(viewToRemove.el).fadeOut(function() {
viewToRemove.remove();
});
View
4 javascript/profile-package/handlebars-helpers.js
@@ -7,10 +7,6 @@ Handlebars.registerHelper("commafy", function(numPoints) {
return numPoints.toString().replace(/(\d)(?=(\d{3})+$)/g, "$1,");
});
-Handlebars.registerHelper("pluralize", function(num) {
- return (num === 1) ? "" : "s";
-});
-
/**
* Convert number of seconds to a time phrase for recent activity video entries.
* Stolen from templatefilters.py
View
4 javascript/profile-package/profile.handlebars
@@ -42,7 +42,7 @@
<section class="tab-content">
<h2 class="profile-sheet-title"></h2>
<div class="profile-notification">
- {{#if profileData.email}}
+ {{#if profileData.isFullyAccessible}}
<div class="empty-graph">
<h2><a href='/#browse'>Watch a video</a> or <a href='/exercisedashboard'>try a skill</a>!</h2>
<p>Once you do, real data will show up here.</p>
@@ -69,7 +69,7 @@
<div id="tab-content-user-profile" rel="profile">
<div class="user-info-container"></div>
<div style="clear: both; margin-bottom: 20px;"></div>
- {{#if profileData.email}}
+ {{#if profileData.isFullyAccessible}}
<div class="activity-column">
<div id="activity-loading-placeholder">
<h2>Loading activity...</h2>
View
49 javascript/profile-package/profile.js
@@ -188,6 +188,8 @@ var Profile = {
if (Profile.profile.get("email")) {
identityParam = "email=" +
encodeURIComponent(Profile.profile.get("email"));
+ } else if (Profile.profile.get("username")) {
+ identityParam = "username=" + Profile.profile.get("username");
}
var hrefLookup = {
@@ -231,8 +233,7 @@ var Profile = {
this.updateTitleBreadcrumbs([prettyGraphName]);
}
- if (Profile.profile.get("isSelf") ||
- Profile.profile.get("email")) {
+ if (Profile.profile.isFullyAccessible()) {
// If we have access to the profiled person's email, load real data.
Profile.loadGraph(href);
} else {
@@ -338,7 +339,7 @@ var Profile = {
parts.unshift(rootCrumb);
sheetTitle.text(parts.join(" » ")).show();
- if (!Profile.profile.get("isSelf") && !Profile.profile.get("email")) {
+ if (!Profile.profile.isFullyAccessible()) {
$(".profile-notification").show();
}
} else {
@@ -920,14 +921,11 @@ var Profile = {
return Profile.goalsDeferred_;
}
- // TODO: Abstract away profile + actor privileges
- // Also in profile.handlebars
- var email = Profile.profile.get("email");
- if (email) {
+ if (Profile.profile.isFullyAccessible()) {
Profile.goalsDeferred_ = $.ajax({
type: "GET",
url: "/api/v1/user/goals",
- data: {email: email},
+ data: Profile.getBaseRequestParams_(),
dataType: "json",
success: function(data) {
GoalProfileViewsCollection.render(data);
@@ -972,15 +970,11 @@ var Profile = {
return Profile.discussionDeferred_;
}
- var email = Profile.profile.get("email");
- if (email) {
+ if (Profile.profile.isFullyAccessible()) {
Profile.discussionDeferred_ = $.ajax({
type: "GET",
url: "/api/v1/user/questions",
- data: {
- email: email,
- casing: "camel"
- },
+ data: Profile.getBaseRequestParams_(),
dataType: "json",
success: function(questions) {
if (questions.length === 0) {
@@ -1070,16 +1064,11 @@ var Profile = {
}
$("#recent-activity-progress-bar").progressbar({value: 100});
- // TODO: Abstract away profile + actor privileges
- var email = Profile.profile.get("email");
- if (email) {
+ if (Profile.profile.isFullyAccessible()) {
Profile.activityDeferred_ = $.ajax({
type: "GET",
url: "/api/v1/user/activity",
- data: {
- email: email,
- casing: "camel"
- },
+ data: Profile.getBaseRequestParams_(),
dataType: "json",
success: function(data) {
$("#activity-loading-placeholder").fadeOut(
@@ -1096,5 +1085,23 @@ var Profile = {
Profile.activityDeferred_.resolve();
}
return Profile.activityDeferred_;
+ },
+
+ /**
+ * Return an object to be used in an outgoing XHR for user profile data
+ * (e.g. activity graph data).
+ * This includes an identifier for the current profile being viewed at,
+ * and other common properties.
+ */
+ getBaseRequestParams_: function() {
+ var params = {
+ "casing": "camel"
+ };
+ if (Profile.profile.get("email")) {
+ params["email"] = Profile.profile.get("email");
+ } else if (Profile.profile.get("username")) {
+ params["username"] = Profile.profile.get("username");
+ }
+ return params;
}
};
View
24 javascript/shared-package/profile-model.js
@@ -38,6 +38,29 @@ var ProfileModel = Backbone.Model.extend({
},
/**
+ * Whether or not the current actor on the app can access this user's full
+ * profile information.
+ */
+ isFullyAccessible: function() {
+ // Right now we're using "email" as a proxy for full information, since
+ // the server will not send down the e-mail address if you don't have
+ // the full information.
+ return this.get("isSelf") || !!this.get("email");
+ },
+
+ /**
+ * Returns either an e-mail or username that will uniquely identify the
+ * user.
+ *
+ * Note that not all users have a username, and not all users have
+ * an e-mail. However, if the actor has full access to this profile,
+ * at least one of these values will be non empty.
+ */
+ getIdentifier: function() {
+ return this.get("username") || this.get("email");
+ },
+
+ /**
* Whether or not the current actor can customize this profile.
* Note that users under 13 without parental consent can only
* edit some data; clients should also check isDataCollectible for full
@@ -56,6 +79,7 @@ var ProfileModel = Backbone.Model.extend({
// to do it here.
json["isFullyEditable"] =
this.isEditable() && this.get("isDataCollectible");
+ json["isFullyAccessible"] = this.isFullyAccessible();
return json;
},
View
10 javascript/studentlists-package/studentlists.js
@@ -105,7 +105,7 @@ var StudentLists = {
$.ajax({
type: 'GET',
url: '/unregisterstudent',
- data: {'student_email': student.email}
+ data: {'email': student.email}
});
// update data model
@@ -125,7 +125,7 @@ var StudentLists = {
$.ajax({
type: 'GET',
url: '/acceptcoach',
- data: {'accept': 0, 'student_email': email}
+ data: {'accept': 0, 'email': email}
});
// update data model
@@ -344,7 +344,7 @@ var AddStudentTextBox = {
$.ajax({
type: 'POST',
url: '/requeststudent',
- data: {'student_email': email},
+ data: {'email': email},
success: function(data, status, jqxhr) {
// data model
StudentLists.Data.coachRequests.push(email);
@@ -500,7 +500,7 @@ var EditListsMenu = {
$.ajax({
type: 'POST',
url: '/addstudenttolist',
- data: {'student_email': student.email, 'list_id': list_id}
+ data: {'email': student.email, 'list_id': list_id}
});
StudentLists.Data.addStudentToList(student, list_id);
@@ -515,7 +515,7 @@ var EditListsMenu = {
$.ajax({
type: 'POST',
url: '/removestudentfromlist',
- data: {'student_email': student.email, 'list_id': list_id}
+ data: {'email': student.email, 'list_id': list_id}
});
StudentLists.Data.removeStudentFromList(student, list_id);
View
14 javascript/video-package/modalvideo.js
@@ -62,14 +62,14 @@ var ModalVideo = {
});
},
- init: function(video, points) {
+ init: function(video) {
var context = {
video: video,
downloadUrl: video.downloadUrls && video.downloadUrls.mp4 || null,
height: 480,
width: 800,
youtubeId: video.youtubeId,
- points: points,
+ points: 0, // will be updated at end of .show()'s ajax request
possible_points: 750, // VIDEO_POINTS_BASE in consts.py
logged_in: !!USERNAME, // phantom users have empty string usernames
video_url: window.Khan && Khan.relatedVideos && Khan.relatedVideos.makeHref(video) || video.relative_url
@@ -89,20 +89,22 @@ var ModalVideo = {
this.modal.removeClass("fade");
}, this));
- VideoStats.updatePointsSaved(points);
Video.init({});
ModalVideo.linkifyTooltip();
return this.modal;
},
show: function(video) {
+ this.modal = this.init(video);
+ this.modal.modal("show");
+ VideoStats.startLoggingProgress(null, video.youtubeId);
+
var apiUrl = "/api/v1/user/videos/" + video.youtubeId;
$.ajax(apiUrl, {
success: $.proxy(function(data) {
var points = data ? data.points : 0;
- this.modal = this.init(video, points);
- VideoStats.startLoggingProgress(null, video.youtubeId);
- this.modal.modal("show");
+ VideoStats.updatePointsSaved(points);
+ VideoStats.updatePointsDisplay(points);
}, this)
});
},
View
12 profiles/util_profile.py
@@ -60,10 +60,9 @@ def get_last_student_list(request_handler, student_lists, use_cookie=True):
return list_id, current_list
def get_student(coach, request_handler):
- student = request_handler.request_student_user_data(legacy=True)
+ student = request_handler.request_student_user_data()
if student is None:
- raise Exception("No student found with email='%s'."
- % request_handler.request_student_email_legacy())
+ raise Exception("No student found")
if not student.is_coached_by(coach):
raise Exception("Not your student!")
return student
@@ -100,7 +99,6 @@ class ViewClassProfile(request_handler.RequestHandler):
child_user_allowed=False,
demo_user_allowed=True)
def get(self):
- show_coach_resources = self.request_bool('show_coach_resources', default=True)
coach = UserData.current()
user_override = self.request_user_data("coach_email")
@@ -238,7 +236,7 @@ def get(self, email_or_username=None, subpath=None):
class UserProfile(object):
- """ Profile information about a user.
+ """Profile information about a user.
This is a transient object and derived from the information in UserData,
and formatted/tailored for use as an object about a user's public profile.
@@ -422,9 +420,7 @@ def get(self):
self.response.out.write(html)
def get_profile_target_user_data(self):
- email = self.request_student_email_legacy()
- # TODO: ACL
- return UserData.get_possibly_current_user(email)
+ return self.request_visible_student_user_data()
def redirect_if_not_ajax(self, student):
if not self.is_ajax_request():
View
76 request_handler.py
@@ -78,41 +78,69 @@ def request_date_iso(self, key, default = None):
else:
raise # No value available and no default supplied, raise error
+ # TODO(benkomalo): kill this, or make it private and
+ # consolidate it with the other request user data methods by
+ # various email keys
def request_user_data(self, key):
email = self.request_string(key)
return user_models.UserData.get_possibly_current_user(email)
def request_visible_student_user_data(self):
- """ Return overridden user data allowed. Otherwise, return the
- currently logged in user.
+ """Return the UserData for the given request.
+
+ This looks for an identifying parameter
+ (see request_student_user_data) and attempts to return that user
+ if the current actor for the request has access to view that
+ user's information (will return None if the actor does not
+ have sufficient permissions)
+
+ If no identifying parameter is specified, returns the current user.
"""
override_user_data = self.request_student_user_data()
+ if not override_user_data:
+ # TODO(benkomalo): maybe this shouldn't fallback to current user?
+ # It seems like a weird API to accept an explicit user identifier
+ # but then fallback to the current user if that identifier doesn't
+ # resolve. It should give an error instead.
+ return user_models.UserData.current()
return user_models.UserData.get_visible_user(override_user_data)
- def request_user_data_by_user_id(self):
+ def request_student_user_data(self):
+ """Return the specified UserData for the given request.
+
+ This looks for an identifying parameter, looking first for userID,
+ then username, then email, from the request parameters and returns
+ the user that matches, if any.
+ """
+ current = user_models.UserData.current()
user_id = self.request_string("userID")
- return user_models.UserData.get_from_user_id(user_id)
-
- # get the UserData instance based on the querystring. The precedence is:
- # 1. email
- # 2. student_email
- # the precendence is reversed when legacy is True. A warning will be logged
- # if a legacy parameter is encountered when not expected.
- def request_student_user_data(self, legacy=False):
- if legacy:
- email = self.request_student_email_legacy()
- else:
- email = self.request_student_email()
- return user_models.UserData.get_possibly_current_user(email)
+ if user_id:
+ if current and current.user_id == user_id:
+ return current
+ return user_models.UserData.get_from_user_id(user_id)
+
+ username = self.request_string("username")
+ if username:
+ if current and user_models.UniqueUsername.matches(
+ current.username, username):
+ return current
+ return user_models.UserData.get_from_username(username)
+
+ email = self._request_student_email()
+ if email:
+ if current and current.email == email:
+ return current
+ return user_models.UserData.get_from_user_input_email(email)
- def request_student_email_legacy(self):
- email = self.request_string("email")
- email = self.request_string("student_email", email)
- # no warning is logged here as we should aim to completely move to
- # email, but no effort has been made to update old calls yet.
- return email
+ return None
- def request_student_email(self):
+ def _request_student_email(self):
+ """Retrieve email parameter from the request.
+
+ This abstracts away some history behind the name changes for the email
+ parameter and is robust to handling "student_email" and "email"
+ parameter names.
+ """
email = self.request_string("student_email")
if email:
logging.warning("API called with legacy student_email parameter")
@@ -318,7 +346,7 @@ def wrapper(self, *args, **kwargs):
return decorator
def user_agent(self):
- return str(self.request.headers['User-Agent'])
+ return str(self.request.headers.get('User-Agent', ""))
def is_mobile_capable(self):
user_agent_lower = self.user_agent().lower()
View
77 request_handler_test.py
@@ -0,0 +1,77 @@
+from __future__ import with_statement
+import mock
+
+from google.appengine.ext import db
+import webapp2
+
+import request_handler
+import testutil
+import user_models
+
+class DummyHandler(request_handler.RequestHandler):
+ pass
+
+# TODO(benkomalo): write tests to check for ACL-checking of
+# request_visible_student_user_data
+class RequestHandlerTest(testutil.GAEModelTestCase):
+ def setUp(self):
+ super(RequestHandlerTest, self).setUp()
+ self.orig_current_user = user_models.UserData.current
+ self.handler = DummyHandler()
+
+ def tearDown(self):
+ user_models.UserData.current = self.orig_current_user
+ super(RequestHandlerTest, self).tearDown()
+
+ def fake_request(self, params={}, current_user=None):
+ if current_user:
+ user_models.UserData.current = staticmethod(lambda: current_user)
+ self.handler.request = webapp2.Request.blank("/", POST=params)
+
+ def make_user(self, user_id, email=None, username=None):
+ email = email or user_id
+ u = user_models.UserData.insert_for(user_id, email, username=username)
+ u.user_email = email
+ u.put()
+ db.get(u.key()) # Flush db transaction.
+ return u
+
+ def test_getting_user_data_when_none_specified(self):
+ actor = self.make_user("actor")
+ self.fake_request(current_user=actor)
+
+ # Note this shouldn't fallback to the current user
+ self.assertTrue(self.handler.request_student_user_data() is None)
+
+ # But this one does
+ self.assertEquals(
+ actor.key(),
+ self.handler.request_visible_student_user_data().key())
+
+ def test_getting_user_data_when_explicitly_specified(self):
+ actor = self.make_user("actor")
+ other = self.make_user("other", username="otherusername")
+ self.fake_request(params={"userID": "other"}, current_user=actor)
+ self.assertEquals(
+ other.key(),
+ self.handler.request_student_user_data().key())
+
+ self.fake_request(params={"email": "other"}, current_user=actor)
+ self.assertEquals(
+ other.key(),
+ self.handler.request_student_user_data().key())
+
+ # Legacy naming should work, too, though it warns
+ with mock.patch("logging.warning") as warnmock:
+ self.fake_request(params={"student_email": "other"},
+ current_user=actor)
+ self.assertEquals(
+ other.key(),
+ self.handler.request_student_user_data().key())
+ self.assertEquals(1, warnmock.call_count)
+
+ self.fake_request(params={"username": "otherusername"},
+ current_user=actor)
+ self.assertEquals(
+ other.key(),
+ self.handler.request_student_user_data().key())
View
49 user_models.py
@@ -116,10 +116,10 @@ class UserData(gae_bingo.models.GAEBingoIdentityModel,
last_badge_review = db.DateTimeProperty(indexed=False)
last_activity = db.DateTimeProperty(indexed=False)
start_consecutive_activity_date = db.DateTimeProperty(indexed=False)
- count_feedback_notification = db.IntegerProperty(default= -1,
+ count_feedback_notification = db.IntegerProperty(default=-1,
indexed=False)
- question_sort_order = db.IntegerProperty(default= -1, indexed=False)
+ question_sort_order = db.IntegerProperty(default=-1, indexed=False)
uservideocss_version = db.IntegerProperty(default=0, indexed=False)
has_current_goals = db.BooleanProperty(default=False, indexed=False)
@@ -426,7 +426,8 @@ def is_phantom(self):
@property
def is_demo(self):
- return self.user_email.startswith(_COACH_DEMO_COWORKER_EMAIL)
+ return (self.user_email and
+ self.user_email.startswith(_COACH_DEMO_COWORKER_EMAIL))
@property
def is_pre_phantom(self):
@@ -493,15 +494,20 @@ def get_from_username_or_email(username_or_email):
user_data = None
if UniqueUsername.is_valid_username(username_or_email):
- user_data = UserData.get_from_username(username_or_email)
+ user_data = UserData.get_possibly_current_user_by_username(
+ username_or_email)
else:
user_data = UserData.get_possibly_current_user(username_or_email)
return user_data
+ # TODO(benkomalo): collapse this method into the other methods in
+ # request_handler related to getting a user that may be the current user.
+ # Either that or make this private and encourage clients to use the
+ # request_handler versions instead.
@staticmethod
def get_possibly_current_user(identifier):
- """ Returns a UserData object corresponding to the identifier
+ """Return a UserData object corresponding to the identifier
using the request cache for the current-logged in user if
the identifier matches.
@@ -511,7 +517,6 @@ def get_possibly_current_user(identifier):
Note that this is the user visible e-mail, not the email used in
the db User property, as most user-visible operations should
use that field as parameters and not the db key email.
-
"""
if not identifier:
@@ -535,7 +540,8 @@ def get_possibly_current_user_by_username(username):
return None
user_data_current = UserData.current()
- if user_data_current and user_data_current.username == username:
+ if (user_data_current and
+ UniqueUsername.matches(user_data_current.username, username)):
return user_data_current
return UserData.get_from_username(username)
@@ -939,9 +945,11 @@ def is_visible_to(self, user_data):
""" Returns whether or not this user's information is *fully* visible
to the specified user
"""
- return (self.key_email == user_data.key_email or self.is_coached_by(user_data)
- or self.is_coached_by_coworker_of_coach(user_data)
- or user_data.developer or user_data.is_administrator())
+ return (self.key() == user_data.key() or
+ self.is_coached_by(user_data) or
+ self.is_coached_by_coworker_of_coach(user_data) or
+ user_data.developer or
+ user_data.is_administrator())
def are_students_visible_to(self, user_data):
return self.is_coworker_of(user_data) or user_data.developer or user_data.is_administrator()
@@ -1076,9 +1084,9 @@ def has_password(self):
return self.credential_version is not None
def has_public_profile(self):
- return (self.is_profile_public
- and self.username is not None
- and len(self.username) > 0)
+ return (self.is_profile_public and
+ bool(self.username) and
+ not self.is_child_account())
def __str__(self):
return self.__unicode__()
@@ -1180,6 +1188,21 @@ def is_available_username(username, key_name=None, clock=None):
clock = datetime.datetime
return entity is None or not entity._is_in_holding(clock.utcnow())
+ @staticmethod
+ def matches(username1, username2):
+ """Determine if two username strings match to one canonical name.
+
+ If either string is not a valid username, will return False.
+ """
+ if not username1 or not username2:
+ return False
+ key1 = UniqueUsername.build_key_name(username1)
+ key2 = UniqueUsername.build_key_name(username2)
+ if (not UniqueUsername.is_valid_username(username1, key1) or
+ not UniqueUsername.is_valid_username(username2, key2)):
+ return False
+ return key1 == key2
+
def _is_in_holding(self, utcnow):
return self.release_date + UniqueUsername.HOLDING_PERIOD_DELTA >= utcnow
View
9 user_models_test.py
@@ -277,6 +277,15 @@ def test_releasing_usernames(self):
u2.user_id,
UserData.get_from_username("superbob").user_id)
+ def test_usernames_dont_match_if_invalid(self):
+ self.assertFalse(UniqueUsername.matches(None, None))
+ self.assertFalse(UniqueUsername.matches("superbob", None))
+ self.assertFalse(UniqueUsername.matches("superbob", "i n v a l id"))
+
+ def test_username_matching(self):
+ self.assertTrue(UniqueUsername.matches("superbob", "super.bob"))
+ self.assertTrue(UniqueUsername.matches("superbob", "SuperBob"))
+ self.assertFalse(UniqueUsername.matches("superbob", "fakebob"))
class ProfileSegmentTest(testutil.GAEModelTestCase):
def to_url(self, user):

0 comments on commit 32012f9

Please sign in to comment.
Something went wrong with that request. Please try again.