@@ -54,7 +54,7 @@ def set_enrollment(self, experiment, alternative):
54
54
alternative will be increment, but those for the old one will not be decremented."""
55
55
raise NotImplementedError
56
56
57
- def record_goal (self , goal_name ):
57
+ def record_goal (self , goal_name , count = 1 ):
58
58
"""Record that this user has performed a particular goal
59
59
60
60
This will update the goal stats for all experiments the user is enrolled in."""
@@ -85,16 +85,62 @@ def is_enrolled(self, experiment_name, alternative, request):
85
85
86
86
return alternative == chosen_alternative
87
87
88
+ def incorporate (self , other_user ):
89
+ """Incorporate all enrollments and goals performed by the other user
90
+
91
+ If this user is not enrolled in a given experiment, the results for the
92
+ other user are incorporated. For experiments this user is already
93
+ enrolled in the results of the other user are discarded.
94
+
95
+ This takes a relatively large amount of time for each experiment the other
96
+ user is enrolled in."""
97
+ for experiment , alternative in other_user ._get_all_enrollments ():
98
+ if not self ._is_enrolled_in_experiment (experiment ):
99
+ self .set_enrollment (experiment , alternative )
100
+ goals = experiment .participant_goal_frequencies (alternative , other_user ._participant_identifier ())
101
+ for goal_name , count in goals :
102
+ experiment .increment_goal_count (alternative , goal_name , self ._participant_identifier (), count )
103
+ other_user ._cancel_enrollment (experiment )
104
+
105
+ def _participant_identifier (self ):
106
+ "Unique identifier for this user in the counter store"
107
+ raise NotImplementedError
108
+
109
+ def _get_all_enrollments (self ):
110
+ "Return experiment, alternative tuples for all experiments the user is enrolled in"
111
+ raise NotImplementedError
112
+
113
+ def _is_enrolled_in_experiment (self , experiment ):
114
+ "Test whether the user currently has an enrollment in the supplied experiment"
115
+ raise NotImplementedError
116
+
117
+ def _cancel_enrollment (self , experiment ):
118
+ "Remove the enrollment and any goals the user has against this experiment"
119
+ raise NotImplementedError
120
+
88
121
89
122
class DummyUser (WebUser ):
90
123
def get_enrollment (self , experiment ):
91
124
return CONTROL_GROUP
92
125
def set_enrollment (self , experiment , alternative ):
93
126
pass
94
- def record_goal (self , goal_name ):
127
+ def record_goal (self , goal_name , count = 1 ):
95
128
pass
96
129
def is_enrolled (self , experiment_name , alternative , request ):
97
130
return alternative == CONTROL_GROUP
131
+ def incorporate (self , other_user ):
132
+ for experiment , alternative in other_user ._get_all_enrollments ():
133
+ other_user ._cancel_enrollment (experiment )
134
+ def _participant_identifier (self ):
135
+ return ""
136
+ def _get_all_enrollments (self ):
137
+ return []
138
+ def _is_enrolled_in_experiment (self , experiment ):
139
+ return False
140
+ def _cancel_enrollment (self , experiment ):
141
+ pass
142
+ def _get_goal_counts (self , experiment , alternative ):
143
+ return {}
98
144
99
145
100
146
class AuthenticatedUser (WebUser ):
@@ -120,17 +166,32 @@ def set_enrollment(self, experiment, alternative):
120
166
enrollment .save ()
121
167
experiment .increment_participant_count (alternative , self ._participant_identifier ())
122
168
123
- def record_goal (self , goal_name ):
124
- enrollments = Enrollment .objects .filter (user = self .user )
125
- if not enrollments :
126
- return
127
- for enrollment in enrollments : # Looks up by PK so no point caching.
128
- if enrollment .experiment .is_displaying_alternatives ():
129
- enrollment .experiment .increment_goal_count (enrollment .alternative , goal_name , self ._participant_identifier ())
169
+ def record_goal (self , goal_name , count = 1 ):
170
+ for experiment , alternative in self ._get_all_enrollments ():
171
+ if experiment .is_displaying_alternatives ():
172
+ experiment .increment_goal_count (alternative , goal_name , self ._participant_identifier (), count )
130
173
131
174
def _participant_identifier (self ):
132
175
return 'user:%d' % (self .user .pk ,)
133
176
177
+ def _get_all_enrollments (self ):
178
+ enrollments = Enrollment .objects .filter (user = self .user ).select_related ("experiment" )
179
+ if enrollments :
180
+ for enrollment in enrollments :
181
+ yield enrollment .experiment , enrollment .alternative
182
+
183
+ def _is_enrolled_in_experiment (self , experiment ):
184
+ return Enrollment .objects .filter (user = self .user , experiment = experiment ).exists ()
185
+
186
+ def _cancel_enrollment (self , experiment ):
187
+ try :
188
+ enrollment = Enrollment .objects .get (user = self .user , experiment = experiment )
189
+ except Enrollment .DoesNotExist :
190
+ pass
191
+ else :
192
+ experiment .remove_participant (enrollment .alternative , self ._participant_identifier ())
193
+ enrollment .delete ()
194
+
134
195
135
196
class SessionUser (WebUser ):
136
197
def __init__ (self , session ):
@@ -151,16 +212,11 @@ def set_enrollment(self, experiment, alternative):
151
212
if self ._is_verified_human ():
152
213
experiment .increment_participant_count (alternative , self ._participant_identifier ())
153
214
154
- def record_goal (self , goal_name ):
215
+ def record_goal (self , goal_name , count = 1 ):
155
216
if self ._is_verified_human ():
156
- enrollments = self .session .get ('experiments_enrollments' , None )
157
- if not enrollments :
158
- return
159
- for experiment_name , (alternative , goals ) in enrollments .items ():
160
- experiment = experiment_manager .get (experiment_name , None )
161
- if experiment and experiment .is_displaying_alternatives ():
162
- experiment .increment_goal_count (alternative , goal_name , self ._participant_identifier ())
163
- return
217
+ for experiment , alternative in self ._get_all_enrollments ():
218
+ if experiment .is_displaying_alternatives ():
219
+ experiment .increment_goal_count (alternative , goal_name , self ._participant_identifier (), count )
164
220
else :
165
221
goals = self .session .get ('experiments_goals' , [])
166
222
goals .append (goal_name ) # Note, duplicates are allowed
@@ -172,16 +228,9 @@ def confirm_human(self):
172
228
173
229
self .session ['experiments_verified_human' ] = True
174
230
175
- enrollments = self .session .get ('experiments_enrollments' , None )
176
- if not enrollments :
177
- return
178
-
179
231
# Replay enrollments
180
- for experiment_name , data in enrollments .items ():
181
- alternative , goals = data
182
- experiment = experiment_manager .get (experiment_name , None )
183
- if experiment :
184
- experiment .increment_participant_count (alternative , self ._participant_identifier ())
232
+ for experiment , alternative in self ._get_all_enrollments ():
233
+ experiment .increment_participant_count (alternative , self ._participant_identifier ())
185
234
186
235
# Replay goals
187
236
if 'experiments_goals' in self .session :
@@ -190,12 +239,34 @@ def confirm_human(self):
190
239
del self .session ['experiments_goals' ]
191
240
192
241
def _participant_identifier (self ):
193
- if not self .session .session_key :
194
- self .session .save () # Force session key
195
- return 'session:%s' % (self .session .session_key ,)
242
+ if 'experiments_session_key' not in self .session :
243
+ if not self .session .session_key :
244
+ self .session .save () # Force session key
245
+ self .session ['experiments_session_key' ] = self .session .session_key
246
+ return 'session:%s' % (self .session ['experiments_session_key' ],)
196
247
197
248
def _is_verified_human (self ):
198
249
if getattr (settings , 'EXPERIMENTS_VERIFY_HUMAN' , True ):
199
250
return self .session .get ('experiments_verified_human' , False )
200
251
else :
201
252
return True
253
+
254
+ def _get_all_enrollments (self ):
255
+ enrollments = self .session .get ('experiments_enrollments' , None )
256
+ if enrollments :
257
+ for experiment_name , data in enrollments .items ():
258
+ alternative , _ = data
259
+ experiment = experiment_manager .get (experiment_name , None )
260
+ if experiment :
261
+ yield experiment , alternative
262
+
263
+ def _is_enrolled_in_experiment (self , experiment ):
264
+ enrollments = self .session .get ('experiments_enrollments' , None )
265
+ return enrollments and experiment .name in enrollments
266
+
267
+ def _cancel_enrollment (self , experiment ):
268
+ alternative = self .get_enrollment (experiment )
269
+ if alternative :
270
+ experiment .remove_participant (alternative , self ._participant_identifier ())
271
+ enrollments = self .session .get ('experiments_enrollments' , None )
272
+ del enrollments [experiment .name ]
0 commit comments