Skip to content

Instantly share code, notes, and snippets.

@josephmisiti
Created September 15, 2014 15:16
Show Gist options
  • Select an option

  • Save josephmisiti/6a0edc5fb83a304c3d2d to your computer and use it in GitHub Desktop.

Select an option

Save josephmisiti/6a0edc5fb83a304c3d2d to your computer and use it in GitHub Desktop.
import time
import uuid
import redis
import datetime
import mongoengine as mongo
from mongoengine.django.auth import User
from socialq import log as logging
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.core import mail
from apps.constants import *
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from vendor.zebra.signals import *
from apps.accounts.mixins import StripeMixin
from apps.agencies.mixins import AgencyMixin
from apps.utils.misc_utils import pretty_date
class MSocialQUser(User, StripeMixin, AgencyMixin):
"""
@creation_date - date of creation
@modified_date - date of modification
@email - user email address
@customer_id - stripe customer id (or recurring payments)
@is_agency - is this user associated with an agency account?
@folders - folders associated with the user. they could be folders he/she created
or folders that he/she was added to
@use_add_ons - does this user have access to additional add on we charge for companies like nbc. aka
the apps/products folder
@max_folders - the maximum number of folders this user can create (admin + agency feature)
@cfolders - folders this user has created, when admin rights are swapped, this dude gets copied over
@master_acct_id - the agency account associated with this user.
@dash_otps - specific dashboard options (what questions are open, general state, etc)
they key is the dashboard id
@current_page - the current page (facebook object id) of the page being analyzed
@cnt_strgy_access - boolean specifying weather the user has access to our content strategy stuff
"""
creation_date = mongo.DateTimeField()
modified_date = mongo.DateTimeField()
username = mongo.StringField(max_length=50, required=True)
email = mongo.EmailField(max_length=50, verbose_name=_('e-mail address'))
customer_id = mongo.StringField(max_length=500,default=None)
forgot_pw_hash = mongo.StringField(max_length=50)
sent_welcome_email = mongo.BooleanField(default=False)
use_add_ons = mongo.BooleanField(default=False)
""" Agency-Related Attributes """
is_agency = mongo.BooleanField(default=False)
folders = mongo.ListField( mongo.StringField(max_length=100) )
cfolders = mongo.ListField( mongo.StringField(max_length=100) )
max_folders = mongo.IntField(default=0)
master_acct_id = mongo.StringField(default=None)
""" content strategy attributes """
cnt_strgy_access = mongo.BooleanField(default=False)
refresh_pages = mongo.BooleanField(default=False)
current_page = mongo.IntField(default=0)
current_folder = mongo.ObjectIdField()
num_pages_purchased = mongo.IntField(default=1)
blacklist = mongo.ListField(mongo.StringField())
page_token = mongo.StringField(default=None)
def to_backbone_Model(self):
results = {}
results['id'] = str(self.id)
results['email'] = self.email
results['first_name'] = self.first_name
results['last_name'] = self.last_name
results['username'] = self.username
return results
@property
def has_more_pages(self):
""" Returns true if they have available page 'credits', ie they've
paid to connect more pages than they currently have connected. """
if settings.DEBUG:
return True
from apps.products.models import MFolder
total_pages = 0
folders = MFolder.objects.filter(uid=self.id)
for folder in folders:
total_pages += folder.num_pages
if total_pages >= self.num_pages_purchased:
return False
return True
def get_user_name(self):
if self.first_name or self.last_name:
return "%s %s (%s)" % (self.first_name, self.last_name, self.username)
return self.username
def update_black_list(self, blacklist):
""" Black list is used for Social Insight Reports """
black_list_as_set = set()
for blitem in blacklist:
try:
int(blitem)
black_list_as_set.add(blitem)
except:
pass
self.blacklist = list(black_list_as_set)
self.save()
def update_customer_subscription(self, plan, prorate="True", coupon=None, token=None):
import stripe
stripe.api_key = settings.STRIPE_SECRET
if self.customer_id:
c = stripe.Customer.retrieve(self.customer_id)
else:
c = stripe.Customer.create(
card=token,
email=self.email,
description="SocialQ Purchase: %s" % plan
)
c.update_subscription(plan=plan, prorate=prorate)
def create_new_stripe_customer(self, token, plan, coupon=None):
"""
Create a new customer in stripe.
"""
import stripe
stripe.api_key = settings.STRIPE_SECRET
customer = stripe.Customer.create(
card=token,
plan=plan,
email=self.email,
coupon=coupon,
description="%s" % self.email
)
self.customer_id = customer.id
self.num_pages_purchased = SOCIALQ_NUM_PAGES_TO_STRIPE_PLANS_MAP[plan]
self.save()
return customer
@classmethod
def pre_delete(cls, sender, document, **kwargs):
""" If someone deletes a user from our system, delete the corresponding account also."""
from apps.accounts.models import MAccount
try:
MAccount.objects.get(user=document).delete()
except MAccount.DoesNotExist:
pass
@property
def preferences(self):
from apps.newsfeed.models import MPreferences
preferences, _ = MPreferences.objects.get_or_create(uid=self.id)
return preferences
@property
def account(self):
from apps.accounts.models import MAccount
return MAccount.objects.get(user=self)
def can_edit_creds(self):
"""
If you are not an agency account, you can always edit everything. If you
are an agency account AND an Administrator, you can edit stuff.
"""
if not self.is_agency:
return True
else:
return self.is_agency and self.is_staff
def set_dashboard_option(self, sid, key, value):
r = redis.Redis(connection_pool=settings.REDIS_POOL)
key = 'DOPT:%s:%s:%s' % (str(self.id), str(sid), str(key))
r.set(key,value)
def get_dashboard_option(self, sid, key):
r = redis.Redis(connection_pool=settings.REDIS_POOL)
key = 'DOPT:%s:%s:%s' % (str(self.id), str(sid), str(key))
return r.get(key)
@classmethod
def get_agency_user_models(klass, folder):
from apps.products.models import MFolder
results = []
for user_id in folder.access:
user = klass.objects.get(id=user_id)
results.append({
'id' : str(user.id),
'first_name' : user.first_name,
'last_name' : user.last_name,
'email' : user.email,
'folder_name' : folder.name,
'folder_id' : str(folder.id),
'access_level' : folder.user_access[str(user.id)],
})
return results
@classmethod
def get_content_strategy_models(klass, uid):
from apps.statistics.models import SocialInsightReport
from apps.newsfeed.models import MPost
from apps.products.models import MPage
results = []
pages = MPage.objects.filter(uid=uid)
for page in pages:
results.append({
'uid' : uid,
'page_id' : page.page_id,
'page_name' : page.name,
'token' : page.token,
'num_posts' : MPost.get_num_posts(uid=uid,facebook_page_id=int(page.page_id)),
'has_expired' : page.has_expired,
})
return results
@classmethod
def get_agency_user( klass, username ):
"""
Get the agency user
"""
logging.debug("~FM get_agency_user: username=%s" % (username))
try:
return klass.objects.get(username=username,is_agency=True)
except MSocialQUser.DoesNotExist:
return None
@classmethod
def is_username_taken(klass, username):
return klass.objects.filter(username=username).count() == 1
def get_forgot_password_url(self):
return "%s/reset/%s/" % (settings.SOCIALQ_DISTRIBUTION_URL, self.forgot_pw_hash )
def is_anonymous(self):
return self.__class__.__name__ == 'AnonymousUser'
def get_num_surveys(self):
from apps.surveys.models import Survey
return Survey.objects.filter(uid=str(self.id)).count()
def has_more_surveys(self):
from apps.surveys.models import Survey
return Survey.objects.filter(uid=str(self.id)).count() < settings.MAX_SURVEYS_FREE_ACCOUNT -1
def send_content_strategy_activation_email(self, debug_email=None):
subject = "Your Account Has Been Activated!"
html_content = """
<p>The account associated with the username/email %s/%s has been activated. Please login and view your data.</p>
""" % (self.username, self.email)
to = [self.email]
if debug_email: to = [debug_email]
message = EmailMultiAlternatives(subject,
html_content,
settings.GENERIC_EMAIL_SENDER,
to=to,
bcc=settings.BCC_EMAIL_LIST )
message.attach_alternative(html_content, 'text/html')
if settings.HAS_INTERNET_CONNECTION:
message.send()
def send_user_forgot_password(self):
unique_id = str(uuid.uuid4()).replace("-","")
self.forgot_pw_hash = unique_id
self.save()
url = self.get_forgot_password_url()
subject = 'SocialQ - Reset Password'
html_content = render_to_string('emails/email_forgot_password.html', { "url" : url, 'username' : self.username })
conn = mail.get_connection()
message = EmailMultiAlternatives(subject, html_content, settings.GENERIC_EMAIL_SENDER,
[self.email], connection=conn)
message.attach_alternative(html_content, 'text/html')
if settings.HAS_INTERNET_CONNECTION:
message.send()
logging.info("user %s forgot their password" % self.username )
def send_new_user_email( self, plan, coupon=None):
pass
def is_premium(self):
from apps.accounts.models import MAccount
return MAccount.objects.get(user=self).account_type != "meet_millie"
def account_type(self):
from apps.accounts.models import MAccount
return MAccount.objects.get(user=self).account_type
def save(self, *args, **kwargs):
if not self.creation_date:
self.creation_date = datetime.datetime.now()
self.modified_date = datetime.datetime.now()
return super(MSocialQUser, self).save(*args, **kwargs)
@classmethod
def get_admin_uid(cls):
""" Return the uid for the socialq-admin user account. """
return cls.objects.filter(username="socialq-admin").only('id').first().id
@classmethod
def get_username_from_uid(cls, uid):
try:
return cls.objects.filter(id=uid).only('username')[0].username
except:
return None
@classmethod
def create_user_if_not_exists(cls, username):
# If insightsexpress user does not exist, create him.
upsert_results = cls._get_collection().find_and_modify(
{
'_cls': cls._class_name,
'username':username,
}, {
'$setOnInsert': {'creation_date': datetime.datetime.now()},
'$unset': {'nonsense_key': 1}
}, upsert=True, full_response=True, new=True, fields=['_id'])
just_created = not upsert_results['lastErrorObject']['updatedExisting']
if just_created:
logging.debug("[MSocialQUser] User %s upserted: %s %s" % (
username,
cls._get_collection(),
upsert_results))
return upsert_results['value']['_id'], just_created
class MSocialQDemoUser(MSocialQUser):
"""
This is the demo user which can simply log in and use the dashboard.
"""
demo_access_only = mongo.BooleanField(default=True)
sid = mongo.StringField(max_length=50)
def get_survey_url(self):
from apps.surveys.models import Survey
return Survey.objects.get(id=self.sid).get_dashboard_url
class MSQEmail(mongo.Document):
"""
The email document used to store the subject and body of
the emails configured under survey settings. They emails
are created on a per survey basis.
"""
subject = mongo.StringField(max_length=120,default="")
body = mongo.StringField(max_length=500,default="")
sid = mongo.StringField(max_length=60)
user = mongo.ReferenceField(MSocialQUser, dbref=True, required=True)
def unicode(self):
return "Subject %s" % self.subject
meta = {
'collection': 'socialq_email',
'allow_inheritance': False,
}
class MSQEmailList(mongo.Document):
"""
An email list item. When a user uploads a CSV of
names, emails, etc, that information ends up getting
stored into this document. It is eventually used to
send out large numbers of emails to customers.
"""
first_name = mongo.StringField(max_length=60)
last_name = mongo.StringField(max_length=60)
sid = mongo.StringField(max_length=60)
email = mongo.StringField(max_length=60)
unique_id = mongo.StringField(max_length=60)
def __unicode__(self):
return "<%s %s> %s" % (self.first_name, self.last_name, self.email)
def url(self):
return "%s/d/%s/%s/" %(settings.SOCIALQ_DISTRIBUTION_URL,self.sid,self.unique_id)
meta = {
'collection': 'socialq_email_list',
'allow_inheritance': False,
}
"""
The account associated with a SocialQ User
"""
class MAccount(mongo.Document):
"""
The SocialQ account document, which stores all
user related account information.
"""
creation_date = mongo.DateTimeField()
modified_date = mongo.DateTimeField()
user = mongo.ReferenceField(MSocialQUser, dbref=True, required=True, unique=True)
account_type = mongo.StringField(max_length=150, default="meet_millie")
# TODO: email is unused. remove it.
email = mongo.EmailField()
name = mongo.StringField(max_length=150)
street = mongo.StringField(max_length=150)
street1 = mongo.StringField(max_length=150)
city = mongo.StringField(max_length=150)
state = mongo.StringField(max_length=150)
zip = mongo.StringField(max_length=150)
country = mongo.StringField(max_length=150)
phone = mongo.StringField(max_length=150)
cc_number = mongo.StringField(max_length=150,default=None)
cc_month = mongo.StringField(max_length=25,default=None)
cc_type = mongo.StringField(max_length=25,default=None)
cc_year = mongo.StringField(max_length=25,default=None)
@classmethod
def get_account_models(klass):
results = []
accounts = klass.objects.all()
for account in accounts:
from apps.accounts.models import MSocialQUser
try:
user = MSocialQUser.objects.get(id=account.user.id)
except MSocialQUser.DoesNotExist:
continue
if user.cnt_strgy_access:
account_type = "Content Strategy"
else:
account_type = "Agency"
results.append({
'uid' : str(user.id),
'username' : user.username,
'name' : user.get_user_name(),
'email' : user.email,
'is_active' : user.is_active,
'account_type' : account_type,
'created' : pretty_date(account.creation_date),
'num_surveys' : account.num_surveys(),
'last_login' : pretty_date(user.last_login),
'cnt_strgy_access' : user.cnt_strgy_access,
'current_page' : user.current_page,
'refresh_pages' : user.refresh_pages,
'num_pages' : user.num_pages_purchased,
})
return results
def copy(self, user):
from copy import deepcopy
account = deepcopy(self)
account.id = None
account.user = user
account.save()
def set_to_demo_account(self):
self.email = "Demo"
self.account_type = "Demo"
self.email = '[email protected]'
self.save()
def get_url(self):
return '/admin/accounts/%s/' % str(self.id)
def surveys_url(self):
return '/admin/accounts/%s/surveys/' % str(self.id)
def num_surveys(self):
from apps.surveys.models import Survey
try:
return Survey.objects.filter(uid=str(self.user.id)).count()
except:
return 0
def get_plan_expiration(self):
return (self.creation_date + datetime.timedelta(365))
def save(self, *args, **kwargs):
if not self.creation_date:
self.creation_date = datetime.datetime.now()
self.modified_date = datetime.datetime.now()
return super(MAccount, self).save(*args, **kwargs)
meta = {
'collection': 'accounts',
'allow_inheritance': False,
}
####################################################
# Stripe Webhooks
####################################################
def socialq_charge_succeeded(sender, full_json, **kwargs):
customer_id = str(full_json['data']['object']['customer'])
try:
user = MSocialQUser.objects.get(customer_id=customer_id)
user.send_folder_added_email(json_data=full_json)
logging.debug("~FM Sending Charge Succeeded Email to: %s %s" % (customer_id, user.email) )
except MSocialQUser.DoesNotExist:
logging.debug("*********** Try to send a charge successed email to %s but customer was not found" % customer_id )
def socialq_invoice_created(sender, full_json, **kwargs):
""" This email will be sent with payment confirmation (monthly, yearly)
"""
time.sleep(5)
customer_id = str(full_json['data']['object']['customer'])
try:
user = MSocialQUser.objects.get(customer_id=customer_id)
user.send_payment_confirmation_email(json_data=full_json)
logging.debug("~FM Sending Payment Confirmation Email to: %s %s" % (customer_id, user.email) )
except MSocialQUser.DoesNotExist:
logging.debug("*********** Try to send an invoice email to %s but customer was not found" % customer_id )
def socialq_customer_deleted(sender, full_json, **kwargs):
"""
This webhook handles cancelations.
"""
customer_id = str(full_json['data']['object']['subscription']['customer'])
try:
user = MSocialQUser.objects.get(customer_id=customer_id)
user.send_cancelation_email(json_data=full_json)
logging.debug("~FM Deleting Customer From Stripe: %s %s" % (customer_id, user.email) )
except MSocialQUser.DoesNotExist:
logging.debug("******Tried to delete customer %s but could not find them" % customer_id )
mongo.signals.pre_delete.connect(MSocialQUser.pre_delete, sender=MSocialQUser)
import datetime,re
from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin, UserManager)
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.core import mail
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.db import models
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from apps.constants import *
from socialq import log as logging
from djorm_pgarray.fields import ArrayField
class User(AbstractBaseUser, PermissionsMixin):
class Meta:
app_label = 'accounts'
db_table = "user"
username = models.CharField(_('username'), max_length=75, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
])
full_name = models.CharField(_('full name'), max_length=254, blank=True, help_text="Full name of the user")
email = models.EmailField(_('email address'), max_length=254, unique=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
phone = models.CharField(max_length=25, blank=True, help_text="Phone number of the user")
company = models.CharField(max_length=100, blank=True, help_text="Company of the user")
website = models.URLField(max_length=100, blank=True, help_text="Website of the user")
street_1 = models.CharField(max_length=100, blank=True, help_text="Street of the user")
street_2 = models.CharField(max_length=100, blank=True, help_text="APT/Building of the user")
city = models.CharField(max_length=100, blank=True, help_text="City of the user")
state = models.CharField(max_length=4, blank=True, help_text="State of the user")
country = models.CharField(max_length=50, blank=True, help_text="Country of the user")
zipcode = models.CharField(max_length=25, blank=True, help_text="Zipcode name of the user")
customer_id = models.CharField(max_length=50, blank=True, help_text="Stripe customer id of user")
forgot_pw_hash = models.CharField(max_length=50, blank=True, help_text="Forgot password hash for user (used in url)")
sent_welcome_email = models.BooleanField(default=False,help_text="Has sent welcome email?")
blacklist = ArrayField(dbtype="varchar(255)",help_text="A list of pages ids (facebook) troy can create to remove them from reports (SIRs) that come out of the admin panel")
page_token = models.CharField(max_length=50, blank=True, help_text="This is the facebook page token that comes for CS authentication. Since a given account has multiple admins, we are attaching them to the user object")
################
# I think all of these can be trashed
#################
folders = ArrayField(dbtype="varchar(255)",help_text="DEPCRIATED")
cfolders = ArrayField(dbtype="varchar(255)",help_text="DEPCRIATED")
max_folders = models.IntegerField(default=0,help_text="DEPCRIATED")
master_acct_id = models.CharField(max_length=50, blank=True,help_text="DEPCRIATED")
is_agency = models.BooleanField(default=False)
cnt_strgy_access = models.BooleanField(default=False)
refresh_pages = models.BooleanField(default=False)
num_pages_purchased = models.IntegerField(default=0)
mongo_id = models.CharField(max_length=50, blank=True,help_text="Mongo user id (from conversion)")
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
def get_full_name(self):
return self.full_name
def get_short_name(self):
return self.username
def __unicode__(self):
return "%s" % (self.full_name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment