Category: Coding

Home / Category: Coding

A minimal WordPress Theme

July 20, 2019 | Coding | No Comments

WordPress Logo

The company I work for is growing, and part of that growth has me learning WordPress. Id honestly rather not touch it, I do not enjoy working in PHP. But there it is anyway.

I was quickly surprised at how simple, and how little you need to make a theme. You only need two files: index.php and style.css. So I decided to make one that was that simple to demonstrate here.

Shortly into it I decided to add a menu and a sidebar, so two more files: functions.php and sidebar.php. How functional is a theme really, if you don’t have a customizable menu or widget area?

All the code is below, comments inline and github repo is available.

style.css

/*
Theme Name: Minimal WordPress
Theme URI: https://wordpress.org/themes/twentyseventeen/
Author: the WordPress team
Author URI: https://wordpress.org/
Description: Minimal WordPress brings your site to life with immersive featured images and subtle animations. With a focus on business sites, it features multiple sections on the front page as well as widgets, navigation and social menus, a logo, and more. Personalize its asymmetrical grid with a custom color scheme and showcase your multimedia content with post formats. Our default theme for 2017 works great in many languages, for any abilities, and on any device.
Version: 1.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: minimaltheme
Tags: one-column, two-columns, right-sidebar, flexible-header, accessibility-ready, custom-colors, custom-header, custom-menu, custom-logo, editor-style, featured-images, footer-widgets, post-formats, rtl-language-support, sticky-post, theme-options, threaded-comments, translation-ready
This theme, like WordPress, is licensed under the GPL.
Use it to make something cool, have fun, and share what you've learned with others.
*/

index.php

<?php
/*
Everything is going to be displayed on this one page,
we don't want to put potentially thousands of posts on 
our one page... so we redefine the query to paginate.
But only if we are not on a single item page.
*/

if (!is_singular()) {
    $paged = (get_query_var('paged')) ? get_query_var('paged') : '1';
    $args = array(
        'nopaging'               => false,
        'paged'                  => $paged,
        'posts_per_page'         => '2',
        'post_type'              => 'post',
    );
    $query = new WP_Query($args);
} else {
    $query = $wp_query;
}


?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My Theme</title>
    <!-- wp_head will inject all of wordpress's specific header info
    and anything else we define, like custome css.  We could just write that
    in here ourselves, but we could end up with mulitple headers and this
    helps keep us DRY
    -->
    <?php wp_head() ?>
</head>

<body>
    <nav>
        <div class="nav-wrapper  container">
            <a href="/" class="brand-logo"><?php echo get_bloginfo('name'); ?></a>
            <?php
            wp_nav_menu(array(
                'theme_location' => 'main-menu',
                'container_class' => 'right'
            ));
            ?>
        </div>
    </nav>
    <main class="container">
        <div class="row">
            <div class="col s12 m8">
                <?php if ($query->have_posts()) : while ($query->have_posts()) : $query->the_post(); ?>
                        <article class="card-panel">
                            <h3>
                                <a href="<?php the_permalink(); ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><?php the_title(); ?></a>
                            </h3>

                            <small><?php the_time('F jS, Y'); ?> by <?php the_author(); ?></small>

                            <div class="entry">
                                <!-- we only want to show the full post if we aren't on a list -->
                                <?php if (is_singular()) {
                                    the_content();
                                    // If comments are open or we have at least one comment, 
                                    // load up the comment template.
                                    if (comments_open() || get_comments_number()) {
                                        comments_template();
                                    }
                                } else {
                                    the_excerpt();
                                } ?>
                            </div>
                            <p class="postmetadata"><?php _e('Posted in'); ?> <?php the_category(', '); ?></p>

                        </article>

                    <?php endwhile;
                    // show pagination links
                    echo paginate_links(array(
                        'total' => $query->max_num_pages,
                        'mid_size' => 2
                    ));

                else : ?>
                    <p><?php esc_html_e('Sorry, no posts matched your criteria.'); ?></p>
                <?php endif; ?>
            </div>
            <div class="col s12 m4">
                <?php get_sidebar() ?>
            </div>
        </div>
    </main>


    <footer>
        <!-- like wp_head wp_footer injects necessary script stuff
            such as the code that gives us the WordPress Admin bar
         -->
        <?php wp_footer(); ?>
    </footer>
