tag:santos.posthaven.com,2013:/posts Santos Solorzano 2021-10-09T02:36:19Z Santos Solorzano tag:santos.posthaven.com,2013:Post/1745629 2021-10-09T02:36:19Z 2021-10-09T02:36:19Z 2021 Fantasy Football Draft: How I Got Here + Current Outlook
I present the players I drafted (with the 7th pick) this year in the one fantasy football league I play in. Kudos to those who currently manage something like five teams
  1. Diggs
  2. CEH
  3. Kittle
  4. Josh Allen
  5. Mike Davis
  6. Golladay
  7. Jeudy
  8. Corey Davis
  9. Tampa Bay
  10. Michael Carter
  11. Butker
  12. Trevor Lawrence
  13. Tre'Quan
  14. Tevin Coleman
  15. Hunter Henry
Diggs - the better RB1s were already taken by this point and Diggs had a tremendous year last year; thought he could build off that and I didn't think Devante would replicate the performance he had last year

CEH - I'll have to double check but I think I passed up on CEH last year; I felt iffy about this draft pick and my concerns were somewhat validated the first couple of weeks but he's bounced back since

Kittle - drafted Kittle thinking I wasn't going to have to worry about the TE position this year but boy was I wrong; Kittle hasn't scored this year and the injury concerns are back baby; one of the Cowboys receiver would've been nice instead...

Josh Allen - pretty sure Russ was my QB the last 3-4 seasons; Russ was hot the first five weeks last season but then it all came crashing down; changed it up here and was also banking on Allen keeping up last year's numbers; if only I had drafted Kyler but I'm still salty about him leaving TAMU

Mike Davis - felt even more iffy about this draft pick but he did alright in place of CMC last year, probably should've gotten DJ Moore here

Golladay - this one was for my hermano Juan; thought a new change of scenery would be good for Kenny and he's been alright

Jeudy - S-E-C!

Corey Davis - Mr. Hot and Cold; benched him week 1 went he went off

Tampa Bay - not really a fan of streaming defenses (unless it makes sense to me) so I like picking up a top team here right around here

Michael Carter - skimmed some info on the rookie during the draft and felt good enough to take him here

Butker - consensus top-5 kicker...not this season

Trevor Lawrence - was already looking forward to Allen's bye week; have since dropped Trevor and will probably stream a QB week 7

Tre'Quan - Michael Thomas was going to be out for the first part of the season

Tevin Coleman - thought someone in the Jets backfield would takeover eventually but have parted ways since

Hunter Henry -  Mr. Irrelevant

---

And now, I present my team 5 weeks into the season. Bolded players are players I either traded for or have picked up. Currently sitting at 0 wins and 4 losses

  1. Diggs
  2. CEH
  3. Kittle
  4. Josh Allen
  5. Golladay
  6. Jeudy
  7. Tampa Bay
  8. Butker
  9. Elijah Mitchell
  10. Odell
  11. Alex Collins
  12. Jared Cook
  13. Gainwell
  14. Josh Reynolds
  15. Mooney

Mitchell - acquired as part of the Davis and Davis trade for Odell and Mitchell; jury is still out...

Odell - acquired as part of the Davis and Davis trade for Odell and Mitchell; will hold out on Odell; there's been a lot of interest + hoping the price goes up

Alex Collins - heard murmurs about Carson's neck injury and sent Collins to the top of my waiver adds list; I'm alright with the shift he put against the Rams

Jared Cook - Kittle back-up plan in 5, 4, 3, 2, 1...

Gainwell - picked up earlier this season; will see how things play out after numerous unsuccessful attempts to get Miles Sanders

Josh Reynolds - Aggie wide receivers need some love; Josh got a lot of targets with Julio and AJ Brown out but I don't think I'll hold on to him with Brown coming back

Mooney - missed out on Mooney in the draft but found him on the waivers earlier this season; I inexplicably dropped him then was able to pick him up again; could be worse...see Arob


]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1658206 2021-02-24T21:59:22Z 2021-02-24T21:59:22Z Atalanta - Real Madrid (2020-02-24)

Lineup thoughts

  • Isco up front with Vini and Asensio; Zidane and the rest of us hoping for three-peat Isco to show up
  • Zapata and Muriel starting up front; I've normally seen one starting and not the other but maybe this isn't as surprising as I think it is

