So, you are learning Python and want to build a portfolio that helps you land your first technical job at a company. Here are some project ideas for building your portfolio.
https://discoverpraxis.com/portfolio-project-ideas-python/Archived Link

So, you are learning Python and want to build a portfolio that helps you land your first technical job at a company. Here are some project ideas for building your portfolio.
https://discoverpraxis.com/portfolio-project-ideas-python/Archived Link
College degrees donât send the signals they once used to. You have to take charge of building your personal brand and work out loud instead. Here are ten ways to build your brand.
https://discoverpraxis.com/10-ways-build-personal-brand/Archived Link

What if you didn’t have to copy, switch window, paste, switch window, copy, switch window, paste, switch window, copy, switch window, and paste just to copy multiple items? With a clipboard manager, you don’t have to.
https://www.youtube.com/watch?v=i74J0n650QQ

So you have a job offer from another company and are ready to leave your existing role. You want to leave without burning the social capital you’ve built up over time. What do you do?
First, relax. Having that first conversation with your manager is usually nerve wracking, but everything will be okay. Switching jobs is expected, and chances are that your manager has been on both sides of the table before. Follow these guidelines below and you’ll be on your way to your new gig in no time.
http://discoverpraxis.com/how-to-leave-your-job/

I started this blog ten years ago today. I was in a room at the Doubletree in Tarrytown, NY. It was a Sunday and it was the weekend in-between my first FEE seminars. I ordered a pizza and some ziti from Capri Pizza and decided to tear down my old HTML site and give blogging a try. I figured out how to install WordPress 2.5.1 and went to town. Here is what the site looked like:

Here is the post that started it all.
Since then, I went on to work at FEE and move to NY, passing that Doubletree regularly. I blogged for a year straight. Iâve lived in 5 different cities and posted from every single one. Iâve written 763 posts on this blog since then and I average about 20,000 unique visitors a month according to my server logs.
The blogging has paid off. Here are things that have come from my blogging and showing my work:
Also, not only is today my 10 year blogging anniversary, it is also my 5 year wedding anniversary! Amanda and I had just started dating when I put this blog up, so sheâs been here since the beginning. Little did I know that weâd get married exactly five years later.
Itâs been a fun ten years and Iâm not done yet. cagrimmett.com is here to stay. Check out my archives and stay tuned for more.

