importurllib2importfeedparserfromurllibimporturlencodefromdatetimeimportdatetime,timedeltafromhashlibimportsha1fromdjango.dbimportmodelsfromdjango.confimportsettingsfromdjango.core.urlresolversimportreverse,NoReverseMatchfromdjpubsubhubbubimportsignalsfromdjpubsubhubbub.configimportConfigDEFAULT_LEASE_SECONDS=2592000# 30 days in secondsclassSubscriptionManager(models.Manager):defdo_action(self,topic,hub=None,callback=None,lease_seconds=None,mode='subscribe',verify='sync',debug=False):config=Config()ifhubisNone:hub=self._get_hub(topic)ifhubisNone:raiseTypeError('hub cannot be None if the feed does not provide it')iflease_secondsisNone:lease_seconds=getattr(settings,'PUBSUBHUBBUB_LEASE_SECONDS',DEFAULT_LEASE_SECONDS)subscription,created=self.get_or_create(hub=hub,topic=topic)signals.pre_subscribe.send(sender=subscription,created=created)subscription.set_expiration(lease_seconds,run_save=False)token=subscription.generate_token(mode)headers=config.get_extra_hub_headers(topic,hub)ifcallbackisNone:try:callback_path=reverse('pubsubhubbub_callback',args=(subscription.pk,))exceptNoReverseMatch:raiseTypeError('callback cannot be None if there is not a reversible URL')else:callback='http://%s%s'%(config.get_default_callback_host(topic,hub),callback_path)response=self._send_request(hub,{'mode':mode,'callback':callback,'topic':topic,'verify':verify,'verify_token':token,'lease_seconds':lease_seconds,},headers,debug)info=response.info()info.status=response.codeifdebug:print'Info:\n%s\n\n'%str(info)ifinfo.statusnotin[204,202]:# 204 is sync verification# 202 is async verificationerror=response.read()raiseurllib2.URLError('error with mode "%s" to %s on %s:\n%s'% \
(mode,topic,hub,error))returnsubscriptiondefsubscribe(self,topic,**kwargs):returnself.do_action(topic,mode='subscribe',**kwargs)defunsubscribe(self,topic,**kwargs):returnself.do_action(topic,mode='unsubscribe',**kwargs)def_get_hub(self,topic):parsed=feedparser.parse(topic)forlinkinparsed.feed.links:iflink['rel']=='hub':returnlink['href']def_send_request(self,url,data,headers={},debug=False):defdata_generator():forkey,valueindata.items():key='hub.'+keyifisinstance(value,(basestring,int)):yieldkey,str(value)else:forsubvalueinvalue:yieldkey,valueencoded_data=urlencode(list(data_generator()))#headers.update({'Content-Length': (len(encoded_data) + 2)})ifdebug:print'Sending:\n%s\n%s\n%s\n\n'%(url,encoded_data,headers)req=urllib2.Request(url,encoded_data,headers=headers)returnurllib2.urlopen(req)classSubscription(models.Model):hub=models.URLField()topic=models.URLField()verified=models.BooleanField(default=False)verify_token=models.CharField(max_length=60)lease_expires=models.DateTimeField(default=datetime.now)is_subscribed=models.BooleanField(default=False)date=models.DateTimeField(default=datetime.now)updated=models.DateTimeField(default=datetime.now)objects=SubscriptionManager()classMeta:ordering=('id',)#unique_together = (('hub', 'topic'),)def__unicode__(self):ifself.verified:verified=u'verified'else:verified=u'unverified'returnu'to %s on %s: %s'%(self.topic,self.hub,verified)def__str__(self):returnstr(unicode(self))defset_expiration(self,lease_seconds,run_save=True):self.lease_expires=datetime.now()+timedelta(seconds=lease_seconds)ifrun_save:self.save()defgenerate_token(self,mode,run_save=True):assertself.pkisnotNone, \
'Subscription must be saved before generating token'token=mode[:20]+sha1('%s%i%s'%(settings.SECRET_KEY,self.pk,mode)).hexdigest()self.verify_token=tokenifrun_save:self.save()returntokendefsave(self,*args,**kwargs):ifself.id:self.updated=datetime.now()super(Subscription,self).save(*args,**kwargs)