First half thoughts

  • 12' - a lot of U-shaped passing for Real Madrid and some cross-field passes sprinkled in; there's not really an aerial threat up front so this is how Madrid will build up their attack
  • 13' - great lob pass from Isco to Vini but the Atalanta keeper gets there Neuer-style; keeper probably lucky not to get a card there since he left his leg on Vini after clearing the ball
  • 16' - already two great defensive stops by Nacho/Mendy to dispossess Zapata
  • 17' - great 1-2 from Mendy and Vini; Vini releases Mendy into the open space and he gets fouled by an Atalanta defender who gets a red card; I'd like to see another angle on the card-worthy foul but a red card seems a bit harsh
  • 23' - some great dribbling from Vini on the left flank which started with Kroos opting to pass to the left instead of slinging another pass to the right
  • 24' - Nacho surprisingly beats Atalanta's backline into the box but overhits a cross; we're going to need more of this 
  • 25' - where was Mendy on that Atalanta break?
  • Not much from Asensio up to this point but Madrid haven't been attacking from his side a lot
  • Attacking plan so far has been to switch the play from one side of the field to the other, dribbling up to the 18-yard box and attempting a through ball to a player; no luck so far
  • 37' - first great chance! Isco outside of the boot pass to Modric who dummies an Atalanta player and plays a pass to Isco in the box; Isco has his back to the goal but is able to get a shot off which leads to a corner
  • 38' - another great chance; Madrid wins the ball in Atalanta's half; Isco releases Vini in the open space and his shot gets deflected; great from Vini
  • TUDN commentator mentioned that Benzema probably makes it 1-0 there but no complaints from me; old Vini probably doesn't do much there
  • Mendy playing so high up near the middle of Atalanta's 18-yard box

Second half thoughts

  • Madrid need an away goal; Casemiro will be out for the second leg and Madrid need to take advantage of currently being a man up
  • 56' - failed counterattack from Madrid after winning the ball in midfield
  • TUDN commentator mentioned an interesting stat (Madrid-Atalanta still 0-0): 6 away sides so far had won the first leg for this year's round of 16, with Porto being the only home team to win the first leg against Juventus

Real Madrid win 1-0 after Mendy scores with a shot from outside the box late in the game. Atalanta never really threatened and Madrid playing again with a makeshift lineup. Madrid should have some key players back for the return leg.

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1658171 2021-02-24T20:05:08Z 2021-02-24T20:05:08Z FC Barcelona - Elche (2020-02-24)

Lineup thoughts

  • No Griezmann, no Dembele in the starting lineup for Barcelona
  • I'm thinking Barca has enough creativity already on the field and you want someone like Braithwaite to take advantage of this; we'll see how this goes

First half thoughts

  • 5' - Elche misses a big chance...
  • 14' - Both teams playing on Elche's side of the field with Alba and Mingueza high up on the wings; Elche almost had a counterattack with Umtiti and Pique practically exposed but they did not capitalize...no way Barca does this against a better team
  • 19' - Silky, smooth dribbling from Trincao near the 6-yard box but an even better save from the keeper; Elche taking it to Barcelona with these counterattacks but can't complete that last pass
  • 39' - Trincao dispossessed against three Elche players but wins the ball back; chance is there for the taking but Elche is not capitlizing
  • 40' - Again, another bad pass from Elche that kills off an attack
  • Quiet game from Pedri and Messi so far
  • 43' - Another counterattack from Elche that finally leads to a shot on target but it's straight at ter Stegen
  • beIN Spanish broadcast showed us a graphic that most of Barcelona's attacks have come on the right flank via Mingueza and Trincao

Second half thoughts

  • Messi takes the lead in the Pichichi race; insane considering his slow start this season
  • 69' - Messi makes it 2-0 but De Jong did all the hard work; game over
  • 73' - Messi with the half-assist to Braithwaite who heads it to Alba who makes it 3-0
  • beIN Spanish commentators commenting how different Barcelona looks in the second half

A tale of two halves. Elche really should've done better in the first half with the plethora of counterattacks that they had. FC Barcelona was forgiving in the first half but in the second half they were not.

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1655762 2021-02-18T21:57:12Z 2021-02-18T21:57:13Z Benfica - Arsenal FC (2020-02-18)

Lineup thoughts

  • I did not know Vertonghen was a Benfica player
  • I completely forgot about Weigl (ex-Dortmund) and was surprised to see him in the lineup
  • Excited to see Núñez; thank you r/soccer for introducing me to him
  • Solid lineup from Arsenal; good to see Tierney back on the bench and happy for Ode

First half thoughts

  • Auba missed a big chance earlier in the game; no idea how...
  • 26' - Delightful through ball from Ode to Saka as he played a pass almost from half-field into the box; more of this!
  • 28' - ESR wins a ball in midfield; failed counterattack but great pressing sequence which led to Benfica giving the ball away
  • Dani playing pretty deep, receiving the ball from Leno and trying to build the attack from the back
  • Xhaka attempting crosses to the wing/inside the box...should opt for the simple ball
  • 39' - Simple 1-2 with Saka and Bellerin leads to an ESR chance in the box; see my previous point
  • 39' - Great passing sequence between Dani and Ode leading to another great chance but Luiz was ruled offside
  • 41' - Good recovery from Dani; getting Casemiro + Kroos vibes with his interventions + passing
  • 42' - ESR has been a constant threat on the left
  • 45' - Xhaka giveaway (crossing across team's own box) almost leads to a Benfica goal...

