def metrics(request): from webinars_web.webinars import models as wm now = time() data = {} data['installs']=[0]*7 data['uninstalls']=[0]*7 data['new_mrr']=[0]*7 data['recurring_mrr']=[0]*7 data['months']=['Dec','Jan','Feb','Mar','Apr','May','Jun'] for hub in wm.Hub.objects.filter(internal=False): if hub.friends_and_family: continue install_month = hub.created_at.month print install_month uninstall_month = hub.uninstalled_at and hub.uninstalled_at.month data['installs'][install_month%12] += 1 if uninstall_month is not None: data['uninstalls'][uninstall_month%12] += 1 if not hub.beta: mrr_at = hub.created_at + delta(md=30) new = True while mrr_at < now and (uninstall_month is None or mrr_at < hub.uninstalled_at): if new: data['new_mrr'][mrr_at.month%12] += 50 new = False else: data['recurring_mrr'][mrr_at.month%12] += 50 mrr_at = mrr_at + delta(md=30) data['net_installs'] = [] data['total_mrr'] = [] for i in range(len(data['months'])): data['net_installs'].append(data['installs'][i]-data['uninstalls'][i]) data['total_mrr'].append(data['new_mrr'][i]+data['recurring_mrr'][i]) for attr in ('installs','uninstalls','net_installs','new_mrr','recurring_mrr','total_mrr'): data['total_%s'%attr] = sum(data[attr]) return render_to_response('hubs/metrics.djml', { 'data': data }, context_instance=RequestContext(request))
def setUp(self): self.userdata = {'username':'******', 'password':'******'} self.user = User.objects.create_user(**self.userdata) self.user = User.objects.get_by_natural_key('xenuuu') self.recipient = Recipient.objects.create(sender=self.user, sender_name='bob', sender_phone='000-000-0000', name='granny', email='*****@*****.**', timezone='America/Los_Angeles' ) self.subscription = TumblrSubscription.objects.create(recipient=self.recipient, short_name='bobs_monkeys', pretty_name="Bob's Monkey Photos", avatar='monkey.jpg') #supplying all fields to avoid call to tumblr start_dt = time() - delta(hours=1 * 7 * 24) end_dt = time() + delta(hours=1 * 7 * 24) self.vacation = Vacation.objects.create(recipient=self.recipient, start_date=start_dt.datetime, end_date=end_dt.datetime ) #urls of things that require you to be logged in to access self.login_required_urls = [ reverse_lazy('subscription_create_tumblr'), reverse_lazy('subscription_list'), reverse_lazy('subscription_detail_tumblr', kwargs={'pk':self.subscription.pk}), reverse_lazy('subscription_delete_tumblr', kwargs={'pk':self.subscription.pk}), reverse_lazy('recipient_create'), reverse_lazy('recipient_detail', kwargs={'pk':self.recipient.pk}), reverse_lazy('vacation_create', kwargs={'recipient_id':self.recipient.pk}), reverse_lazy('vacation_cancel', kwargs={'pk':self.vacation.pk}) #todo: delete the recipient ] #urls of things that you must own the object in question (or a related one) in order to access. #these will just be tested by being logged in as a user other than xenuuu self.ownership_required_urls = [ reverse_lazy('subscription_detail_tumblr', kwargs={'pk':self.subscription.pk}), reverse_lazy('subscription_delete_tumblr', kwargs={'pk':self.subscription.pk}), reverse_lazy('recipient_detail', kwargs={'pk':self.recipient.pk}), reverse_lazy('vacation_create', kwargs={'recipient_id':self.recipient.pk}), reverse_lazy('vacation_cancel', kwargs={'pk':self.vacation.pk}) ] self.login_url = reverse('auth_login')
def new_or_edit(request, account_id=None, setup=False): from webinars_web.webinars import models as wm hub = models.Hub.ensure(request.marketplace.hub_id) kwargs = {'hub': hub} if account_id: kwargs['instance']=models.Account.objects.get(pk=account_id) if request.method == 'POST': # If the form has been submitted... if request.POST.get('cancel'): return HttpResponseRedirect('%saccounts'%request.marketplace.base_url) # Redirect for cancel if request.POST.get('account_type','1') == '2': redirect_uri = urlquote_plus('%s/webinars/hubs/%s/accounts/new?label=%s' % (settings.GTW_OAUTH_REDIRECT_PROTOCOL_HOST, hub.id, urlquote_plus(request.POST.get('extra','')))) return HttpResponseRedirect('https://api.citrixonline.com/oauth/authorize?client_id=%s&redirect_uri=%s' % (settings.GTW_API_KEY, redirect_uri)) form = AccountForm(request.POST, **kwargs) # A form bound to the POST data if form.is_valid(): # All validation rules pass if form.cleaned_data.get('account_type') == 1: deleted_possibles = wm.Account.objects.filter(hub=hub, username=form.cleaned_data['username'], extra=form.cleaned_data['extra'], deleted_at__isnull=False) elif form.cleaned_data.get('account_type') == 2: deleted_possibles =wm.Account.objects.filter(hub=hub, username=form.cleaned_data['username'], deleted_at__isnull=False) if deleted_possibles: account = deleted_possibles[0] account.deleted_at = None account.password = form.cleaned_data['password'] account.exclude_old_events_from_hubspot = bool(form.cleaned_data.get('exclude_old_events_from_hubspot')) if account.exclude_old_events_from_hubspot: ignore_delta = int(form.cleaned_data.get('exclusion_date_delta')) account.exclusion_date = (time() - delta(md=ignore_delta)).us else: account.exclusion_date = None else: account = form.save(commit=False) account.exclude_old_events_from_hubspot = bool(form.cleaned_data.get('exclude_old_events_from_hubspot')) if account.exclude_old_events_from_hubspot: ignore_delta = int(form.cleaned_data.get('exclusion_date_delta')) account.exclusion_date = (time() - delta(md=ignore_delta)).us print account.exclusion_date else: account.exclusion_date = None account.hub_id = request.marketplace.hub_id account.default = False account.prevent_unformed_lead_import = False account.save() account.hub.sync(visible=True) return HttpResponseRedirect('%sevents'%(request.marketplace.base_url)) # Redirect after POST else: form = AccountForm(**kwargs) # An unbound form return render_to_response('accounts/%s.djml'%(setup and 'setup' or account_id and 'edit' or 'new'), { 'form': form, 'account_types': models.AccountType.objects.all() }, context_instance=RequestContext(request))
def test_register(self): w = Webinar(self.organizer, key=2394, timezone='America/New_York', sessions=[]) s = Session(w, key=6043, started_at=time('2012-06-01'), attendees=[]) s.attendees.append( Registrant(webinar=w, session=s, key=2305, first_name=u'Suzy', last_name=u'Samwell', email=u'*****@*****.**', duration=delta(s=4931))) with mocker(CreateRegistrant, text=self.registered_json): seed_registrant = Registrant(webinar=w, session=s, first_name=u'J\u00f6hn', last_name=u'Smith', email=u'*****@*****.**') expected_registrant = Registrant( webinar=w, session=s, key=2038, first_name=u'J\u00f6hn', last_name=u'Smith', email=u'*****@*****.**', join_url='https://bit.ly/00293423') self.assertEquals(expected_registrant, seed_registrant.create())
def test_delete_started_vacation(self): """ Cancelling a vacation that's already started actually sets the end date to now, not actually deleting it. """ self.client.login(**self.userdata) start = time(tz='UTC') - delta(hours=2*24) #started yesterday end = start + delta(hours=7*24) vacation = Vacation.objects.create(recipient=self.recipient, start_date=start.datetime, end_date=end.datetime) url = reverse('vacation_cancel', kwargs={'pk':vacation.pk}) self.client.post(url) vacation = Vacation.objects.get(pk=vacation.pk) #reload self.assertTrue(vacation.end_date <= time(tz='UTC').datetime)
def __init__(self, **kwargs): super(Registrant, self).__init__() self.webinar = kwargs.get('webinar') self.session = kwargs.get('session') self.key = mget(kwargs, 'key', 'registrant_key', 'registrantKey') self.email = nlower(mget(kwargs, 'email', 'attendeeEmail')) self.first_name = mget(kwargs, 'first_name', 'firstName', 'first') self.last_name = mget(kwargs, 'last_name', 'lastName', 'last') if kwargs.get('name'): self.name = nstrip(kwargs.get('name')) self.registered_at = ntime( mget(kwargs, 'registered_at', 'registrationDate')) self.join_url = mget(kwargs, 'join_url', 'joinUrl') self.status = kwargs.get('status') self.viewings = kwargs.get('viewings', []) if not self.viewings and kwargs.get('attendance'): self.viewings = sort([(time(d['joinTime']), time(d['leaveTime'])) for d in kwargs['attendance']]) if not self.viewings and ( kwargs.get('duration') or kwargs.get('attendanceTimeInSeconds') ) and self.session and self.session.key and self.session.started_at: duration = kwargs.get( 'duration') or kwargs.get('attendanceTimeInSeconds') and delta( s=kwargs['attendanceTimeInSeconds']) self.viewings = [(self.session.started_at, self.session.started_at + duration)]
def test_delete_future_vacation(self): """ Cancelling a future vacation that has not yet started is fine, just delete it. """ self.client.login(**self.userdata) start = time(tz='UTC') + delta(hours=1*24) end = start + delta(hours=1*24*7) vacation = Vacation.objects.create(recipient=self.recipient, start_date=start.datetime, end_date=end.datetime) url = reverse('vacation_cancel', kwargs={'pk':vacation.pk}) self.client.post(url) self.assertRaises(Vacation.DoesNotExist, Vacation.objects.get, pk=vacation.pk)
def lock(self, timeout=None, poll_rate=None): # poll_rate & timeout as deltas or micros timeout = ndelta(timeout) or self.timeout or delta(0) poll_rate = ndelta(poll_rate) or self.poll_rate start = time() while not self._attempt_lock(): if poll_rate: poll_rate.sleep() if time() > start + timeout: break return self.locked
def test_bad_vacation_form(self): """ Submitting a vacation form where the start date is after the end date is an error. """ start = time(tz='UTC') end = start - delta(hours=1*24) data = {'start_date': start.strftime('%Y-%m-%d'), 'end_date': end.strftime('%Y-%m-%d')} form = VacationForm(data) self.assertFalse(form.is_valid())
def test_delete_somebody_elses_vacation(self): """ Users shouldn't be able to delete vacations belonging to other users. """ new_user = User.objects.create_user("new_user", password='******') new_recipient = Recipient.objects.create(sender=new_user, name='Nanna', email='*****@*****.**', timezone='Europe/Copenhagen') #this vacation starts and ends in the future, so it can be deleted and not just trigger #the end_date change. start_date = time(tz='UTC') + delta(hours=1 * 24) end_date = time(tz='UTC') + delta(hours=4 * 24 * 7) vacation = Vacation.objects.create(recipient=new_recipient, start_date=start_date.datetime, end_date=end_date.datetime) url = reverse('vacation_cancel', kwargs={'pk':vacation.pk}) self.client.login(**self.userdata) #login as self.user, NOT new_user #try to delete vacation belonging to new_user res = self.client.post(url) vacation = Vacation.objects.get(pk=vacation.pk) #force reload, should still exist
def test_get_vacationing_recipients(self): """ Tests getting the set of recipients currently on vacation. """ #start with a clean slate Recipient.objects.all().delete() now = time(tz='UTC') last_week = now - delta(hours=7*24) next_week = now + delta(hours=7*24) for i in range(10): recipient = Recipient.objects.create(sender=self.user, name='Nonna_%s' % i, email='*****@*****.**' % i, postcode='02540', timezone='America/New_York') if i < 4: #create vacations for the first four Vacation.objects.create(recipient=recipient, start_date=last_week.datetime, end_date=next_week.datetime) self.assertEqual(Recipient.get_vacationing_recipients().count(), 4) self.assertEqual(Recipient.objects.exclude(pk__in=Recipient.get_vacationing_recipients()).count(), 6)
def pre_sync_hook(self, sync): from webinars_web.webinars import models as wm if self.mothballed: return False if self.account.is_webex: if self.starts_at < time()-delta(md=89): # past webex registrant/attendee reporting limit (90 days) self.unknowable_registrants = True self.mothballed = True self.save() return False if not self._lock_for_sync(): if sync.visible and self.current_sync and not self.current_sync.visible: wm.EventSync.objects.filter(id=self.current_sync.id).update(visible=True) return False self.__class__.objects.filter(id=self.id).update(current_sync=sync) self.current_sync = sync # faster than changing attr and save()-ing return True
def test_create_somebody_elses_vacation(self): new_user = User.objects.create_user("new_user", password='******') new_recipient = Recipient.objects.create(sender=new_user, name='Nanna', email='*****@*****.**', timezone='Asia/Tokyo') self.client.login(**self.userdata) url = reverse('vacation_create', kwargs={'recipient_id': new_recipient.pk}) start = time(tz='UTC') end = start - delta(hours=1*24) data = {'start_date': start.strftime('%Y-%m-%d'), 'end_date': end.strftime('%Y-%m-%d')} res = self.client.post(url, data) self.assertEqual(res.status_code, 403) #can't do that
def _parcel_timings(): from webinars_web.webinars import models as wm now = time() time_chunks = {'hour':10**6*60**2, 'day':10**6*60**2*24, 'week':10**6*60**2*24*7} since_times = dict((k,now-v) for k,v in time_chunks.iteritems()) times = dict((k,{'wait':[], 'work':[]}) for k,v in time_chunks.iteritems()) min_since_time = min(since_times.values()) models = (wm.AccountSyncStage, wm.AccountSyncShard, wm.HubSpotEventSyncStage, wm.WebexEventSyncStage, wm.EventSyncShard) for m in models: for created_at, started_at, completed_at in m.objects.filter(created_at__isnull=False, created_at__gt=min_since_time, parent_sync__debug=False, parent_sync__forced_stop=False).values_list('created_at','started_at','completed_at'): for k in time_chunks.keys(): if created_at >= since_times[k] and started_at: times[k]['wait'].append(time(started_at)-time(created_at)) if completed_at: times[k]['work'].append(time(completed_at)-time(started_at)) for k in times.keys(): for t in ('wait','work'): lst = times[k][t] times[k][t] = len(lst)>0 and ((sum(lst,delta(0))/len(lst)).ms, min(lst).ms, max(lst).ms) or (None,None,None) return times
def start(self): from webinars_web.webinars import models as wm now = time() self.__class__.objects.filter(id=self.id).update(started_at=now) self.started_at = now if not self.event.pre_sync_hook(self): self.completed_at = time() self.save() return self if self.event.account.is_webex: wm.StagedWebexRegistrant.pre_stage(self.event) wm.WebexEventSyncStage.trigger_initial_stages(self) elif self.event.account.is_gtw: wm.StagedGTWRegistrant.pre_stage(self.event) wm.GTWEventSyncStage.trigger_initial_stages(self) wm.StagedHubSpotRegistrant.pre_stage(self.event) for event_form in self.event.event_forms.all(): if time() > max(self.event.ended_at,self.event.ends_at)+delta(m=30) and not event_form.cms_form.is_sync_target: continue # avoid pulling any more leads after event is over wm.HubSpotEventSyncStage.objects.create(parent_sync=self, max_size=settings.HUBSPOT_EVENT_SYNC_STAGE_SIZE, event_form=event_form, start_last_modified_at=event_form.last_last_modified_at).trigger() #TODO: figure out what to do when cms form goes away-- maybe need to soft delete forms to keep them from violating foreign key constraints on old syncs? self.event.update_cms_form # establishes its existence-- this is a good place to do it-- don't let the individual shards do it cuz then there are async issues to contend with return self
def actual_duration_in_minutes(self, minutes): if self._started_at: self._ended_at = self._started_at + delta(m=minutes) elif self._ended_at: self._started_at = self._ended_at - delta(m=minutes)
from utils.property import cached_property from utils.dict import mget,kwargs_str from utils.list import sort from giftwrap import Auth, Exchange, JsonExchange from .webinar import Webinar from .session import Session from sanetime import time,delta API_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ' DEFAULT_HISTORY_DELTA = delta(my=20) GTW_NOW_DELTA = delta(s=10) # cuz gtw 500s when your time range enters the future, and their servers seem to be off by a few seconds class Organizer(Auth): domain = 'api.citrixonline.com' def base_path(self): return 'G2W/rest/organizers/%s' % self.key def headers(self): return {'Authorization': 'OAuth oauth_token=%s'%self.oauth} def __init__(self, **kwargs): super(Organizer, self).__init__() self.oauth = mget(kwargs,'oauth','oauth_token','access_token') self.key = mget(kwargs,'key','organizerKey','organizer_key') self.now = time() self.starts_at = kwargs.get('starts_at',None) or (self.now - DEFAULT_HISTORY_DELTA) def __repr__(self): return "Organizer(%s)" % kwargs_str(self.__dict__,'oauth','key') def __str__(self): return unicode(self).encode('utf-8') def __unicode__(self):
ACCOUNT_SYNC_STAGE_SIZE = 10 HISTORIC_ACCOUNT_SYNC_STAGE_SIZE = 10 ACCOUNT_SYNC_SHARD_SIZE = 50 WEBEX_REGISTRANT_EVENT_SYNC_STAGE_SIZE = 500 WEBEX_ATTENDANT_EVENT_SYNC_STAGE_SIZE = 50 HUBSPOT_EVENT_SYNC_STAGE_SIZE = 100 # tried 50, but it just made things worse, cuz the chance of a single call failing is high-- so we need to reduce number of calls EVENT_SHARD_SIZE = 30 TASK_QUEUE_AUTH = tq.Auth(53, HUBSPOT_API_KEY, env=ENV) # Bump NUM_QUEUES if you want more work processed simultaneously. We are currently near our max # capacity, but if more boxes are added, we can consider adding more queues. NUM_QUEUES = 3 # Retry schedule for all taskqueues schedule = [delta(s=20),delta(m=1),delta(m=5),delta(m=30)] # These queues are used to process syncs. # ... TASK_QUEUES = [tq.Queue( TASK_QUEUE_AUTH, name='webinarsxx_%s%s'%(ENV.lower(), x), retry_schedule=schedule, timeout=delta(s=60), frequency=12, idempotent=True) for x in range(NUM_QUEUES)] # CONVERSION_QUEUE is the taskqueue used to create conversion events in hubspot via the leads API. # It is a separate queue because we rely on its idempotency to prevent duplicate conversion events. # It is used by webinars/cynq/registrant.py in HubSpotRegistrantRemoteStore._single_update(). # uid is a hexdigested hash of the form submission json. We could avoid an extra queue by slamming
def scheduled_duration_in_minutes(self, minutes): if self._starts_at: self._ends_at = self._starts_at + delta(m=minutes) elif self._ends_at: self._starts_at = self._ends_at - delta(m=minutes)
def sync_overdue(self): if self.uninstalled_at or self.current_sync: return False if not self.last_sync: return True return not self.last_sync or (time() - self.last_sync.completed_at) > delta(m=MINUTES_TIL_STALE_SYNC) # greater than 15 minutes
def post_sync_hook(self, started_at, completed_at): if started_at > self.ended_at: if not self.registrant_set.filter(lead_guid__isnull=True, deleted_at__isnull=True).count(): if self._ended_at or self.ends_at and self.ends_at < time()-delta(md=2): self.mothballed = True self.save()
def test_register(self): w = Webinar(self.organizer, key=2394, timezone='America/New_York', sessions=[]) s = Session(w, key=6043, started_at=time('2012-06-01'), attendees=[]) s.attendees.append(Registrant(webinar=w, session=s, key=2305, first_name=u'Suzy', last_name=u'Samwell', email=u'*****@*****.**', duration=delta(s=4931))) with mocker(CreateRegistrant, text=self.registered_json): seed_registrant = Registrant(webinar=w, session=s, first_name=u'J\u00f6hn', last_name=u'Smith', email=u'*****@*****.**') expected_registrant = Registrant(webinar=w, session=s, key=2038, first_name=u'J\u00f6hn', last_name=u'Smith', email=u'*****@*****.**', join_url='https://bit.ly/00293423') self.assertEquals(expected_registrant, seed_registrant.create())
def is_bad(self): return not self.completed_at and (time() - self.started_at) > delta(h=1) @property
def __init__(self, **kwargs): super(Registrant, self).__init__() self.webinar = kwargs.get('webinar') self.session = kwargs.get('session') self.key = mget(kwargs, 'key', 'registrant_key', 'registrantKey') self.email = nlower(mget(kwargs, 'email', 'attendeeEmail')) self.first_name = mget(kwargs, 'first_name', 'firstName', 'first') self.last_name = mget(kwargs, 'last_name', 'lastName', 'last') if kwargs.get('name'): self.name = nstrip(kwargs.get('name')) self.registered_at = ntime(mget(kwargs, 'registered_at', 'registrationDate')) self.join_url = mget(kwargs, 'join_url', 'joinUrl') self.status = kwargs.get('status') self.viewings = kwargs.get('viewings',[]) if not self.viewings and kwargs.get('attendance'): self.viewings = sort([(time(d['joinTime']),time(d['leaveTime'])) for d in kwargs['attendance']]) if not self.viewings and (kwargs.get('duration') or kwargs.get('attendanceTimeInSeconds')) and self.session and self.session.key and self.session.started_at: duration = kwargs.get('duration') or kwargs.get('attendanceTimeInSeconds') and delta(s=kwargs['attendanceTimeInSeconds']) self.viewings = [(self.session.started_at, self.session.started_at+duration)]
def is_worrisome(self): return not self.completed_at and (time() - self.started_at) > delta(m=10) @property