During the Praxis bootcamp, participants are expected to make every single day a non-zero day. Most participants ask us to hold them accountable, so they email one of our staff each day and that person emails them the next day if they miss a check-in. This takes an enormous number of emails, staff overhead to keep track of, and is difficult to search. Isaac asked me, “What can we do to automate this?”
It needs this functionality:
There are existing systems that do this sort of thing, so why make our own? During the bootcamp, our customers use what we call The Portal, which is a curriculum platform I built on top of WordPress with the help of Restrict Content Pro and wpcomplete. I didn’t want to add another destination into the mix. I wanted a reason for customers to go to the Portal every day and keep their work moving forward. Plus, they already have logins there. No need to add yet another login for them to keep track of.
USP Pro handles all sorts of complexity when it comes to submitting posts from the front-end of a WordPress site: The form, the custom post type, assigning the current logged-in user to be the author, and displaying success and error messages. They also have hooks and filters to make using the posts in templates easier.
I used a custom template to hide some of the things I’d need to make the whole process work: User ID to assign as the post author, name and today’s date for the title of the post.
After the form, we also want to show all the posts someone has already submitted. I do that with a WP_Query_ Note: I added a Delete button here in case some makes an accidental post. If you want someone to be able to delete their check-in post, they need to have the role Author.
Here is the code I used for the template and comments about what it does:
// Check if user is logged in. Display the check-in form if so, login form if not. if ( is_user_logged_in() ) { //Get the logged in user, today's date, and the PHP session ID $current_user = wp_get_current_user(); $today = date("F j, Y"); $ses_id = session_id(); ?> class="usp-pro-form"> id="usp-pro" class="usp-pro usp-form-4102"> class="submitted-checkins"> Your submitted check-ins: // Show posts from this user ID with the type usp_post $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $current_user->ID, 'orderby' => 'date', 'order' => 'DESC', ) ); if ( $post_query->have_posts() ) : while( $post_query->have_posts() ) : $post_query->the_post(); ?> post_class(); ?>> the_time('F j, Y \a\t g:i a') ?> endwhile; else: echo "You have no check-in posts. Submit one above!
"; endif; ?> } else { ?> style="padding:50px;"> echo do_shortcode('[login_form]'); ?> } ?>
The page (with a little bit of CSS added, which I’ll leave as an exercise for the reader) looks like this: 
This goes in functions.php:
add_action( 'show_user_profile', 'accountability_opt_in' ); add_action( 'edit_user_profile', 'accountability_opt_in' ); function accountability_opt_in( $user ) { ?> Daily Accountability class="form-table"> type="checkbox" name="checkin" id="checkin" if (get_the_author_meta( 'checkin', $user->ID) == 'True' ) { ?>checked="checked" }?> value="True" />
/> class="description">Yes, opt me in.
} add_action( 'personal_options_update', 'save_accountability_opt_in' ); add_action( 'edit_user_profile_update', 'save_accountability_opt_in' ); function save_accountability_opt_in( $user_id ) { if ( !current_user_can( 'edit_user', $user_id ) ) return false; update_usermeta( $user_id, 'checkin', $_POST['checkin'] ); }
So, at the bottom of the user profile, it now shows this: 
Outline:
I like to leave the echos on for debugging if something goes wrong. Like, for example, if you use the_date instead of get_the_date and occasionally get NULL instead of a date and can’t figure out why. (Hint: the_date only fires once in a loop. Thanks for the help debugging, Eric Davis!)
Also, I know I’m using two different types for formatting for if statements. It helps me keep them separate.
$key = $_GET['key']; if ($key == 'RANDOM_KEY' ) { require_once($_SERVER['DOCUMENT_ROOT'] . '/wp-load.php'); function get_user_by_meta_data( $meta_key, $meta_value ) { // Query for users based on the meta data $user_query = new WP_User_Query( array( 'meta_key' => $meta_key, 'meta_value' => $meta_value ) ); // Get the results from the query, returning the users $users = $user_query->get_results(); return $users; } $opted_in_users = get_user_by_meta_data( 'checkin', 'True'); foreach ($opted_in_users as $user) { echo $user->ID . ' ' . $user->user_email . '
'; $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $user->ID, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'showposts' => '1', ) ); // The Loop if ( $post_query->have_posts() ) : while ( $post_query->have_posts() ) : $post_query->the_post(); $date = get_the_date('U'); echo 'last post date: ' . $date . '
'; //echo 'now: ' . time() . '
'; if( $date > (time() - 100800)) { echo 'Within 28 hours
'; continue; } else { echo 'Not within 28 hours
'; // Initialize curl $curl = curl_init(); $data = array( 'user_id' => $user->ID, 'email' => $user->user_email, 'first_name' => $user->first_name, 'last_name' => $user->last_name, 'last_post' => $date, ); $jsonEncodedData = json_encode($data); $opts = array( CURLOPT_URL => 'https://hooks.zapier.com/hooks/catch/1503890/aqztv5/', CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $jsonEncodedData, CURLOPT_HTTPHEADER => array('Content-Type: application/json','Content-Length: ' . strlen($jsonEncodedData)) ); // Set curl options curl_setopt_array($curl, $opts); // Get the results $result = curl_exec($curl); // Close resource curl_close($curl); echo $result; } endwhile; else : echo "No posts
"; // Initialize curl $curl = curl_init(); $data = array( 'user_id' => $user->ID, 'email' => $user->user_email, 'first_name' => $user->first_name, 'last_name' => $user->last_name, ); $jsonEncodedData = json_encode($data); $opts = array( CURLOPT_URL => 'https://hooks.zapier.com/hooks/catch/1503890/aqztv5/', CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $jsonEncodedData, CURLOPT_HTTPHEADER => array('Content-Type: application/json','Content-Length: ' . strlen($jsonEncodedData)) ); // Set curl options curl_setopt_array($curl, $opts); // Get the results $result = curl_exec($curl); // Close resource curl_close($curl); echo $result; endif; } } else { exit("You are not authorized. Go away."); } ?>
Viewing posts in the wp-admin area is less than ideal. They are grouped by date instead of author and the full contents doesn’t show unless you click. No want to view updates at a glance. So I made a template for that.
// First, check if user is an admin. if ( !is_user_logged_in() || !current_user_can('administrator') ) { wp_redirect( site_url() ); exit; } else { // User IDs are passed via query string. If no string, show all names $user = $_GET['id']; if ( $user == null ) {?> Participants who have submitted check-ins: $args1 = array( //'role' => 'author', 'orderby' => 'first_name', 'order' => 'ASC' ); $authors = get_users($args1); foreach ($authors as $user) { $user_post_count = count_user_posts( $user->ID , 'usp_post' ); if ( $user_post_count > 0) { echo '. $user->id . '">' . $user->first_name . ' ' . $user->last_name . '
'; } } } else { $user = get_user_by('id', $user); $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $user->ID, 'orderby' => 'date', 'order' => 'DESC', ) ); ?>
/> href="/check-ins/">← Back to all participants with check-ins
echo $user->first_name . ' ' . $user->last_name .'\'s check-in posts';?> if ( $post_query->have_posts() ) : while( $post_query->have_posts() ) : $post_query->the_post(); ?> post_class(); ?>> the_time('F j, Y \a\t g:i a') ?> the_content(); ?> endwhile; else: echo "You have no check-in posts. Submit one above!
"; endif; } }
Here is what the template outputs:


The best way to do this is to hook in to when the post type is initiated and toggle the ‘exclude_from_search option so ‘true’. I added this to functions.php:
add_action( 'init', 'usp_post_hide_search', 99 ); function usp_post_hide_search() { global $wp_post_types; if ( post_type_exists( 'usp_post' ) ) { // exclude from search results $wp_post_types['usp_post']->exclude_from_search = true; } }
I opted to use Zapier for two things:
wp_cron isn’t super reliable if you need something to run at a given time, and since I’m hosting on WPengine, I don’t have access to the server cron. We already use Zapier for a ton of stuff, so I fire off a GET to my script with the query string random key.Here are a few ways I want to improve this when I have time:

Praxis alumni Nate Baker and Nick Rundlett both became new managers this month. Congrats, guys! Nate asked the community for tips on how to be a good supervisor. Here is what our advisors and alumni had to say.
https://discoverpraxis.com/12-tips-for-new-managers/Archived Link

Yesterday my friend Derek Magill asked the Praxis community, âWhat books have had the most impact on your career/education?â
I love this question. Notice the use of the word impact. He isnât asking for your favorite books, the books you recommend on a specific subject, or the books you agree with the most. He is asking for the books that have had a significant impact on your education. Books that influenced and changed the way you think and interact with the world.
Here is my list, in order of when I read them. I explain what impact they had on my education and how that shifted the way I interact with the world.
https://discoverpraxis.com/16-books-shaped-interact-world/Archived Link

I made a video that shows you how to send custom conversion events to Facebook whenever a visitor, lead or customer engages with one of your websiteâs calls-to-action â all without having to touch your websiteâs code. It was featured on the ConvertFlow blog!
Check it out here: https://convertflow.com/blog/how-to-use-convertflow-with-your-facebook-ad-campaigns/Archived Link

Reamde is a wild ride that traverses half the globe, has multiple storylines intertwined, and jumps back and forth from the virtual and physical world. Stephenson is a captivating writer who pulls you into the story. He makes 1000+ pages feel like 300.
Check it out here: http://cagrimmett.com/book-notes/reamde_neal_stephenson/

When I migrated all of my old posts to Jekyll, my build time skyrocketed. The average time was 39 seconds for 853 posts. Abysmal. This is especially annoying when I’m actively working on the site with jekyll serve and waiting 40 seconds for changes to reflect.
The first step to figuring out what takes so long is running the Liquid profiler: $ jekyll build --profile
This is what it output for me:
Filename | Count | Bytes | Time --------------------------------------------------------+-------+----------+------ _layouts/default.html | 853 | 9652.42K | 7.462 search/feed.json | 1 | 1394.16K | 4.148 _includes/header.html | 853 | 1230.35K | 3.485 _layouts/post.html | 755 | 5119.00K | 2.301 _includes/opengraph.html | 853 | 870.67K | 1.694 _includes/icons.html | 810 | 1752.89K | 1.454 sitemap.xml | 1 | 203.34K | 1.321 _includes/footer.html | 853 | 109.12K | 0.273 til/posts.yml | 1 | 14.27K | 0.147 about.md | 1 | 18.02K | 0.085 _posts/2016-07-04-posts-heatmap-calendar.md | 1 | 16.78K | 0.080 isotope.html | 1 | 389.52K | 0.048 _layouts/book.html | 34 | 198.47K | 0.039 til.md | 1 | 58.76K | 0.023 feed.xml | 1 | 139.11K | 0.022 ... with a dozen more tiny blog index pages from paginator done in 39.359 seconds.
I tried a bunch of random stuff that made tiny improvements before I started doing research: Removing extraneous files, cleaning up if blocks that I no longer used, hardcoding things in the header that relied on variables from _config.yml, etc. None of this made significant improvements. I had already moved extraneous plugins long ago and I keep Jekyll up to date, so there was nothing more to do there.
I eventually came across this post by Mike Neumegen, which mentioned jekyll-include-cache, which caches the files you tell it to with the first instance and then serves the cached versions up for subsequent requests. For includes that don’t change based on the page content, you can replace {% include example.html %} with {% include_cached example.html %}. When I did this with my sidebar, nav bar, and footer, it cut my average first build time to 28 seconds, with subsequent regeneration times around 19 seconds. Awesome! (Neumegen’s post, which mentioned jekyll-include-cache contains a lot of other helpful advice for Jekyll build optimization. Check it out!)
My next step was to look at other includes to see if I could shave more time off. Some files had a mix of static and dynamic assets, so I split out all of the static assets into more include_cached includes. This cut another 5-6 seconds off of my build time. Boom.
Now that my layouts and my includes were as optimized as I could get them without major rewrites, I turned to the next major time hog: search/feed.json. 4 seconds for a single file! This file loops through everything on the site and dumps it into a single file so that I can crawl it with Javascript to power search on my site.
I used Mat Hayward’s search project with a few tweaks. As I dug deeper on the specific functions in the feed.json generator, I noticed that everything was being converted from Markdown to HTML first, then HTML stripped out, then turned into JSON. I wondered if skipping the markdown conversion step would lead to inaccurate search, so I tried it. Not only did my search function the same, but it dropped feed.json’s build time down to around a sixth of a second.
My build time now looks like this:
Filename | Count | Bytes | Time --------------------------------------------------------+-------+----------+------ _layouts/default.html | 853 | 9564.71K | 2.922 _includes/head.html | 853 | 1571.79K | 2.623 _includes/opengraph.html | 853 | 870.67K | 2.171 sitemap.xml | 1 | 203.34K | 1.177 _layouts/post.html | 755 | 5195.80K | 0.509 search/feed.json | 1 | 1394.65K | 0.176 _posts/2016-07-04-posts-heatmap-calendar.md | 1 | 16.78K | 0.091 about.md | 1 | 18.02K | 0.076 isotope.html | 1 | 389.52K | 0.050 _layouts/book.html | 34 | 198.47K | 0.030 _includes/header.html | 1 | 1.43K | 0.021 til.md | 1 | 58.76K | 0.019 feed.xml | 1 | 139.24K | 0.018 ... with a dozen more tiny blog index pages from paginator done in 19.594 seconds.
Regeneration times when running jekyll serve are hovering around 13 seconds now, which is a significant quality of life improvement for me when managing this blog.
There is probably more work I can do with moving the highlighter to the front end with JS and simplifying the Liquid in head.html and opengraph.html, but I’m stopping here for now.

Today I installed Craft CMS in for a new Praxis project. I use Laravel Homestead for my local development environment. It installs like pretty much every other PHP app on Homestead (use this guide by Matt Collins if you aren’t very familiar with Homestead), but I ran into a few annoying errors along the way:
If Mcrypt is required shows when you first go to http://yoursite.local/admin to finish the install, it is probably because you are a good citizen and your Homestead box is actually up to date, running PHP 7.2 as the default. Here’s the issue: mcrypt was removed from PHP 7.2. Craft 2 still needs it. There are three solutions:
Homestead.yaml, set the php variable to php: "7.0" under your site in the Sites block. Here is what mine looks like:- map: craft-praxis.local to: /home/vagrant/craft-praxis/public php: "7.0"
If you see this message, chances are that you are using MySQL 5.7.5+, which changed the way it deals with GROUP BY. See this StackOverflow post for the two solutions: Removing the ONLY_FULL_GROUP_BY option from sql_mode or adding the initSQLs database config settings in craft/config/db.php.

David,
First, I want to extend my sympathy for you and your friends at Marjory Stoneman Douglas HS and Parkland. No one should have to go through something like that, let alone be thrown into the public spotlight and questioned and mocked by people who disagree with you. You are a brave, impressive dude.
That’s why I don’t think you need college.
A college degree is a signal. The thing is, this signal is not as valuable as you think. It’s a signal to the world of the value you can create in the market. It conveys information about your ability, skill, and intelligence. There is a lot of noise in the world of work, and it’s hard to figure out who’s worth working with. A degree cuts through some of that noise and puts you in a smaller pool of competitors.
College persists for the ambitious — and thus the self-reinforcing data about successful people having degrees — because of a religious-like belief in it’s necessity. But it’s not necessary at all.
Not long ago a degree may have been the best signal most people could get. There weren’t many ways to demonstrate your value to the market, so a degree was one of the better bets.
Things have changed dramatically. Technology has opened up the world. The tools available to you now have lowered search and information costs, and you can create signals of your own that are far more powerful than a degree. Now, a college degree is one of the weakest, least common denominator efforts to doing this and is easy to surpass.
You told Axios that you are thinking about taking a gap year. That is a great idea.
I encourage you to continue throwing yourself into activism, organizing, and speaking. Learn how big events are planned and operated behind the scenes. Learn what different kinds of messaging works best for certain audiences. Continue refining your speeches. Make personal connections. Build email lists, communities, and followings. All of this will help you gain the experience and skills that future employers will value. Don’t spend the next four years in a classroom, wasting the valuable opportunity you have now.
You have forward tilt. You have natural sales skills. You have organizing skills. You have some marketing experience. You know how to work in fast-paced environments. You have a drive and focus that most people your age will never come close to.
At Praxis, we work with hundreds of bright, ambitious young people and help them launch careers without degrees. They’re better than college. You are too!
Sincerely,
Chuck Grimmett
CTO
Praxis

If you are going out for a development job or any sort of technical job, especially if you donât have a lot of professional experience in the field, the best thing you can do is put some of your work on display. GitHub is the most popular place to do that.
Here are the guidelines we give our technically skilled participants at Praxis for getting their GitHub accounts ready for their apprenticeship applications and interviews.
https://discoverpraxis.com/get-github-profile-ready-job-applications/Archived Link

Here are the three pillars of getting work done. If you are having trouble with staying on top of your work, chances are that it comes back to one of these three main areas.
This is common advice for our participants as they complete their challenging apprenticeships at growing startups.
https://discoverpraxis.com/get-work-done-primer/Archived Link

We have a big focus on projects (learning by doing!) at Praxis. Here are three recent participant projects I love and what the projects signaled. https://discoverpraxis.com/three-recent-participant-projects-love/Archived Link

Amanda and I spent the weekend exploring the Hudson Valley. On our way back today, we drove through Red Hook and saw that the Sawkill Farm farm store was open, so we stopped to pick up some meat. I had my camera gear in tow and they graciously allowed me to take some photos of their sheep. It was a drizzly day, but I had a wonderful time taking photos. The sheep have so much character and personality!























People often compare actual things with imagined, perfect, idealized alternatives. Asking Compared to what? helps you frame your decisions in relation to the relevant alternatives and ground them in reality. https://discoverpraxis.com/compared-to-what/Archived Link

TIL that you can’t change the CSV delimiter or enclosure characters on Excel for Mac or Apple Numbers. You have to use Open Office, and even there it is tricky. You need to go to File > Save As, select Text CSV, then make sure you check the “Edit Filter Settings” checkbox:

Then you get the field option AFTER you hit Save, only if the Edit Filter settings box is checked:


I’m using the ImportWP Pro plugin to quickly create pages in the Praxis curriculum portal. One of the custom fields I need to set is for the WPComplete course system we use. It takes JSON for which course the page belongs to and which page to redirect the to once the current page is marked complete. Here is an example:
{"course":"New Module 4 - Thinking","redirect":{"title":"You Have No Idea How Wrong You Are","url":"https://portal.discoverpraxis.com/bootcamp/new-module-4/no-idea-how-wrong"}}
I needed to build this JSON in Excel with the rest of the page info. The pages I’m setting the redirect title and URL for are being created in the spreadsheet, too. So I wanted to build this JSON cell from the contents of other cells. The commas and quotes were a problem for my typical way of building text from other cells: =B5&" "&C5
Here is the new solution I found: Break the JSON down into constituent parts, put each in its own cell, then build the JSON with the concatenate() function.
Here are the 5 cells I needed:
{"course":"New Module 4 - Thinking","redirect":{"title":"","url":"https://portal.discoverpraxis.com/bootcamp/new-module-4/"}}I put 1 in A23, 3 in A24, and 5 in A25. Then pulled 2 and 4 from various cells in columns A and B. Then my JSON cells used this formula: =CONCATENATE(A23,A4,A24,B4,A25)
Concatenated together, it looks like this:
{"course":"New Module 4 - Thinking","redirect":{"title":"PAGETITLE","url":"https://portal.discoverpraxis.com/bootcamp/new-module-4/PAGESLUG"}}

I never remember the keyboard shortcut for Fill Down in Excel on macOS, so I’m posting it here where I’ll have it.
Control + D
I’m catching up on my book notes. Today I posted three more before work:
More to come, so stay tuned!
I’m catching up on my book notes. There are a lot of books I read this past year that I neglected to post notes for, so I’m getting caught up on that. I posted five new books to my Book Notes section today:
Lots more to come, so stay tuned!
Today I upgraded from Homestead 4.0.0 to 5.0.1 so I could test my sites in PHP 7. That was a major upgrade and a number of things changed, so I decided that I needed to back up my databases before I did the upgrade. I’d only ever dumped specific databases before, but TIL how to dump them all at once. Here is how to do that in Laravel Homestead:
$ cd ~/Homestead$ vagrant ssh$ mysqldump -u homestead -psecret --all-databases > Code/homestead-YYYY-MM-DD-hh-mm.sql$ mysql -u homestead -psecret < Code/homestead-YYYY-MM-DD-hh-mm.sqlI had some major issues with the upgrade. When I first tried it, vagrant up hung at the SSH auth step and never moved. I went down a deep rabbit hole with my friend Eric Davis trying to debug the SSH issues, to no avail. My last trial was to roll back to Homestead 4.0.0, check my vagrant ssh-config settings, then try the upgrade with those settings. Then, miraculously, when I tried the upgrade again, vagrant up worked with no SSH problems! No difference in settings. I’m baffled, but relieved.
FYI, there are some Homestead.yaml differences between Homestead 4 and 5, so make sure you have some time set aside to read the docs, update your yaml file, and rerun vagrant up --provision a bunch of times to get everything working again.

A Praxis participant emailed me last week asking for recommendations on free embeddable charting tools for an article he is writing. I thought that the information would be useful for everyone, so I wrote an article with examples! https://discoverpraxis.com/free-charting-tools-next-article/Archived Link