Second half thoughts

  • 53' - Unlucky penalty call; Leno almost stopped it too
  • 56' - Arsenal tie-up the game; Cedric assist to Saka but key play was the Ode line-breaking pass to Cedric - yes!
  • TUDN commentator commented on Ode having a "partido flojo" or "sub-par game" - Ode has had his moments this game but he seems to drift in and out; haven't seen him exert his influence on a game like he did when he was at Real Sociedad
  • 67' - Great tackle from Dani to win the ball from Everton; not bad, not bad
  • Asides from the one shot on target that forced Leno into a save this half, Benfica haven't threatened much...great from Arsenal but they need to take advantage of this on the other end
  • 70' - Tierney-ESR partnership down the left :handshake_emoji:
  • Uneventful last 15 minutes tbh

1-1 draw.

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1654941 2021-02-16T21:58:40Z 2021-02-18T20:13:56Z FC Barcelona - PSG (2020-02-16)

First half thoughts

  • Gana (Gueye) was walking on a tight rope with his first yellow card
  • I wish more replays were shown regarding the penalty given to Barcelona; it looked like De Jong tripped himself
  • Mbappe showed great composure to create space in the box and score; when he dribbles, it's like the ball is stuck to his foot
  • As much as I miss the energy from the fans, it's great hearing what players and coaches are saying on the pitch
  • Verratti has been immense so far; great assist to Mbappe on one of his goals
  • Kean's performance so far has been serviceable; he's been linking up well with the midfielders and he's playing the right pass
  • I'm watching the TUDN broadcast and one commentator mentioned that PSG had been the more physical side so far

Second half thoughts

  • Long balls have been Barcelona's undoing
  • Ter Stegen is Barcelona's second best player; PSG have 2-3 more goals if he's not in goal
  • Not much Messi magic; he was dispossessed a couple of times when he wasn't fouled
  • Counterattack leading up to the Mbappe 4-1 goal...ruthless
  • No Neymar, no problem

PSG earn a 4-1 victory!

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1570207 2020-07-07T01:45:09Z 2020-07-07T01:45:09Z Technical Writer Application for 2020 Season of Docs

Photo by Kaitlyn Baker on Unsplash

Documentation is essential to the adoption of open source projects as well as to the success of their communities. Season of Docs brings together technical writers and open source projects to foster collaboration and improve documentation in the open source space.

https://opensource.googleblog.com/2020/06/season-of-docs-now-accepting-technical.html

Open source organization for proposal

Bokeh

Title of technical writing project proposal

Improving the Documentation Experience for Bokeh Developers

Description of technical writing project proposal

Current documentation state

Bokeh has done a tremendous job in documenting visualization use cases in the User Guide [1]. In the Reference [2], you can find all the API methods afforded by their models. The documentation has grown large and there is no easy way to find misspellings, repetition errors, or formatting issues in the text [3].

You can find dozens of code examples on how you might use Bokeh with your own data on GitHub[4]. You can find some of these examples inline in the documentation but not all of them are referenced[5]. Users may spend a considerable amount of time trying to figure out how a tool works without realizing there exists code they can reference. For example, you can use Themes to style a plot on Bokeh but these examples exist in the Reference when one would expect to find an example listed inline or referenced in the User Guide [6][7].

Lastly, a subset of the Bokeh documentation could benefit from the inclusion of metadata. Bokeh uses Sphinx to build documentation. Sphinx[8] is a tool that makes it easy to document software projects. This tool does not automatically include any structured data on the HTML pages it generates. Metadata in this case is metadata about the HTML pages. When searching for “Bokeh docs” on a search engine, the results users get back do not describe the content of the page. When sharing links to the Bokeh documentation on social media sites or forums, there is no way to preview the content on the page before clicking on links.

Proposed documentation state

Automated checks for spelling, repetition, and writing style errors

Vale Linter [9] is available as a GitHub Action [10]. It checks for spelling, repetition, and styling issues on every pull request. This Action can be added to the existing build process Bokeh uses for pull requests on GitHub. Automated checks would find existing errors in the documentation to fix. This technology would prevent future errors from creeping into the documentation. Vale Linter can also enforce a consistent writing style across all documentation. For example, suggesting the term "JavaScript" over "Javascript," preferring active voice over passive voice, etc.

Additional cross-referencing across docs

Different parts of the documentation should link back and forth for a more complete discussion. Users interested in learning more about a topic should be able to navigate to the Reference from the User Guide. Users interested in seeing an example of an API method should also be able navigate to the User Guide from the Reference. All examples found in the GitHub repository should either be referenced or exist inline in the documentation.

