Building Django SocialPost

An tool to schedule Tweets for multiple Twitter accounts, built with Django. Includes Python and shell script.

Building Django SocialPost

Perhaps you follow me and have seen particular tweets retweeted ... at times ad nauseum perhaps ... eeks

Though it's all part of my view of the internet super-highway ... with billboards ...

And that's what Django SocialPost does ... it uses the Django Framework and cronjobs to automate interactions with Twitter. And in this tutorial I'm going to walk through the first Twitter interaction, posting to Twitter.

We actually don't use Django for anything but an Object Manager, to manage the objects in our database.

Though we add a bit of a twist, not only are the tweets automated; we can also input a previous tweet we wrote, and the application will retweet that tweet.

And all of this will run on a routine, posting tweets every hour, every other hour, or at specific times during the day/night ...

So let's get started !!

Link to Github Repo

Last Things First

A quick review of what you'll need to get this application up and running ...

  1. A basic comprehension of Django
  2. A basic comprehension of Python
  3. Basic understanding of bash

I will not be covering how to get a virtual environment up and running ... though I will write another tutorial about that very soon.

Next, our application will do the following:

  1. Store Twitter Accounts and API Keys
  2. Store Tweet Topics/Categories
  3. Store individual tweets within Topics/Categories
  4. Randomly tweet on a set schedule

The reason for creating Categories is to diversify our tweet set ... I personally have only a few products that I want promote; though I don't want to be tweeting out the same tweet every-single-time ... Categories will allow me to organize this small multitude of tweets more easily.

Here's the build process:

  1. Twitter API keys
  2. Django Instance
  3. Create Models
  4. Create Shell Script

Twitter API keys

You will also need to get a set of API keys from Twitter, and this may take a bit of time ... Head on over to this website, and fill out an application.

At the end of this tutorial we'll review what to do with the keys once you have them.

Django Instance & Create Models

We've gotta get one of these up and running ...

$ django-admin startproject DjangoSocialPost

Next, we'll want to add our specific app: $ django-admin addapp SocialPost

Ok, now we need to dig into the code and create our models.py

First things first, we need to import the additional libraries/packages we'll need in this file.

Of course we have the database model for our models, and then we'll start to create our database models; we do this by defining certain characteristics. Later on we'll run a process called make migrations and migrate that will write this structure into our database.

The most frequent field type we are using in this instance is CharField, or character field. We are setting max_length=220 characters, as well as allowing the field to be left blank blank=True and the field can also be null null=True.

We also use a BooleanField to provide a True or False verdict, if the TweetUser is published or not ...

from django.db import models


class TweetUser(models.Model):
    handle = models.CharField(max_length=220, blank=True, null=True)
    fb_page = models.CharField(max_length=64, blank=True, null=True)
    token = models.CharField(max_length=220, blank=True, null=True)
    token_key = models.CharField(max_length=220, blank=True, null=True)
    secret = models.CharField(max_length=220, blank=True, null=True)
    secret_key = models.CharField(max_length=220, blank=True, null=True)
    published = models.BooleanField(default=False)


    class Meta:
        verbose_name_plural = "Tweet Users"

    def __str__(self):
        return "%s" % self.handle
    
    def __unicode__(self):
        return "%s" % self.name

We have just created the outline for the Tweet User, and are including the Twitter handle, token, token_key, secret and secret_key in their database entry. This way we can manage multiple accounts, through our single application.

Quick note about the Meta section.

    class Meta:
        verbose_name_plural = "Tweet Users"

    def __str__(self):
        return "%s" % self.handle
    
    def __unicode__(self):
        return "%s" % self.name

Next we need to make a database model for our Tweet Topics.

I've added an arguably unnecessary feature here; or rather I've chosen to leave this feature in to illustrate the example.

Though the first characteristic we're adding to our database model is the domain using models.ForeignKey where we reference the database model that we just created. Again we're allowing this trait to be either blank or null; though we do have an additional setting on_delete=models.PROTECT.

Because this object is attached to another object, we need to answer what happens when the object is deleted? In this instance we are making sure to protect the connected object.

Next we add the name field followed by a category field which utilizes another attribute called choices. Here we are able to both minimize the amount of data in our database, while retaining the use of human readable names to categorize our TweetTopics.

Perhaps I have multiple albums that I want to tweet out ... Rather than placing them all into the same topic; each album can have it's own topic, and they all be unified under the category of music.

Lastly we use a TextField to provide a bit more writing room to describe any additional notes or details about this TweetTopic.

class TweetTopic(models.Model):
    CATEGORIES = (
            ('M', 'Music'),
            ('P', 'Promotions'),
            ('E', 'Educational'),
            ('B', 'Blog'),
            )

    domain = models.ForeignKey('TweetUser', blank=True, null=True, on_delete=models.PROTECT)
    name = models.CharField(max_length=32, blank=True, null=True)
    category = models.CharField(max_length=2, choices=CATEGORIES)
    description = models.TextField(max_length=220, blank=True, null=True)
    published = models.BooleanField(default=False)

    class Meta:
        verbose_name_plural = "Tweet Links"

    def __str__(self):
        return "%s" % self.name

    def __unicode__(self):
        return "%s" % self.name