</body>

</html>

functions.php

<?php
/* 
You can register your stylesheets and scripts in the same function
but I prefer to keep them seperate, it just feels cleaner to me
*/
function minimaltheme_enqueue_styles()
{
    wp_enqueue_style('materialize-css', "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css");
}
add_action('wp_enqueue_scripts', 'minimaltheme_enqueue_styles');

function minimaltheme_enqueue_scripts()
{
    wp_enqueue_script('materialize-js', "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js");
}
add_action('wp_enqueue_scripts', 'minimaltheme_enqueue_scripts');
wp_enqueue_style('style', get_stylesheet_uri());


/*
  We need one menu for our nav bar, but you can register
  as many as you like and put them anywhere
*/
function register_menus()
{
    register_nav_menu('main-menu', __('Main Menu'));
}
add_action('init', 'register_menus');


// register a sidebar to user throughout the site
add_action('widgets_init', 'minimaltheme_register_sidebars');
function minimaltheme_register_sidebars()
{
    register_sidebar(array(
        'name' => __('Main Sidebar', 'minimaltheme'),
        'id' => 'sidebar',
        'description' => __('Widgets in this area will be shown on all posts and pages.', 'minimaltheme'),
        'before_widget' => '<div class="card"><div class="card-content">',
        'after_widget'  => '</div></div>',
        'before_title'  => '<div class="card-title">',
        'after_title'   => '</div>',
    ));
}

sidebar.php

<?php
if (!is_active_sidebar('sidebar')) {
    return;
}

dynamic_sidebar('sidebar');

Available at https://github.com/wsimmerson/minimaltheme

PipEnv

July 10, 2019 | Coding | No Comments

python programming language

Here I am, apparently late to the party again. Set in my ways, using the same old — trustworthy — tools and along comes another one, seemingly better.

Pipenv will eventually be included in pip itself, but for now we have to pip install it. Hopefully you’re using Python 3, because that’s where you need to get it, although once you have it you can use it to create Python 2 environments.

Pipenv gets rid of the necessity for a requirements.txt file — but can both use and generate them — instead using two files a Pipfile and Pipfile.lock. The pipfile to me, is very reminiscent of a npm package.json while the Pipfile.lock “locks” the specific package versions, dependencies and hashs.

Instead of creating the virtual env specifically pipenv creates one in your home directory based on the current folder name. You can get around this by creating a .venv directory in your current folder, which pipenv will prioritize.

C:\code\myproject>pipenv shell
Creating a virtualenv for this project…
Pipfile: C:\code\myproject\Pipfile                                                                                      Using c:\users\wayne\appdata\local\programs\python\python37-32\python.exe (3.7.0) to create virtualenv…
[ ===] Creating virtual environment...Already using interpreter 
....
Installing setuptools, pip, wheel...
done.

Successfully created virtual environment!                                                                               Virtualenv location: C:\Users\Wayne\.virtualenvs\myproject--MmAU6cW
Creating a Pipfile for this project…                                                                                    Launching subshell in virtual environment…

You will see that a .virtualenvs with the current folder name was created in my home directory. But if I create a .venv directory and run pipenv shell again…

C:\code\myproject>pipenv shell
Creating a virtualenv for this project…
Pipfile: C:\code\myproject\Pipfile                                                                                      Using c:\users\wayne\appdata\local\programs\python\python37-32\python.exe (3.7.0) to create virtualenv…
[ ===] Creating virtual environment...Already using interpreter 
...
Installing setuptools, pip, wheel...
done.

Successfully created virtual environment!                                                                               Virtualenv location: C:\Users\Wayne\.virtualenvs\myproject--MmAU6cW
Creating a Pipfile for this project…                                                                                    Launching subshell in virtual environment…