Metadata across docs

Search engines extract and collate the metadata found on web pages to describe and classify them. Including metadata, such as descriptions, in the Bokeh documentation would give users more data when browsing search engine result pages. This metadata would also provide rich previews when sharing links to these pages. Some metadata would appear alongside these links, giving readers a preview of the content before clicking. Specifying HTML metadata, like a description, can be done by manually adding the the "meta" directive on some pages. Later,  Sphinx extensions can be developed to automate adding relevant metadata throughout the entire documentation.

Timeline

Pre-community bonding

  • Stay active as a contributor by tackling documentation issues
  • Start a friction log to keep track of areas of documentation needing improvements

Community bonding

  • Establish project requirements
  • Schedule a time to meet with mentors
  • Agree on method of providing progress and updates

Week 1

  • Set up and test Vale to check for existing spelling and repetition errors
  • Identify terms to ignore that cause spelling errors like http, Bokeh, JupyterLab, etc.
  • Add a new text file with list of terms to ignore when checking for spelling errors

Week 2 and Week 3

  • Identify suggested terms to use throughout documentation for consistency
  • Add a new style guide for suggested terms
  • Configure Vale to run on every pull request submitted to Bokeh

Week 4 and Week 5

  • Start working on improving cross-referencing across Bokeh documentation
  • Identify existing Bokeh examples not shown in-line in documentation
  • Link examples in the documentation to the source code location on GitHub

Week 6 and Week 7

  • Review topics covered in the User Guide
  • Identify topics to link to sections in the Reference

Week 8

  • Identify pages on https://bokeh.org/ and manually add metadata
  • Investigate existing Sphinx extensions that can be used to add metadata across docs

Week 9

  • Integrate existing Sphinx extension or develop a new Sphinx extension to automatically add metadata across docs

Week 10

  • Test Sphinx extension(s)

Week 11

  • Finish remaining tasks
  • Start working on Season of Docs project report

Week 12

  • Finish project report
  • Submit project report to Google

References

  1. User Guide - https://docs.bokeh.org/en/latest/docs/user_guide.html
  2. Reference - https://docs.bokeh.org/en/latest/docs/reference.html
  3. Documentation spelling and formatting - https://github.com/bokeh/bokeh/issues/8448
  4. Bokeh Examples - https://github.com/bokeh/bokeh/tree/master/examples
  5. Include example code of PolyEditTool and PolyDrawTool Docs - https://github.com/bokeh/bokeh/issues/9962
  6. Add mention of Themes to "Styling Visual Attributes" docs page - https://github.com/bokeh/bokeh/issues/9007
  7. Reference Guide should link to Users Guide where appropriate. - https://github.com/bokeh/bokeh/issues/9363
  8. Sphinx - https://www.sphinx-doc.org/en/master/
  9. Vale - https://github.com/errata-ai/vale
  10. Vale Linter - https://github.com/marketplace/actions/vale-linter

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1558043 2020-06-26T02:38:55Z 2020-06-26T17:55:28Z Building a search app with Django and Haystack

Goal

The goal of this tutorial is to build a search app using Django and Haystack You will learn how to use Django commands to initialize a database with emoji data. You will also learn how to add search to a Django project using Haystack.

Upon completion, you will have a built an app that allows you to search for over a thousand emojis. This app also gives you the ability to copy any emoji to your clipboard with one click.

Before you start

Make sure you meet the following prerequisites before starting the tutorial steps:

This project depends on Pipenv. Pipenv allows you to download and install versions of packages in a virtual environment.

Another prerequisite is Elasticsearch. An Elasticsearch instance needs to run separate from the app.

Installing packages

The app depends on the following packages:

Open up a terminal prompt and create a directory called emoji-in-the-haystack:

mkdir emoji-in-the-haystack
cd emoji-in-the-haystack

Install the packages:

pipenv install django==3.0.7
pipenv install git+https://github.com/django-haystack/django-haystack.git#egg=django-haystack
pipenv install elasticsearch==5.5.3
pipenv install requests==2.24.0

You’ll see a bunch of colorful output and a couple of 🐍 emojis. In this directory, you should now see the files Pipfile and Pipfile.lock.

You’re ready to create a Django project.

Setting up a Django project and app

After installing the packages, the next step is to create a Django project.

Activate your virtual environment:

pipenv shell

You should now see your terminal prompt prefixed with (emoji-in-the-haystack).

Create a Django project called emoji_haystack:

django-admin startproject emoji_haystack .

The directory should now look like this:

├── Pipfile
├── Pipfile.lock
├── manage.py
└── emoji_haystack
   ├── __init__.py
   ├── asgi.py
   ├── settings.py
   ├── urls.py
   └── wsgi.py

