# HG changeset patch # User Pawel Solyga # Date 1243285196 -7200 # Node ID f78caf12f32d41f780b3f047a828fedbbdd6a287 # Parent 621252e2cc187cb46415c6a3c80768451aa2f274 Add helper functions, model update and jobs for unique user ids. diff -r 621252e2cc18 -r f78caf12f32d app/soc/cron/job.py --- a/app/soc/cron/job.py Mon May 25 22:59:33 2009 +0200 +++ b/app/soc/cron/job.py Mon May 25 22:59:56 2009 +0200 @@ -29,8 +29,10 @@ from google.appengine.runtime import DeadlineExceededError from soc.cron import student_proposal_mailer +from soc.cron import unique_user_id_adder from soc.models.job import Job + class Error(Exception): """Base class for all exceptions raised by this module. """ @@ -67,6 +69,10 @@ student_proposal_mailer.setupStudentProposalMailing self.tasks['sendStudentProposalMail'] = \ student_proposal_mailer.sendStudentProposalMail + self.tasks['setupUniqueUserIdAdder'] = \ + unique_user_id_adder.setupUniqueUserIdAdder + self.tasks['addUniqueUserIds'] = \ + unique_user_id_adder.addUniqueUserIds def claimJob(self, job_key): """A transaction to claim a job. diff -r 621252e2cc18 -r f78caf12f32d app/soc/cron/unique_user_id_adder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/cron/unique_user_id_adder.py Mon May 25 22:59:56 2009 +0200 @@ -0,0 +1,135 @@ +#!/usr/bin/python2.5 +# +# Copyright 2009 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cron job handler for adding unique user id. +""" + +__authors__ = [ + '"Pawel Solyga" ', + ] + + +from google.appengine.ext import db +from google.appengine.api import users +from soc.logic.models.job import logic as job_logic +from soc.logic.models.priority_group import logic as priority_logic +from soc.logic.models.user import logic as user_logic +from soc.models.user import User + + +# amount of users to create jobs for before updating +DEF_USER_STEP_SIZE = 10 + + +class TempUserWithUniqueId(db.Model): + """Helper model for temporary storing User Property with unique id. + """ + user = db.UserProperty(required=True) + + +def emailToAccountAndUserId(address): + """Return a stable user_id string based on an email address, or None if + the address is not a valid/existing google account. + """ + user = users.User(address) + key = TempUserWithUniqueId(user=user).put() + obj = TempUserWithUniqueId.get(key) + return (obj, obj.user.user_id()) + + +def setupUniqueUserIdAdder(job_entity): + """Job that setup jobs that will add unique user ids to all Users. + + Args: + job_entity: a Job entity with key_data set to + [last_completed_user] + """ + + from soc.cron.job import FatalJobError + + user_fields = {'user_id': None} + + if len(key_data) == 1: + # start where we left off + user_fields['__key__ >'] = key_data[0] + + m_users = user_logic.getForFields(user_fields, + limit=DEF_USER_STEP_SIZE) + + # set the default fields for the jobs we are going to create + priority_group = priority_logic.getGroup(priority_logic.CONVERT) + job_fields = { + 'priority_group': priority_group, + 'task_name': 'addUniqueUserIds'} + + job_query_fields = job_fields.copy() + + while m_users: + # for each user create a adder job + for user in m_users: + + job_query_fields['key_data'] = user.key() + adder_job = job_logic.getForFields(job_query_fields, unique=True) + + if not adder_job: + # this user doesn't have unique id yet + job_fields['key_data'] = [user.key()] + job_logic.updateOrCreateFromFields(job_fields) + + # update our own job + last_user_key = m_users[-1].key() + + if len(key_data) == 1: + key_data[0] = last_student_key + else: + key_data.append(last_student_key) + + updated_job_fields = {'key_data': key_data} + job_logic.updateEntityProperties(job_entity, updated_job_fields) + + # rinse and repeat + user_fields['__key__ >'] = last_user_key + m_users = student_logic.getForFields(user_fields, + limit=DEF_USER_STEP_SIZE) + + # we are finished + return + + +def addUniqueUserIds(job_entity): + """Job that will add unique user id to a User. + + Args: + job_entity: a Job entity with key_data set to [user_key] + """ + + from soc.cron.job import FatalJobError + + user_keyname = job_entity.key_data[0].name() + user_entity = user_logic.getFromKeyName(user_keyname) + + if not user_entity: + raise FatalJobError('The User with keyname %s does not exist!' % ( + user_keyname)) + + # add unique user id + account, user_id = emailToAccountAndUserId(user_entity.account.email()) + user_entity.account = account + user_entity.user_id = user_id + user_entity.put() + + # we are done here + return \ No newline at end of file diff -r 621252e2cc18 -r f78caf12f32d app/soc/logic/accounts.py --- a/app/soc/logic/accounts.py Mon May 25 22:59:33 2009 +0200 +++ b/app/soc/logic/accounts.py Mon May 25 22:59:56 2009 +0200 @@ -35,6 +35,13 @@ return normalizeAccount(account) if (account and normalize) else account +def getCurrentUserId(): + """Returns a unique id of the current user. + """ + + return users.get_current_user().user_id() + + def normalizeAccount(account): """Returns a normalized version of the specified account. """ @@ -46,6 +53,7 @@ return users.User(email=normalized) + def denormalizeAccount(account): """Returns a denormalized version of the specified account. """ @@ -58,6 +66,7 @@ return users.User(email=denormalized) + def isDeveloper(account=None): """Returns True if a Google Account is a Developer with special privileges. diff -r 621252e2cc18 -r f78caf12f32d app/soc/logic/models/user.py --- a/app/soc/logic/models/user.py Mon May 25 22:59:33 2009 +0200 +++ b/app/soc/logic/models/user.py Mon May 25 22:59:56 2009 +0200 @@ -75,6 +75,20 @@ return self.getForAccount(account) + def getForCurrentUserId(self): + """Retrieves the user entity for the currently logged in user id. + + If there is no user logged in, or they have no valid associated User + entity, None is returned. + """ + + user_id = accounts.getCurrentUserId() + + if not user_id: + return None + + return self.getForUserId(user_id) + def getForAccount(self, account): """Retrieves the user entity for the specified account. @@ -94,6 +108,23 @@ return self.getForFields(filter=fields, unique=True) + def getForUserId(self, user_id): + """Retrieves the user entity for the specified user id. + + If there is no user logged in, or they have no valid associated User + entity, None is returned. + """ + + if not user_id: + raise base.InvalidArgumentError + + fields = { + 'user_id': user_id, + 'status':'valid', + } + + return self.getForFields(filter=fields, unique=True) + def isDeveloper(self, account=None, user=None): """Returns true iff the specified user is a Developer. diff -r 621252e2cc18 -r f78caf12f32d app/soc/models/user.py --- a/app/soc/models/user.py Mon May 25 22:59:33 2009 +0200 +++ b/app/soc/models/user.py Mon May 25 22:59:56 2009 +0200 @@ -71,6 +71,9 @@ verbose_name=ugettext('User account')) account.help_text = ugettext( 'A valid Google Account.') + + #: Google Account unique user id + user_id = db.StringProperty(required=True) #: A list (possibly empty) of former Google Accounts associated with #: this User. diff -r 621252e2cc18 -r f78caf12f32d scripts/stats.py --- a/scripts/stats.py Mon May 25 22:59:33 2009 +0200 +++ b/scripts/stats.py Mon May 25 22:59:56 2009 +0200 @@ -277,6 +277,21 @@ job_logic.updateOrCreateFromFields(job_fields) +def startUniqueUserIdConversion(): + """Creates the job that is responsible for adding unique user ids. + """ + + from soc.logic.models.job import logic as job_logic + from soc.logic.models.priority_group import logic as priority_logic + + priority_group = priority_logic.getGroup(priority_logic.CONVERT) + job_fields = { + 'priority_group': priority_group, + 'task_name': 'setupUniqueUserIdAdder'} + + job_logic.updateOrCreateFromFields(job_fields) + + def reviveJobs(amount): """Sets jobs that are stuck in 'aborted' to waiting. @@ -493,6 +508,7 @@ 'reviveJobs': reviveJobs, 'deidleJobs': deidleJobs, 'acceptedStudentsCSVExport': acceptedStudentsCSVExport, + 'startUniqueUserIdConversion': startUniqueUserIdConversion, } interactive.remote(args, context)