Now we have a new one in the .venv

We can also specify a specific python version by pipenv –python=version.number

We now have a Pipfile in our project directory that looks like:

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]

[requires]
python_version = "3.7"

Notice the seperation of dev-packages and packages. We can now deploy our projects and not include test runners that found their way into requirements.txt in production. Lets install something.

$ pipenv install flask

Pipenv has been updated to:

[packages]
flask = “*”

We also now have a Pipfile.lock file that looks like:

{
    "_meta": {
        "hash": {
            "sha256": "a82b674d67d29678775ff6b773de1686a9593749ec14483b0d8e05131b662286"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.7"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "click": {
            "hashes": [
                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
                "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
            ],
            "version": "==7.0"
        },
        "flask": {
            "hashes": [
                "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
                "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
            ],
            "index": "pypi",
            "version": "==1.1.1"
        },
        "itsdangerous": {
            "hashes": [
                "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
                "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
            ],
            "version": "==1.1.0"
        },
        "jinja2": {
            "hashes": [
                "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
                "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
            ],
            "version": "==2.10.1"
        },
        "markupsafe": {
            "hashes": [
                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
            ],
            "version": "==1.1.1"
        },
        "werkzeug": {
            "hashes": [
                "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c",
                "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6"
            ],
            "version": "==0.15.4"
        }
    },
    "develop": {}
}

This file includes the specific version of flask and all of its dependencies. This file will be updated every time we install, upgrade or remove a package.

This would be a incredibly long post if I continued to post demonstrations for everything this can do, so below is a descriptive cheat sheet that should be self explanatory.

Cheat Sheet

Activate the shell
pipenv shell

Initialize environment with a specific python version
pipenv –python=2.7.3

Install from Pipfiles
pipenv install

Install in production
pipenv install –deploy

Install a development dependancy
pipenv install nose –dev

Install all packages specified in Pipfile.lock
pipenv sync

Install from a requirements.txt file
pipenv install -r ./requirements.txt

Check for known security issues in your installed packages
pipenv check

View a graph of all your dependencies
pipenv graph

Update the Lock file before deploying
pipenv lock

Ignore the pip file
pipenv install –ignore-pipfile

Installs all packages specified in Pipfile.lock
pipenv –rm

Show the path to the virtual env
pipenv –venv

Create a requirements.txt file (if your team refuses to change to pipenv)
pipenv run pip freeze > requirements.txt

Deactivate the shell
exit

PyCharm Save Actions

July 9, 2019 | Coding | No Comments

programming code

I think I’m late to the party on this, but wanted to throw out my gleeful support of this plugin. Automatically cleanup and/or format your code when you save a file, or run against several files.

This should be part of PyCharm itself and not just a plugin, its that useful. https://plugins.jetbrains.com/plugin/7642-save-actions

If you use PyCharm on a daily basis like me, just do yourself the favor and install it.

TwitterBot!

July 9, 2019 | Coding | No Comments

twitter

I’ve been a bit bored, haven’t been able to come up with a new personal side project for a while. Last night I realized I hadn’t done any browser automation stuff, and what better way of learning it than building a Twitter bot?

Caveat: This is probably a violation of Twitters terms of service so use for educational purposes only!

So what I’ve done is taken Python and with Selenium automated the process of signing into Twitter, searching a specific hashtag, get a list of tweets and like them. All this interspersed with some short, random weight times to mimic more human like behavior.

The repo can be found at https://github.com/wsimmerson/twitterbot but it is only two files, the bot itself and a config file with variables such as username and password, excluded from git of course.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import random
import time
import sys

import config