Create a Django app called search:

python manage.py startapp search

The directory should now look like this:

├── Pipfile
├── Pipfile.lock
├── manage.py
├── emoji_haystack
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── search
   ├── __init__.py
   ├── admin.py
   ├── apps.py
   ├── migrations
   │   └── __init__.py
   ├── models.py
   ├── tests.py
   └── views.py

You need to enable the newly created app.

Update the INSTALLED_APPS setting in settings.py:

33
34
35
36
37
38
39
40
41
42
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',

   'search.apps.SearchConfig',
]

To test that everything is working, run the app:

python manage.py runserver

Navigate to http://127.0.0.1:8000/ and confirm that the app is working.

Note: You can run python manage.py migrate to get rid of the Django warnings when running the app.

Emoji data

The next step is to create a Django model class to represent the emoji data.

Update models.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.db import models


class Emoji(models.Model):
    name = models.CharField(
        max_length=50,
    )
    code = models.CharField(
        max_length=50,
    )

You need to store the name for each emoji. For example, “grimacing face” is the name given to 😬. You also need to store the code for an emoji. These code points are unique for every emoji. Django handles rendering emojis in the browser using these codes.

After creating the model, run a migration to apply these changes to the database:

python manage.py makemigrations --name add_emoji_model search
python manage.py migrate

The next step is to create a new directory for the Django command. Django commands are special scripts registered in Django projects.

The command in this app retrieves emoji data and saves it to the database using the Emoji model class. This commands must live in the new directory.

Create the new directory:

cd search
mkdir management
cd management
mkdir commands
cd commands

Inside this commands directory, create the initemojidata command:

touch initemojidata.py

The directory should now look like this:

├── Pipfile
├── Pipfile.lock
├── db.sqlite3
├── emoji_haystack
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── search
   ├── __init__.py
   ├── admin.py
   ├── apps.py
   ├── management
   │   └── commands
   │       └── initemojidata.py
   ├── migrations
   │   ├── 0001_add_emoji_model.py
   │   └── __init__.py
   ├── models.py
   ├── tests.py
   └── views.py

Here is the code to retrieve and save emoji data:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import json
import requests

from django.core.management.base import BaseCommand, CommandError

from search.models import Emoji


EMOJI_JSON_URL = 'https://raw.githubusercontent.com/iamcal/emoji-data/master/emoji.json'


class Command(BaseCommand):
    help = 'Initialize database with emoji data'

    def add_arguments(self, parser):
        parser.add_argument(
            '--dry-run',
            action='store_true',
            default=False)

    def execute(self, *args, **options):
        self.count = 0

        try:
            super().execute(*args, **options)
        except KeyboardInterrupt:
            self.stdout.write('')

        self.stdout.write(self.style.SUCCESS(
            'Emojis created: {}'.format(self.count)))

    def handle(self, *args, **options):
        self.dry_run = options['dry_run']

        emojis = self.get_emojis()

        for emoji in emojis:
            if not emoji.get('name'):
                continue

            code = self.handle_code(emoji)
            name = emoji['name'].lower()
            self.stdout.write(
                '{} - {}'.format(name, code))

            if not self.dry_run:
                emoji = Emoji(
                    name=name,
                    code=code)

                emoji.save()

            self.count += 1

    def get_emojis(self):
        response = requests.get(
            url=EMOJI_JSON_URL)

        emojis = json.loads(response.content)

        return emojis

    def handle_code(self, emoji):
        """
        U+1F1EC, U+1F1FE - > &#x1F1EC&#x1F1FE
        """
        unified = emoji.get('non_qualified') or emoji.get('unified')
        unified = unified.split('-')

        codes = []
        for code in unified:
            _code = '&#x' + code
            codes.append(_code)

        return ''.join(codes)

The syntax for Django commands may take some time getting used to. Django commands require a Command class definition that subclasses BaseCommand. This class requires a handle() method. Your logic goes in here.

I use the execute() method to define some variables to count and output the number of items updated when a command finishes running.

On line 35, the get_emojis() method defined on the class gets called using the self property. The method makes a request to the URL defined on line 9. This endpoint is a JSON file hosted on GitHub.

It may not include the newest emojis but it’s the best option for this app. The Emojipedia API is no longer available for public use. Typically you need to handle errors when making API requests but it’s fine to leave out here.

The command retrieves the emoji data and begins to process each data item on line 37. It ignores data items with no name field. On line 41, the command calls the handle_code(). This method transforms the emoji unicode data into a string that gets stored in the database. The transformation of this unicode data makes it possible to render emojis in HTML. More on this later.

You can run this command with an optional dry_run argument. Providing this argument means you can test your Django command logic without saving anything to the database. If this argument is not passed in when running the command, the command creates an Emoji object with name and code set and saves it to the database.