Finally we need to create the object model for our actual tweets; and we'll call them TweetCopy. Each object will contain its copy_text, a connection to it's topic, as well as two BooleanField traits, running and published.

This is so that after a tweet has been tweeted, it will not be immediately tweeted in the next hour ... it can remain published but will not be running. In our shell script, later, we'll create a function to amend this if none of our Tweets are running.

Lastly, we'll add a few traits to grab details about our tweets, collecting their number of likes and retweets / rtwts.

class TweetCopy(models.Model):
    copy_text = models.TextField(max_length=280, blank=True, null=True)
    topic = models.ForeignKey('TweetTopic', blank=True, null=True, on_delete=models.PROTECT)
    published = models.BooleanField(default=False)
    running = models.BooleanField(default=False)
    twt_id = models.CharField('Tweet ID', max_length=64, blank=True, null=True, unique=True)
    likes = models.IntegerField('Likes', blank=True, null=True)
    rtwts = models.IntegerField('Retweets', blank=True, null=True)
    

    class Meta:
        verbose_name_plural = "Tweet Copy"

    def __str__(self):
        return "%s" % self.id

Conclusion ...

Checkout the Admin & Add Tweets

Here it is y'all, and we'll almost ready to get this thing postin'!!

Take a look at our initial options ... Topics & Tweets ...

Write the Shell Script

We need to activate our django application and then we need to run a python script to run our application and post our tweets ...

Import our packages

import sys
import os
import django

from datetime import datetime, timedelta
from pprint import pprint as ppr

from time import sleep
import random

sys.path.append('/var/www/df.org/server/')
os.environ['DJANGO_SETTINGS_MODULE'] = 'server.settings'
django.setup()

from dfsocialpost.models import *

import tweepy
from pprint import pprint as ppr

Write a few functions ...

def renewCopies():
    all_db_copies = TweetCopy.objects.filter(link__domain=t_user.id,  published=True, running=False,)
    if len(all_db_copies) > 0:   
        for adc in all_db_copies:
            adc.running = True
            adc.save()
        return True
    else:
        return False

Get Random Copy

def get_random_copy():
    all_db_copies = TweetCopy.objects.filter(link__domain=t_user.id,  published=True,)
    if not len(all_db_copies) > 0:
        w = renewCopies()
        if w == True:
            all_db_copies = TweetCopy.objects.filter(link__domain=t_user.id,  published=True,)
            tweet = random.sample(set(all_db_copies), 1)
        elif w == False:
            tweet = None
    else:
        all_db_copies = TweetCopy.objects.filter(link__domain=t_user.id,  published=True,)
        tweet = random.sample(set(all_db_copies), 1)
    return tweet

Get Copy & Text Tweet

def getCopies():
    tweet, category = get_random_copy()
    return tweet


# Tweet Text
def tweetText(tweetCopy):
    if tweetCopy.link.link:
        finalText = tweetCopy.copy_text + ' ' + tweetCopy.link.link
    else:
        finalText = tweetCopy.copy_text
    return finalText

Load up the User and Tweets

tname = sys.argv[1]
t_user = TweetUser.objects.filter(handle=tname)[0]
# print(t_user)

auth = tweepy.OAuthHandler(t_user.token, t_user.token_key)
auth.set_access_token(t_user.secret, t_user.secret_key)
api = tweepy.API(auth)

try:
    tweet_copy = getCopies()
    if tweet_copy == None:
        quit()
except:
    quit()

Let's tweet a Tweet

tweet_copy = tweet_copy[0]

if tweet_copy.twt_id != None:
    try:
        api.retweet(int(tweet_copy.twt_id))
    except:
        api.unretweet(tweet_copy.twt_id)
        # Get Likes & Retweets Here
        ntwt = api.get_status(tweet_copy.twt_id)
        tweet_copy.likes = ntwt.favorite_count
        tweet_copy.rtwts = ntwt.retweet_count
        tweet_copy.save()
        sleep(random.randint(135,330))
        api.retweet(tweet_copy.twt_id)
else:
    try:
        tc = tweetText(tweet_copy)
        print(tc)
        tweet = api.update_status(tc)
        tweet_copy.twt_id = tweet.id
        tweet_copy.running = False
        tweet_copy.save()
    except Exception as e:
        tweet_copy.copy_text = 'DNW ' + tweet_copy.copy_text[:3] + e
        tweet_copy.published = False
        tweet_copy.save()

Write the shell script to run it in cron ...

cd /var/www/df.org
source bin/activate

python /var/www/website/djangoproject/app/tweet.py cultureclap

Attach this to a cronjob

Cronjobs are scheduled tasks, that we can schedule !!

# Edit this file to introduce tasks to be run by cron.
# 
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command

37 * * * * /bin/bash /var/www/website/djangoproject/app/tweet.sh > /home/canin/logs/df.socialpost.log 2>&1

Stretch Goals

  1. Grab Faved Tweets for Review
  2. Grab BookMarks for archive
  3. UI to Review Bookmarks & Faves
  4. Forward Tweets to Ghost for Articles

Can this same sometime soon ? Or what's going on ?