class TwitterBot():
    def __init__(self, username, password):
        self.username = username
        self.password = password

        try:
            self.hashtag = config.HASHTAG
        except:
            print("config.HASHTAG must be defined")
            sys.exit(1)

        try:
            self.pages_to_scroll = config.PAGES
        except:
            self.pages_to_scroll = 10

        self.bot = webdriver.Chrome()

    def activate(self):
        print("running...")
        self.bot.get("https://twitter.com/login")
        time.sleep(3)

        usernameBox = self.bot.find_element_by_class_name("js-username-field")
        usernameBox.clear()
        usernameBox.send_keys(self.username)

        passwordBox = self.bot.find_element_by_class_name(
            "js-password-field")
        passwordBox.clear()
        passwordBox.send_keys(self.password)
        passwordBox.submit()

        time.sleep(15)
        self.bot.get(
            "https://twitter.com/search?f=tweets&vertical=default&q=%23{}&src=tyah".format(self.hashtag))

        for x in range(1, self.pages_to_scroll):
            self.bot.execute_script(
                "window.scrollTo(0, document.body.scrollHeight);")
            # for more humanesque scrolling behavior
            time.sleep(random.randrange(10, 30))

        links = []
        for tweet in self.bot.find_elements_by_class_name("tweet"):
            links.append(tweet.get_attribute("data-permalink-path"))

        for link in links:
            self.bot.get("https://twitter.com/{}".format(link))
            favBtn = self.bot.find_element_by_class_name("js-actionFavorite")
            try:
                favBtn.click()
            except Exception as e:
                # sometimes the clicks fail, not sure why yet.
                print("Error: ", e)
            # How long does it take for a human to read a tweet?
            time.sleep(random.randrange(20, 60))


if __name__ == '__main__':
    bot = TwitterBot(config.USERNAME, config.PASSWORD)
    bot.activate()

The config.py file requires:
USERNAME=
PASSWORD=
HASHTAG= (don’t actually add the #, its in the search string by default)
and an optional
PAGES= (this defaults to 10 if not present; determines how many tweets you will find on the page)

It took me about an hour or so to get this working and I may extend it out further. I.E. retweet, if the number of likes is over a specific threshold or exclude tweets that contain certain words, etc. Otherwise, this was a very short project and my quest to find something better continues.

Convert strings to HEX Colors

July 4, 2019 | Coding | No Comments

programming code

I’ve been working with charts.js and data being generated from the python backed of the API. Since the data is changing all the time, and the nature of the different queries, I never know how many data sets will be generated to plot.

This makes it difficult to define a specific set of colors to go with the unknown quantity of labels. At first I wrote a function to generate a random color code, but then, on every page reload, everything is a different color.

This of course, makes the experience rough and kinda annoying the longer you’re on the page, pulling different queries. A different approach was in order.

The individual data sets have unique names, which get used for labels. So I thought, why not try to use those names to create hex color codes? Well it turns out its really simple to do.

def convertToHexColor(word):
    word = "AB" + word.upper()
    buff = ""
    for c in word:
        buff += "{}".format(ord(c))
    word = "000000" + hex(int(buff)).lstrip("0x")

    return "#" + word[-6:]

That’s it. It’s small, but I created a github repo for it. If you need it, enjoy.
https://github.com/wsimmerson/convertToHexColor

programming code

A while back I had to write an app to parse data from an old billing system to be used with a new banks credit card processing system via batch upload. Most of what I do is either on Linux systems and only need to be run directly by me, or are web applications of one form or another, so a standalone application for our billing department was something really new — well not really new, but something I haven’t done in a very, very long time and not with web technologies — and exciting.

At the time I was completely obsessed with Angular and wanted to use it for everything, so of course, I chose it to write the app and after some research, decided on ElectronJS to do the stand alone part of it.

Today we got notice from the bank that the format was going to be changing due to changes in how credit cards have to be handled, so I opened up the code to take a look and refresh my memory only to find that I had completely forgotten how everything worked with Electron. So I fired up a new Angular app and reverse engineered my own code, to record here for posterity.

Start an Angular project and install Electron.

$ ng new electron-app
$ cd electron-app
$ npm install --save electron
$ npm install -D electron-packager

Electron’s main configuration.

// main.js

const { app, BrowserWindow } = require('electron');

let win;

function createWindow () {
  // Create the browser window.
  win = new BrowserWindow({
    width: 900, 
    height: 900,
    backgroundColor: '#ffffff',
    icon: `file://${__dirname}/dist/electron-app/assets/logo.png`
  });

  win.loadURL(`file://${__dirname}/dist/electron-app/index.html`);

  // uncomment below to open the DevTools.
  // win.webContents.openDevTools()

  // Event when the window is closed.
  win.on('closed', function () {
    win = null;
  });
}

// Create window on electron initialization
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On macOS specific close process
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', function () {
  // macOS specific close process
  if (win === null) {
    createWindow();
  }
});