Django commands are ran from the root of the project.

Run the Django command (--dry-run option):

python manage.py initemojidata --dry-run

Run the Django command (no regrets option):

python manage.py initemojidata

The emoji data is now stored in the database.

Haystack setup

Haystack makes it easy to add custom search to Django apps. You write your search code once and can go back and forth between search backends as you please. You can choose to use different search backends like Elasticsearch, Solr, and others. This tutorial uses Elasticsearch.

Integrating Haystack consists of creating a search index model and updating a couple of Django settings.

The search index model corresponds to the database model defined earlier. Haystack requires this file to know what data to place in the search index.

Inside the search app directory, create a search_indexes.py file:

cd search
touch search_indexes.py

Here’s what the code for that looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import datetime

from haystack import indexes
from search.models import Emoji


class EmojiIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)

    def get_model(self):
        return Emoji

When you make search a query, Haystack searches the text field. This field corresponds to the name field defined in the Emoji model.

Next, include the urls provided by Haystack in urls.py. Django implicitly calls a custom Haystack view that handles search requests and returning responses. This response uses an HTML template that you need to create and configure. More on this later.

16
17
18
19
20
21
22
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('search/', include('haystack.urls')),
]

You need to enable the Haystack app.

Update the INSTALLED_APPS setting in settings.py:

33
34
35
36
37
38
39
40
41
42
43
44
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'search.apps.SearchConfig',

    'haystack',
]

Add a connection to Elasticsearch in settings.py:

127
128
129
130
131
132
133
134
135
136
# Haystack configuration
# https://haystacksearch.org

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine',
        'URL': 'http://127.0.0.1:9200/',
        'INDEX_NAME': 'haystack',
    },
}

Haystack setup continued

The following steps are cumbersome but they are essential in getting Haystack to work.

In settings.py, update the TEMPLATES setting:

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

From the root of the project, create a templates directory:

mkdir templates
cd templates

Creating a single project-level templates directory is a recognized Django pattern.

In the templates directory, create a search directory and a file called search.html:

mkdir search
cd search
touch search.html

In the search directory, create an indexes directory:

mkdir indexes
cd indexes

In the indexes directory, create a search directory and a file called emoji_text.txt:

mkdir search
cd search
touch emoji_text.txt

Here’s what emoji_text.txt should look like:

{{ object.name }}

Haystack uses this data template to build the document used by the search engine.

The final directory structure should look like this:

├── Pipfile
├── Pipfile.lock
├── db.sqlite3
├── emoji_haystack
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── search
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── management
│   │   └── commands
│   │       └── initemojidata.py
│   ├── migrations
│   │   ├── 0001_add_emoji_model.py
│   │   └── __init__.py
│   ├── models.py
│   ├── search_indexes.py
│   ├── tests.py
│   └── views.py
└── templates
   └── search
      ├── indexes
      │   └── search
      │       └── emoji_text.txt
      └── search.html

Search template

Now it’s time to update search.html. This template contains a text field to type in a search query, a button that fires a search request and some template variables. Use the template example found here.

Note: Remove {% extends 'base.html' %} at the top of the file.

The main differences in the template for this tutorial are the following two lines:

18
19
20
21
{% for result in page.object_list %}
   <p>{{ result.object.code|safe }}</p>
   <p>{{ result.object.name }}</p>
{% empty %}

object_list is a list of search results. For each search result, display the emoji and its name. result.object provides direct access to the Emoji model and its database fields.

Displaying the emoji requires using the safe Django filter. It does not require further HTML escaping.

Running Elasticsearch

Navigate to the location of your Elasticsearch installation and start an instance. For example, say you downloaded Elasticsearch in your Downloads folder:

cd Downloads
cd elasticsearch-5.5.3
cd bin
elasticsearch

Haystack ships with a set of Django commands that handle indexing the emoji data stored in the database. This tutorial uses the rebuild_index command. This command rebuilds the search index by first clearing it and then updating it. Have a look at the source code for more info.

From the root of the project, run the command:

python manage.py rebuild_index

Run the app:

python manage.py runserver

Navigate to http://127.0.0.1:8000/search and confirm that the app is working.

If you query for “cat,” you get back a list of results. If you query for “flag,” you get back results for flag emojis.

If you scroll to the bottom, you’ll see a Previous button and Next button. Haystack returns at most 20 results per page. This out of the box feature is awesome. The layout needs a little bit of work though.

Bootstrap + clipboard.js

You can use Bootstrap to clean up the design. Another feature is to copy an emoji to your clipboard by clicking on it - clipboard.js can help here.

Load Bootstrap and clipboard.js from CDN in search.html:

1
2
3
4
5
6
7
8
9
<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>

<!-- Bootstrap CSS -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">

{% block content %}

A couple of Bootstrap <div> elements and some styling updates go a long way in improving the look of the app.

Including the data-clipboard-text attribute on the emoji button lets you copy emojis to your clipboard:

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
        {% if query %}
            <h3>Results</h3>

            <div class="container">
            <div class="row">
            {% for result in page.object_list %}
                <div class="col-sm">
                    <button type="button" class="btn" data-clipboard-text="{{ result.object.code|safe }}" style="font-size:90px;">{{ result.object.code|safe }}</button>
                    <p style="text-align: center">{{ result.object.name }}</p>
                </div>
            {% empty %}
                <p>No results found.</p>
            {% endfor %}
            </div>
            </div>

The last thing to do is to initialize clipboard.js in search.html:

50
51
52
53
54
55
56
57
58
59
60
61
62
{% endblock %}

<script>
    var clipboard = new ClipboardJS('.btn');

    clipboard.on('success', function(e) {
        console.log(e);
    });

    clipboard.on('error', function(e) {
        console.log(e);
    });
</script>

Run the app with these new changes:

python manage.py runserver

Navigate to http://127.0.0.1:8000/search and confirm the changes. This looks much better. The emojis are more prominent and the click-to-copy feature is the 🍒 on top.

What you’ve learned

Rejoice and show your friends how to find the emoji in the haystack. If you’re up for the challenge, see if you can make the following app improvements:

  • Load a subset of emojis on the homepage before a user searches

  • Add a navigation bar to filter by emoji category

  • Support for newer emojis

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1537659 2020-04-30T04:36:02Z 2020-06-06T05:48:57Z Good First Issue: Fixing hardcoded dates in a Web2py application

A video showcasing search on GitHub and how one can leverage it to navigate unfamiliar codebases and frameworks.

Links:
- https://github.com/RunestoneInteractive/RunestoneServer/pull/1441

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1530396 2020-04-15T01:57:42Z 2020-06-06T05:49:08Z Webhook signatures for fun and profit

Webhooks - less painful than playing hooky by skipping work.

Image source: Encyclopedia SpongeBobia

What's a webhook?

Application programming interfaces (API) consist of client requests and server responses. Webhooks are the reverse of APIs! A third-party service (e.g. server) will send data to one or more configured listeners (e.g. clients). You can set up a listener to consume webhook events by following these steps:
  1. create a new URL in your web application to listen for events (e.g. mycoolapp.com/webhooks)
  2. create a secret token with your third-party service (e.g. GitHub repository settings)
  3. give your application access to this secret token (e.g. environment variables)
  4. deploy the application to listen for requests
  5. verify the webhook signature found in each request
  6. if the signature passes this verification step, process the event data
  7. if it doesn't pass, raise an error
Webhooks allow us to get information in real-time. Let's say we want to find out if a task has finished. Instead of polling an API and asking for the state of a task, webhooks automatically notify us when a task is done. All we have to do is verify the webhook signature.

Companies like Stripe and Twilio provide developers with software development kits (SDKs). These SDKs typically verify signatures for you. If not, have no fear! We can manually verify these signatures using Python.

Note: the terms "third-party" and "authorized users" will be used interchangeably from here on out.

Trust...

Let's assume our application was partly compromised. Our webhook URL is now public and out in the open. How do we differentiate authorized users from bad actors? Our application and the third-party service need some way to authenticate messages. One way to achieve this is to use a hash-based message authentication code (HMAC).

First, an authorized user sends a signature with every request to our application. Next, our application computes the expected signature by combining HMAC with our secret token. It compares both signatures and allows requests from this user if the signatures match. Bad actors would have a hard time trying to fool us without this secret token.

Now that we've covered secret tokens, let's take a look at the code to manually verify signatures.

...but verify


We define a request "object" on line 20. We use this object to represent a request that would normally be sent by an authorized user. This request has a signature, which is a bytes string. Let's assume the signature in the request is valid. The goal of our application is to calculate this signature using HMAC and our secret token.

The shared secret is hardcoded on line 7 for demonstration purposes. Remember, the secret should be stored as an environment variable on your server!

Next, we use the hmac and the hashlib Python modules to create a hashing object on line 9.

The method signature for the new() method is: hmac.new(key, msg=None, digestmod=''):
  • key is set to the secret token encoded in bytes
  • msg is set to the request body encoded in bytes
  • digestmod is set to the SHA-1 hashing algorithm

We get the expected signature on line 14 by encoding the digest of our hashing object using Base64. You might be able to skip this step. You should confirm if the data you receive is encoded using Base64.