Update files to use Electron

// package.json

{
  "name": "electron-app",
  "version": "0.1.0",
  "main": "main.js", // <-- ADD THIS
// package.json
  "scripts": {
    ....
    "electron": "electron .",                       // <-- ADD THIS
    "package": "electron-packager .",               // <-- ADD THIS
<!-- index.hmtl -->
<base href="./">

Build and run the application.

$ ng build
$ npm run electron

Build the final, distributable package.

$ ng build --production
$ npm run package

This will create a directory name like electron-app-win32-x64 (or similar depending on your platform), containing all the necessary files to run and an electron-app.exe

Django: Extending the User Model

June 22, 2019 | Coding | No Comments

programming code

I love Django. It’s a wonderful framework and ORM. Whether you’re building a full website or a restful api, its got you covered. But if you are like me, you HATE the default user model. This generally comes down to it having both a username & e-mail field; and the e-mail field isn’t even unique.

In my world, the e-mail address is the perfect, unique username. So how do you make this happen?

If your app is out, live in the world all ready, you’re out of luck. But if you are firing up a new one, there are a couple of options. Just DON’T RUN THE MIGRATIONS until the new user model is ready.

First thing after starting the new project is starting the users app.

(ENV) $: python manage.py startapp users

Next we are going to create our new manager and user model in users. DO NOT RUN MIGRATIONS! Did you get that? I’ll tell you when.

#users/managers.py

from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _


class UserManager(BaseUserManager):
    """
    Our custom user model that uses email as the unique username
    """
    def create_user(self, email, password, **extra_fields):
        """
        Create user.
        """
        if not email:
            raise ValueError(_('The Email must be set'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create superuser.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(_('Superuser must have is_staff=True.'))
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(_('Superuser must have is_superuser=True.'))
        return self.create_user(email, password, **extra_fields)

When we create our user model, we are going to extend AbstractUser. We can extend BaseAbstractUser as well, but that is a lot more work and I really don’t see a need to ever do that at all.

# users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _

from .managers import UserManager

class User(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = UserManager()

    def __str__(self):
        return self.email

Notice the username = None and USERNAME_FIELD = ’email’. We can also add any other fields we want here now. Instead of adding a separate 1 to 1 profile model, we can put things right on the user. Such as phone number, or company name, customer id #, etc.

Now we can add it to our settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'users',
]

AUTH_USER_MODEL = 'users.User'

You can go ahead and create and run the migrations now and run python manage.py createsuperuser to set yourself up.

Now we need Forms and Admin integration.

# users/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import User

class UserCreationForm(UserCreationForm):
    class Meta(UserCreationForm):
        model = User
        fields = ('email',)

class UserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = ('email',)
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import UserCreationForm, UserChangeForm
from .models import User

class UserAdmin(UserAdmin):
    add_form = UserCreationForm
    form = UserChangeForm
    model = User
    list_display = ('email', 'is_staff', 'is_active',)
    list_filter = ('email', 'is_staff', 'is_active',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_staff', 'is_active')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)


admin.site.register(User, UserAdmin)

That’s it. You have your own customized user model that you can extend further at will.

Referencing the User model:

There are two ways to get the user model now. You can’t simply import AbstractUser anymore. The preferred method is to use get_user_model() but you can also use settings.AUTH_USER_MODEL. For instance, if you want a foreign key relationship to the user.

# Example
User = get_user_model()
## or User = settings.AUTH_USER_MODEL ## but get_user_model() is preferred

class OurTestModel(models.Model):
     customer = models.ForeignKey(User, on_delete=models.CASCADE)

Edit:

To make this a simpler process I’ve created a GitHub repo that you can drop into your Django project and get started right away.
https://github.com/wsimmerson/DjangoExtendedUser

Django: Unique Slug for Urls

June 21, 2019 | Coding | No Comments

programming code

Let’s imagine a situation where you want to create, lets say, a blog post or a news article in Django. You want to use take the Title and turn it into a slug that can be used in the URL. Meaning, it needs to be unique, or at least, unique for the date. Depending on the URL parameters you with you use.

You could make it a editable field with a unique property, but its probably better to abstract it away and check recursively for existing matches and increment a number on the end. Simplifying the process.

First, lets define the model as such.

class Article(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(unique=True, blank=True)
    body = models.TextField()
    published = models.BooleanField(default=True)
    published_on = models.DateTimeField(blank=True)
    updated_on = models.DateTimeField(blank=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

Next we will need to override the models save method.

def save(self, *args, **kwargs):
        self.updated_on = timezone.now()
        if not self.id:
            self.published_on = timezone.now()

            # find unique slug
            cnt = 0
            while not self.slug:
                if cnt > 0:
                    # Create a new slug string with the counter
                    slug = "{}-{}".format(slugify(self.title), cnt)
                else:
                    slug = slugify(self.title)

                # check to see if a article exists with that slug
                a = Article.objects.filter(slug=slug)
                if not a:
                    # Assign the slug, this will end the loop.
                    self.slug = slug
                else:
                    cnt += 1
        super(Article, self).save(*args, **kwargs)

You could easily abstract that loop out into a function.

Python 3: Send SMTP Authenticated Email

February 18, 2014 | Coding | No Comments

programming code

Write enough shell scripts and eventually you will have to write one that sends a email report, in fact now a day’s I find that most shell scripts I write (the scheduled ones anyway) require some kind of notification after they have run. Likewise, if you set up enough mail servers — usually one is enough — you will need a quick easy way to test SMTP.

Below is a python 3 script with smtplib and argparse, which sends a test email using smtp authentication.  It’s short, to the point, and I hope pretty self explanatory for anyone needing to send authenticated email via python.

#!/usr/bin/env python3

import smtplib
import argparse

def testmail(username, password, server, deliveryaddr):
    header = "From: %s\r\nTo: %s\r\nSubject: SMTP Test\r\n\r\nTest Successful" % (username, deliveryaddr)
    
    try:
        server = smtplib.SMTP(server)
        server.login(username, password)
        server.sendmail(username, deliveryaddr, header)
    except Exception as e:
        print("An Error Occurred! %s" % e)
    else:
        print("Test Successful")

if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--user', required=True,
            help="Username for SMTP Login")
    parser.add_argument('-p', '--password', required=True,
            help="Password for SMTP Login")
    parser.add_argument('-s', '--server', required=True,
            help="SMTP Server url")
    parser.add_argument('-d', '--dest', required=True,
            help='Destination Email Address')

    args = parser.parse_args()

    testmail(args.user, args.password, args.server, args.dest)

Note: This post was recovered from an old, now defunct, blog.

Learn Python by Building an IRC Logging Bot!

February 6, 2014 | Coding | No Comments

programming code

When I first sat down to learn Python, I was instantly disappointed with all the the available resources, and the fact that almost none of the tutorials I found seemed to work.  In fact I got so frustrated with the language that I shelved the language and went to Ruby for several months.  It wasn’t until I paid a visit to the Windsor Hackforge and met 3 Python fanatics that I decided to take another look.

What I found during my second look at Python was much different, now knowing a couple avid Python programmers I had people who could answer questions and my problems began to become clear.  The source of most of my frustration was caused by the incompatibilities between Python 2 and 3.  I always want to keep my software up to date so it meant using Python 3, but pretty much every example, tutorial, and book I looked at was written in Python 2.

Its incredibly frustrating when you cant even print text to the console!

The other side of the frustration was the boring tutorials,  I mean how many different ways can you generate or manipulate the Fibonacci sequence anyway?  Sure they were full of good information, but when you’ve learned several languages over the years all these tutorials are just a repetition.  I needed something different, interesting, and potentially useful to spend my time working on.

So I set my sights on writing a IRC Bot — How 1995 is that? — but it was fun.  It dawned on me after that, wouldn’t it be great if programming tutorials actually walked you through writing something useful or entertaining? I mean, its one thing to learn that “This is how you declare a variable” or “This is how you write a loop” and completely another thing to see how to implement them in the structure of the program.

I’ve gone back and re-written my original bot, took a lot of it out and tried to smooth out some of the rough edges, and updated it so it runs properly in Python 3.  Without further Ado…

How to write a IRC Logging Bot in Python 3

First things first… lets skip those.  I’ll assume you have Python 3 already installed on your system and know how to use a text editor to create your script.  This is a small program and fits nicely in a single file, so open up your editor of choice and follow along.

At the top of our file we are going to put in our shebang line and import a few packages.

#!/usr/bin/env python3

import sys
import socket
import string
import argparse 

Now in order to handle an instance of an ircbot, I’m going to create an object.  If your not familiar with OOP (Object Oriented Programming) this may be a bit tricky for you, but Python makes this extremely simple.

We define a new class with the class keyword and the class name as such:

class ircbot():

Make a note of that colon. Everything will need to be indented based on it after this.  The next thing we are going to do is create a constructor, which will populate some variables and establish the connection to the IRC server.

 class ircbot():
    
        def __init__(self, host, port, channel, name, log):
                self.HOST = host
                self.PORT = port
                self.CHANNEL = channel
                self.NICK = name
                self.IDENT = name
                self.REALNAME = name
                self.LOG = log 

The def __init__ you are seeing is a function which is automatically called when the object is created (we will do that later), make note of the “self” in the definition.  Its needed by all class functions and global variables, in order for other functions to have access to them.

This isn’t the full constructor either, the next thing we are going to put in is the connection to the server.  And just so that we don’t get any messy errors thrown at us, I’m also going to introduce exception handling via try-except.  This isn’t in depth, but serves as a good example for your future programs.

   def __init__(self, host, port, channel, name, log):
                # Everything above, after self is a variable we are going
                # to expect every time a instance of ircbot is created
                self.HOST = host
                self.PORT = port
                self.CHANNEL = channel
                self.NICK = name
                self.IDENT = name
                self.REALNAME = name
                self.LOG = log

                try:
                        # Any exception thrown here will halt the program, jumping
                        # down to the except block below
                        self.irc = socket.socket()
                        self.irc.connect((self.HOST, self.PORT))

                        # If you have played with Python 2 you will notice a difference
                        # in the below print statement, print is now a function
                        print("Connected to %s\n" % self.HOST)

                        self.irc.send(("NICK %s\r\n" % self.NICK).encode())

                        self.irc.send(("USER %s %s bla :%s\r\n" % (self.IDENT,
                                                                   self.HOST,
                                                                   self.REALNAME)).encode())
                        self.irc.send(("JOIN %s\r\n" % self.CHANNEL).encode())
                        print("Joined %s as %s\n" % (self.CHANNEL, self.NICK))
                except Exception as e:
                        # Exception is generic all built-in, non-system-exiting 
                        # exceptions are derived from this class.
                        # which makes it a simple way to catch whatever and print a 
                        # clean message and exit with a system 
                        # error code
                        print(e)
                        sys.exit(1)

There, if we created a instance of ircbot, with the correct info in host, port, channel, name and log it would connect to the irc server.  It won’t do us much good through, its not reading responses or handling the PING/PONG that the IRC server will send to see if we are still connected and it certainly isn’t going to write all the activity to a log file for us either.  Lets finish this off the ircbot class by creating a “run” function,  it doesn’t have to be run.  In fact it doesn’t even have to be a separate function, we could just as easily write this into the __init__, but I don’t like to put to much into the constructor if I don’t have to.

   def run(self):
                """
                        Main Application Loop
                """

                # creating a tring variable to  dump irc data into
                readbuffer = ""

                # Everything the program does is going to happen here
                # an infinit loop of catching data and parsing it
                while True:
                        readbuffer = readbuffer + self.irc.recv(1024).decode()

                        # We don't need everything the server sends to us
                        # so we split it into a list and then drop the first part
                        temp = readbuffer.split("\n")
                        readbuffer = temp.pop()

                        # Now we are going to step through the rest of the
                        # lines in the list
                        for line in temp:
                                # Here I am going to strip off line endings
                                # and split the string into a list using
                                # white space as the separator. It's not
                                # necessary, but useful for parsing commands
                                # if you grow the bot that way
                                linex = line.rstrip()
                                linex = linex.split()

                                # PING PONG!
                                # When the IRC Server sends us a ping we
                                # best respond, or it will drop us
                                if (linex[0] == "PING"):
                                        self.irc.send(bytes("PONG %s\r\n" % linex[1]))
                                else:
                                        # And here we handle printing to the screen
                                        # or to a file
                                        if self.LOG == 'stdout':
                                                print(line) 
                                        else:
                                                with open(self.LOG, "a") as log:
                                                        log.write(line)

Now the IRC Logger Bot is complete.  You could import this and use it in another program, but lets make it runnable as one file.  I’m going to add another function that will use the argparse module to give us some Command line power, as well as give some default settings.

def parseargs():

        parser = argparse.ArgumentParser()
        parser.add_argument('-s', '--server', default='irc.freenode.net',
                             help='DNS address of the IRC server. 
                            default=irc.freenode.net')
        parser.add_argument('-p', '--port', type=int, default=6667,
                            help='port number of irc server.default=6667')
        parser.add_argument('-c', '--channel', default='#python-unregistered',
                                help='IRC channel to join. default=#python-unregistered')
        parser.add_argument('-n', '--name', default='irc_logger_bot',
                                help='how the bot will be identified in the channel. default=irc_logger_bot')
        parser.add_argument('-o', '--output', default='stdout',
                                help='file to write log to. default=stdout')

Most of these parser lines are the same, but take an extra look at the -p/–port option. It has one more configuration flag than the others.  I’m sure you can figure that out on your own, Python makes coding easy.

Once we add our final lines to this project, you will be able to run this script with a -h or –help and get a shiny helpful output like:

usage: tutorial_bot.py [-h] [-s SERVER] [-p PORT] [-c CHANNEL] [-n NAME]
                       [-o OUTPUT]

optional arguments:
  -h, --help            show this help message and exit
  -s SERVER, --server SERVER
                        dns address of the irc server.
                        default=irc.freenode.net
  -p PORT, --port PORT  port number of irc server. default=6667
  -c CHANNEL, --channel CHANNEL
                        irc channel to join. default=#python-unregistered
  -n NAME, --name NAME  how the bot will be identified in the channel.
                        default=irc_logger_bot
  -o OUTPUT, --output OUTPUT
                        file to write log to. default=stdout

Finally, we are going to make this run if executed from the command line by using if __name__ == ‘__main__’:

if __name__ == '__main__':

        # Get the options from the parser
        opt = parseargs()

        # Create bot object and run it!
        bot = ircbot(opt.server, opt.port, opt.channel, opt.name, 
                     opt.output)
        bot.run() 

What does if __name__ == ‘__main__’ do?

Well when the python interpreter runs it creates some special variables such as __name__, if a file is executed it is assigned the name __main__.  using __name__ == ‘__main__’ in this way allows us to cleanly either import the file as a module, or execute specific code.

And that it, program complete.  I hope you have found this post both helpful and useful.  

The full code can be found at https://github.com/wsimmerson/ircbot/blob/master/tutorial_bot.py

Thanks for reading!

Note: This post was recovered from an old, now defunct, blog.