On line 16 we compare the signature found in the request with the signature we expect. You typically use the == operator when comparing values in Python. Do not do this here! Heed the following warning found in the Python documentation:
Warning: When comparing the output of digest() to an externally-supplied digest during a verification routine, it is recommended to use the compare_digest() function instead of the == operator to reduce the vulnerability to timing attacks.
On line 25 we combine all of this together and verify the request. We display a thumbs up emoji for authorized users and a red light emoji for bad actors!

Wrapping up

I took a cybersecurity course my last semester in college. I'd be lying if I told you I enjoyed writing C code and setting up Ubuntu virtual machines on my Windows laptop. That being said, it's awesome seeing the theories I learned in school put to practice.

Check out these links with more information on HMAC and webhook security:
]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1528773 2020-04-10T04:51:17Z 2020-06-06T05:48:46Z My initial thoughts on Posthaven

I've been a Posthaven user for less than a week. Here's what I've gathered about the platform:

  • Having limited themes is a good thing - I can focus more on creating content and not spend 10 hours choosing a theme;
  • The editor is rough around the edges - I wish there was support for Markdown and editing links after inserting them is broken;
  • SEO support is lacking - I'm not worried about ranking on Google but I would like the links I share on Slack to look nice;
  • Clicking "Save as Draft" is fun;
It's good enough for me. I can afford the $5 a month and the fee is a good forcing function to get me to write.

It also looks like Posthaven is still being maintained. Their Twitter account is active and one can request features. If you're reading this, you should go vote!

]]>
Santos Solorzano
tag:santos.posthaven.com,2013:Post/1528508 2020-04-08T03:10:06Z 2020-06-06T05:49:19Z When Google and Stack Overflow don't pick up

Larry David knows all about phone etiquette.

Image source: NBC News

Pick up the phone, baby

I recently worked on improving some phone number validation logic at Winnie. We validate a batch of phone numbers and send them off to a third-party service. Some of the numbers we were sending were deemed invalid by the service. This was preventing us from automating some data updates we wanted to run daily. How hard could validating digits be?

Some validation boxes we already checked off included:

  • regex pattern matching to only return digits (e.g. removing non digit characters from 281-330-8004)
  • checking if the value is equal to 10 characters (e.g. 2813308004 has no country code)
  • checking if the value is equal to 11 characters (e.g. 12813308004 has a country code)

An edge case we were not considering were 800 numbers! A code change went out to ignore these type of phone numbers. The next day we were able to send a new batch of phone numbers to the third-party with no issues. Problem solved? Not quite.

Man with a plan

We were still sending them invalid phone numbers. Perfectly-looking phone numbers were being deemed invalid by them. For example, 234-911-5678 is an invalid phone number. How? There are no non-digit characters and it looks like a valid phone number!

It turns out there is something called the North American Numbering Plan. Under the modern plan, a U.S. phone number must adhere to the NPA-NXX-xxxx format. Each component in this format must follow certain rules. The valid range for the first digit in the NPA component is 2-9. The valid range for each digit in the xxxx component is 0-9. 123-234-5678 is invalid because the first digit is a 1. In the example above, 234-911-5678 was invalid because it violated the following rule: the second and third digit in the NXX component can't both be 1.

I was determined to avoid translating these rules to brittle Python code. I knew there had to be a solution we could leverage instead of reinventing the wheel.

1-800-GOOGLE-IT

What does a software engineer do when they're stuck? Turn to Google. Here are some search queries I tried:

  • "npa nxx validator"
  • "npa nxx github"
  • "npa nxx python"
No luck. The Stack Overflow results I got were not what I was looking for. Where was the accepted answer I yearned for? Finally, I Googled "django phone look up". One of the first results was a GitHub link for django-phonenumber-field. I started searching for more of the same terms in this repository: "nxx", "valid", "is_valid". On a side note, the search experience on GitHub has improved tremendously.

I finally found a promising method in the source code:

def is_valid(self):
    """
    checks whether the number supplied is actually valid
    """
    return phonenumbers.is_valid_number(self)

I searched for is_valid_number to get the method definition but got nothing. I realized that phonenumbers was an external package that the project relied on. I immediately Googled the package, skimmed the README and tested it with our invalid phone numbers. It worked! I was confident that this package was enough for our needs and soon it found a home in our requirements.txt file.

I went back and looked at django-phonenumber-field README and saw the following:
The answer to my problem was right there! All I had to do was read the freaking docs.

Can you hear me now?

Would I have saved 5 minutes by skipping straight to the README instead of browsing the source code? Sure. But being able to read code, especially code that you didn't write, is a useful skill. Plus, GitHub has made it even easier to navigate code on their platform. Can you tell I'm a fan of GitHub?

Googling is a skill. Reading source code is a skill. Reading documentation is a skill. Combine these skills with communicating effectively and what do you get? Probably something better than a 10x engineer.


]]>
Santos Solorzano