Monthly Archives: September 2014

My code after running through Jeff Knopp’s unittest tutorial

Ran through Jeff Knopp’s unittest tutorial, which I recommend. This post documents the code I ended up with.

This class–the one to be tested–is the same:

def is_prime(number):
   """
      Is the provided number divisible by any number between two and itself?
   """
   if  number < 0:
      return  False

   for  element in range(2, number):
      if  number % element == 0:
         return  False

   # number is not divisible by anything between 2 and itself
   return  True

The testing class, though, is different. Just tailored to my style:

import unittest
from prime_number_util import is_prime

class PrimesTestCase(unittest.TestCase):
    """
        Tests for prime_number_util.is_prime()
    """

    def test__prime_numbers__zero_through_seven(self):
        """
           Testing all prime numbers, starting with zero, ending with seven.
        """
        self.assert_is_prime(True, 0)
        self.assert_is_prime(True, 1)
        self.assert_is_prime(True, 2)
        self.assert_is_prime(True, 3)
        self.assert_is_prime(True, 5)
        self.assert_is_prime(True, 7)

    def test__non_prime_numbers__negative(self):
        """
           Testing negative numbers, which are never prime.
        """
        self.assert_is_prime(False, -500)
        self.assert_is_prime(False, -5)
        self.assert_is_prime(False, -1)

    def test__non_prime_numbers__four_through_nine(self):
        """
           Testing some positive prime numbers.
        """
        self.assert_is_prime(False, 4)
        self.assert_is_prime(False, 6)
        self.assert_is_prime(False, 8)
        self.assert_is_prime(False, 9)

    def assert_is_prime(self, boolean_expected, number):
        """
            Master function for is_prime testing.
        """
        self.assertEqual(boolean_expected, is_prime(number))

if  __name__ == '__main__':
    unittest.main()
Advertisements

Goodbye TextPad, hello Sublime and Emmet, and back to Chrome

I hesitate to say this, and I’m not feeling confident about it yet, but…

After fifteen great years with TextPad, I’m moving on to Sublime Text. TextPad has been stuck in basically the same version for about a decade now. The most major change–albeit a great one–is an upgraded regex engine, from some POSIX-y thing to a much more powerful, Perl-compatible Boost.

Very sad. So far, though, Sublime is impressive.

I’m also back to Chrome. I tried Firefox, but it just freezes too much. Many (mostly temporary) freezes while reloading heavy pages, or when deleting lots of elements from the bookmark or history manager. Chrome is much smoother. Disappointing.


And speaking of impressive: Holy cow, Emmet!!!

This

div

becomes this

<div></div>

This

div#important_section

becomes this

<div id="important_section"></div>

This

ul>li*3

becomes this

<ul>
   <li></li>
   <li></li>
   <li></li>
</ul>

and this:

!>.container>(nav>(ul>.item{item$}*3)+(url>.item{item$@-4}*3))+.content>(h${Article $}+p.copy$>lorem10^hr)*3

becomes this:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Document</title>
</head>
<body>
   <div class="container">
      <nav>
         <ul>
            <li class="item">item1</li>
            <li class="item">item2</li>
            <li class="item">item3</li>
         </ul>
         <url>
            <div class="item">item6</div>
            <div class="item">item5</div>
            <div class="item">item4</div>
         </url>
      </nav>
      <div class="content">
         <h1>Article 1</h1>
         <p class="copy1">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius, dolores.</p>
         <hr>
         <h2>Article 2</h2>
         <p class="copy2">Voluptates nesciunt vero culpa maxime aliquid voluptatibus labore dolorem laudantium.</p>
         <hr>
         <h3>Article 3</h3>
         <p class="copy3">Dignissimos aliquam ad id incidunt, iusto. Fuga beatae fugit repellat!</p>
         <hr>
      </div>
   </div>
</body>
</html>

And, by golly, it actually makes sense. I can’t believe I’ve never heard of Emmet before. It’s actually already installed into JSFiddle.

The step-by-step tutorial I wish I had when learning Ajax in Django (with JQuery) — Part three of three

In part one of this tutorial, we made a simple “colors” website in which you can search based on substrings, and choose one or more as your favorites (here is a JavaScript-only demonstration). In part two, we updated the like buttons with Ajax, using JQuery. Ajax provides the benefit of reloading only the just-pressed button, instead of the whole page.

[All parts: ONE, TWO, THREE]

In this final part, we’re also going to implement Ajax into the search feature. In addition, we’ll make the search execute automatically on each key-press, and prevent rapid-fire requests, by ignoring key-presses less than one-tenth-of-a-second apart.

(A reminder to backup your work before proceeding.)

Changing the views

In the original code, the search is implemented as a logic branch in the main ColorList view. If a GET is detected

        if(self.request.method == "GET"):

(and there’s at least two characters) then a search is executed. Otherwise, it’s just a "normal" request, without interactivity–in other words: just load the page.

In order to refresh only the search results, that portion of the main template must be separated out into it’s own file, and its corresponding view code must be moved out of the ColorList and into its own (function-based) view.

Do this by replacing the existing single ColorList (class-based) view in
    /home/myname/django_files/django_ajax_demo/color_liker/views.py
with the following two views:

class ColorList(ListView):
    """
    Displays all colors in a table with only two columns: the name
    of the color, and a "like/unlike" button.
    """
    model = Color
    context_object_name = "colors"

    def dispatch(self, request, *args, **kwargs):
        return super(ColorList, self).dispatch(request, *args, **kwargs)

    def get_queryset(self):
        """
        This returns the all colors, for display in the main table.

        The search result query set, if any, is passed as context.
        """
        return  super(ColorList, self).get_queryset()

    def get_context_data(self, **kwargs):
        #Get the current context.
        context = super(ColorList, self).get_context_data(**kwargs)

        context["MIN_SEARCH_CHARS"] = MIN_SEARCH_CHARS

        return  context

def submit_color_search_from_ajax(request):
    """
    Processes a search request, ignoring any where less than two
    characters are provided. The search text is both trimmed and
    lower-cased.

    See <link to MIN_SEARCH_CHARS>
    """

    colors = []  #Assume no results.

    global  MIN_SEARCH_CHARS

    search_text = ""   #Assume no search
    if(request.method == "GET"):
        search_text = request.GET.get("color_search_text", "").strip().lower()
        if(len(search_text) < MIN_SEARCH_CHARS):
            """
            Ignore the search. This is also validated by
            JavaScript, and should never reach here, but remains
            as prevention.
            """
            search_text = ""

    #Assume no results.
    #Use an empty list instead of None. In the template, use
    #   {% if color_search_results.count > 0 %}
    color_results = []

    if(search_text != ""):
        color_results = Color.objects.filter(name__contains=search_text)

    #print('search_text="' + search_text + '", results=' + str(color_results))

    context = {
        "search_text": search_text,
        "color_search_results": color_results,
        "MIN_SEARCH_CHARS": MIN_SEARCH_CHARS,
    };

    return  render_to_response("color_liker/color_search_results__html_snippet.txt",
                               context)

Update the templates

Create the search-results sub-template

[The template from parts one and two]

In order to render the search results separately, they must be moved from the main template into its own sub-template.

Save the following as
    /home/myname/django_files/django_ajax_demo/color_liker/color_liker/templates/color_liker/color_search_results__html_snippet.txt

{% if  search_text|length >= MIN_SEARCH_CHARS %}
   <p><b>Searching for &quot;<code>{{ search_text }}</code>&quot;:</b>
   {% if  color_search_results.count > 0 %}
      </p>
      <ul>
         {% for  color in color_search_results %} {# No colon after "color_search_results" #}
            <li>{{ color.name }}</li>
         {% endfor %}
      </ul>
   {% else %}
      <i>No results</i></p>
   {% endif %}
{% endif %}

Change the main template

The changes:

  1. The search-results are no longer in the main template,
  2. Instead, they are now represented in the main template by a div, which is populated (and un-populated) by the yet-to-be-created JavaScript
  3. The search form is no longer a form–it’s only a text box,
  4. The JavaScript imports are different, and require two additional Django variables to be assigned to JavaScript variables, before the imports.

Replace the contents of
    /home/myname/django_files/django_ajax_demo/color_liker/templates/color_liker/color_list.html
with

{% comment %}
   humanize:
      For the "apnumber" filter, to display "two" instead of
      "2". Requries 'django.contrib.humanize' in INSTALLED_APPS

   static:
      To access the public static file directory, without having to hard
      code urls.
{% endcomment %}
{% load humanize %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Color Likenatorizer</title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta name="viewport" content="width=device-width"/>
      <link rel="stylesheet" type="text/css" href="{% static 'stylesheet.css' %}">
   </head>
<body>
   <div class="search_section">
      <!--
         The form is submitted on every key press ("keyup") to the ajax
         function. If the number of characters is greater than
         MIN_SEARCH_CHARS, then it is submitted to the Django view. Django
         then renders the sub-template

         color_search_results__html_snippet.txt

         whose output is fed back to the ajax function. Ajax then populates
         the rendered sub-template into the below div.

         This no longer needs to be a form since the JavaScript directly
         reads both fields (that is, it attaches event listeners to them,
         which automatically react to key-presses). To be clearer, I've
         added "color_" to the beginning of the text field's id.

         Notes:
            - csrf_token-s are not required in get requests.
            - Only because the view expects a GET request, the search may
            be directly tested with
               http://my.website/color_liker/search?color_search_text=bl
      -->
      <input type="text" id="color_search_text" name="search_text"/>
      <p>(Requires {{ MIN_SEARCH_CHARS|apnumber }} or more characters)</p>
      <div id="color_search_results"></div>
   </div>
   <div class="content_section">
      <h1>Color Likenatorizer</h1>

      {% if  colors.count == 0 %}
         <p><i>There are no colors in the database.</i></p>
      {% else %}
         <table>
            <tr>
               <th colspan="2">Color</th>
               <th>Favorite?</th>
            </tr>
               <!--
                  The yes/no like button is contained in the third column.
                  The stylesheet eliminates the button's border and expands
                  its width and height to 100%, so it fills its entire
                  container: the table cell. It therefore appears to *be*
                  the table cell.

                  The table cell is in this main template, the button in
                  it, is in the "include"d sub-template. The button
                  sub-template is used by the below for-loop, and also by
                  the toggle_color_like view, which is called by Ajax.

                  Ajax calls Django, which renders the sub-template and
                  feeds it back to Ajax, which then replaces the current
                  button/sub-template with the new one.

                  (The data-color_id is how the id is passed to JQuery. See
                  http://api.jquery.com/data/ )
               -->
            {% for  color in colors %} {# No colon after "colors" #}
               <tr>
                  <td style="background-color: {{ color.name }};" class="td__color_color">{{ color.name }}</td>
                  <td class="td__color_name">{{ color.name }}</td>
                  <td id="toggle_color_like_cell_{{ color.id }}" class="td__toggle_color_like_button" data-color_id="{{ color.id }}">
                     {% include "color_liker/color_like_link__html_snippet.txt" %}
                  </td>
               </tr>
            {% endfor %}
         </table>
      {% endif %}
   </div>
   <script type='text/javascript' src="{% static 'jquery-1.11.1.min.js' %}"></script>
   <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
   <script language="javascript">
      /*
         Before our JavaScript can be imported, the following JavaScript
         variables need to be set from Django variables. While these
         values could be hard-coded into the JavaScript, this allows
         them to be centrally located.
      */
      //From color_liker.views.MIN_SEARCH_CHARS
      var MIN_SEARCH_CHARS = {{ MIN_SEARCH_CHARS }};

      //The url to submit the search form. From color_liker.urls
      var SUBMIT_URL = "{% url 'color_list' %}";

      /*
         the url to toggle the like. From color_liker.urls

         Since an id must be provided to the Django url, give it a
         bogus one, then immediately lop it off (along with the
         ending '/'). It is re-appended by the JavaScript.
      */
      var LIKE_URL_PRE_ID = "{% url 'toggle_color_like' color_id='999999999' %}"
      LIKE_URL_PRE_ID = LIKE_URL_PRE_ID.substring(0, LIKE_URL_PRE_ID.length - "999999999/".length);
   </script>
   <script type='text/javascript' src="{% static 'color_ajax_search.js' %}"></script>
   <script type='text/javascript' src="{% static 'color_ajax_like.js' %}"></script>
   <script type='text/javascript' src="{% static 'color_ajax_main.js' %}"></script>
</body></html>

Update the URL configuration

There is a new view for the search form, so a new url must point to it. Replace the contents of
    /home/myname/django_files/django_ajax_demo/color_liker/urls.py
with

from django.conf.urls  import patterns, include, url
from color_liker.views import ColorList

urlpatterns = patterns('',
    url(r"^$", ColorList.as_view(), name="color_list"),
    url(r"^like_color_(?P<color_id>\d+)/$", "color_liker.views.toggle_color_like", name="toggle_color_like"),
    url(r"^search/$", "color_liker.views.submit_color_search_from_ajax", name="color_list"),
)

Ajax: JavaScript

The JavaScript for the search feature will be placed in a new file. Before creating it, lets split the single existing JavaScript file, containing both the like button and "main" functionality, into two. While all this JavaScript code could easily be placed into a single file (without comments or empty lines, there’s only about 50 lines of code), I find it clearer to split them into three: main, like, and search.

This code now uses Underscore.js. The original code doesn’t.

File one: The like button

The "main" function is moved out of this file, and into the one below.

File name:
    /home/myname/django_files/django_ajax_demo/static/color_ajax_like.js

//THIS FILE MUST BE IMPORTED BEFORE THE "main" FILE.

/**
   Executes a like click. Triggered by clicks on the various yes/no links.
 */
var processLike = function()  {

   //In this scope, "this" is the button just clicked on.
   //The "this" in processServerResponse is *not* the button just clicked
   //on.
   var $button_just_clicked_on = $(this);

   //The value of the "data-color_id" attribute.
   var color_id = $button_just_clicked_on.data('color_id');

   var processServerResponse = function(sersverResponse_data, textStatus_ignored,
                            jqXHR_ignored)  {
      //alert("sf sersverResponse_data='" + sersverResponse_data + "', textStatus_ignored='" + textStatus_ignored + "', jqXHR_ignored='" + jqXHR_ignored + "', color_id='" + color_id + "'");
      $('#toggle_color_like_cell_' + color_id).html(sersverResponse_data);
   }

   var config = {
      url: LIKE_URL_PRE_ID + color_id + '/',
      dataType: 'html',
      success: processServerResponse
      //Should also have a "fail" call as well.
   };
   $.ajax(config);
};

File two: The "main" function

This includes the original main function, with the addition of one that attaches a key-press listener to the search text box. The new function is called by both the main and the below search-JavaScript file.

Save the following as:
    /home/myname/django_files/django_ajax_demo/static/color_ajax_main.js

//THIS MUST BE IMPORTED AS THE VERY LAST THING BEFORE THE CLOSE </body>
//tag.

/**
  The number of milliseconds to ignore key-presses in the search box,
  after a key *that was not ignored* was pressed. Used by
  `$(document).ready()`.

  Equal to <code>100</code>.
 */
var MILLS_TO_IGNORE_SEARCH = 100;
/**
  The number of milliseconds to ignore clicks on the *same* like
  button, after a button *that was not ignored* was clicked. Used by
  `$(document).ready()`.

  Equal to <code>500</code>.
 */
var MILLS_TO_IGNORE_LIKES = 500;
/**
   The Ajax "main" function. Attaches the listeners to the elements on
   page load, each of which only take effect every
   <link to MILLS_TO_IGNORE_SEARCH> or <link to MILLS_TO_IGNORE_LIKES>
   seconds.

   This protection is only against a single user pressing buttons as fast
   as they can. This is in no way a protection against a real DDOS attack,
   of which almost 100% bypass the client (browser) (they instead
   directly attack the server). Hence client-side protection is pointless.

   - http://stackoverflow.com/questions/28309850/how-much-prevention-of-rapid-fire-form-submissions-should-be-on-the-client-side

   The protection is implemented via Underscore.js' debounce function:
  - http://underscorejs.org/#debounce

   Using this only requires importing underscore-min.js. underscore-min.map
   is not needed.
 */
$(document).ready(function()  {
  /*
    Warning: Placing the true parameter outside of the debounce call:

    $('#color_search_text').keyup(_.debounce(processSearch,
        MILLS_TO_IGNORE_SEARCH), true);

    results in "TypeError: e.handler.apply is not a function"
   */
  $('#color_search_text').keyup(_.debounce(processSearch,
      MILLS_TO_IGNORE_SEARCH, true));
  /*
    There are many buttons having the class

      td__toggle_color_like_button

    This attaches a listener to *every one*. Calling this again
    would attach a *second* listener to every button, meaning each
    click would be processed twice.
   */
  $('.td__toggle_color_like_button').click(_.debounce(processLike,
      MILLS_TO_IGNORE_LIKES, true));
});

File three: The search feature

Save the following as:
    /home/myname/django_files/django_ajax_demo/static/color_ajax_search.js

///THIS FILE MUST BE IMPORTED BEFORE THE "main" FILE.
/**
  Executes a search for colors containing a substring.
 */
var processSearch = function()  {
  //The key-press listener is no longer attached

  //Get and trim the search text.
  var searchText = $('#color_search_text').val().trim().toLowerCase();

  if(searchText.length < MIN_SEARCH_CHARS)  {
    //Too short. Ignore the submission, and erase any current results.
    $('#color_search_results').html("");

  }  else  {
    //There are at least two characters. Execute the search.

    var processServerResponse = function(sersverResponse_data, textStatus_ignored,
                        jqXHR_ignored)  {
      //alert("sersverResponse_data='" + sersverResponse_data + "', textStatus_ignored='" + textStatus_ignored + "', jqXHR_ignored='" + jqXHR_ignored + "'");
      $('#color_search_results').html(sersverResponse_data);
    }

    var config = {
      /*
        Using GET allows you to directly call the search page in
        the browser:

        http://the.url/search/?color_search_text=bl

        Also, GET-s do not require the csrf_token
       */
      type: "GET",
      url: SUBMIT_URL,
      data: {
        'color_search_text' : searchText,
      },
      dataType: 'html',
      success: processServerResponse
    };
    $.ajax(config);
  }
};

That’s it! Give it a try!

Start your server, and go to
    http://my.website/color_liker

Once again, you should see something like this. The obvious difference is the page no longer reloads on each search request. To see the "rapid-fire request protection" in action, dramatically increase the value of MILLS_TO_IGNORE_SEARCH.

[All parts: ONE, TWO, THREE]


Thank you for going through this tutorial.

If you need help or have any suggestions, please leave a comment below. I’ll be glad to assist you.

The step-by-step tutorial I wish I had when learning Ajax in Django (with JQuery) — Part two of three

In the previous post, we created a simple Django “colors” website (here is a JavaScript-only demonstration), which allows you to search the colors by substring, and to declare one or more to be your favorite. Currently, every action taken causes the entire page to be reloaded.

[All parts: ONE, TWO, THREE]

Now let’s change it so only the portion being changed–the search-results or the pressed like button–is refreshed. This will be achieved by implementing Ajax with JQuery. In addition:

  • Instead of having to press the "search" button, a new search will be executed automatically at every key-press.
  • We’ll implement some modest protection against rapid-fire requests by
    • Ignoring key-presses on the search form if they come less than 100 milliseconds after the previous.
    • Ignoring clicks on the same like button, if it occurs less than one-half second after the most-recently-processed click.

In this post, we’ll do some necessary preparations and update the like buttons. In the next post, we’ll update the search feature.

(A reminder to backup your work before proceeding.)

Create a public static-file directory

Since these changes require a substantial amount of JavaScript, it will be placed in a publicly-available and static location. Because of this, we might as well also move the stylesheet out of the web page and into the same directory. Finally, although we could import JQuery directly from the internet, I’m choosing download it locally (version 1.11.1).

Below is my setup. You’ll have to tailor these instructions as necessary for your system.

In
    /home/myname/django_files/django_ajax_demo/django_ajax_demo/settings.py
create-or-replace the following variables, and then, if you have an Nginx server, follow the instructions in the comments:

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/

STATIC_URL = '/color_liker/static/'
STATIC_ROOT = ""
"""
Put the following in
    `/etc/nginx/sites-available/django_ajax_demo`
----------------------------------------
server {
    server_name my.websites.ip.or.domain;

    access_log on;

    #Django JQuery Demo...START

    #Static images, js, css
    location /color_liker/static/ {
       alias /home/myname/django_files/django_ajax_demo/static/;
    }

    #Static images for the admin
    location /color_liker/static/admin/ {
       alias /home/myname/django_files/django_ajax_demo_venv/lib/python3.4/site-packages/django/contrib/admin/static/admin/;
    }

    #Django JQuery Demo...END

   location / {
      proxy_pass http://127.0.0.1:8001;
      proxy_set_header X-Forwarded-Host $server_name;
      proxy_set_header X-Real-IP $remote_addr;
      add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
   }
}
----------------------------------------

This prevents nginx passing the request to the WSGI/Django app
server. Static files need no processing, so nginx handles them
directly.
"""
STATICFILES_DIRS = (
    #Put strings here, like "/home/html/static" or "C:/www/django/static".
    #Always use forward slashes, even on Windows.
    #Don't forget to use absolute paths, not relative paths.
    ("assets", BASE_DIR + "/static"),
    os.path.join('static'),
)

Create the directory
    /home/myname/django_files/django_ajax_demo/static/
and restart your server.

To confirm your setup, create a file named
    /home/myname/django_files/django_ajax_demo/static/temp.txt
containing something. Then in your browser, you should be able to see that text at the url
    http://my.website/color_liker/static/temp.txt

Delete the file before proceeding.

Download JQuery 1.11.1 into the just-created static directory

Download link (right-click and "Save as…"): http://code.jquery.com/jquery-1.11.1.min.js

Changing the views

File:
    /home/myname/django_files/django_ajax_demo/color_liker/views.py

In the original toggle_color_like function, once a like is processed (saved to the database), the request is then redirected to the main ColorList view:

return  redirect("color_list")  #See urls.py

This results in the entire page being reloaded, thus reflecting the new like-state. In order to refresh only the like button, that part of the template must be moved into its own file (as described below). For the view to directly render it, the above redirect line should be changed to

    #Render the just-clicked-on like-button.
    return  render_to_response("color_liker/color_like_link__html_snippet.txt",
                               {"color": color})

As a consequence of this change, the import statement at the top of the same file, which used to be

from django.shortcuts     import redirect

must be changed to

from django.shortcuts     import render_to_response

Update the template and stylesheet

[The original template]

In order to individually refresh the just-clicked-on like button, it needs to be moved out of the main template and into its own file.

<button class="button_{% if not color.is_favorited %}un{% endif %}liked"
   >{% if color.is_favorited %}Yes{% else %}No{% endif %}</button>

This sub-template is used by both the main page load, as it creates the color table, and by the Ajax call, for refreshing a single color. Save it as
    /home/myname/django_files/django_ajax_demo/color_liker/templates/color_liker/color_like_link__html_snippet.txt

The main template

There are three changes in the template:

  1. The like button is now "include"-d from a sub-template,
  2. The static directory is "load"-ed and used to import both JQuery and our yet-to-be-created JavaScript
  3. The stylesheet has also been moved to the static directory (and somewhat updated).

Replace the contents of
    /home/myname/django_files/django_ajax_demo/color_liker/templates/color_liker/color_list.html
with

{% comment %}
   humanize:
      For the "apnumber" filter, to display "two" instead of
      "2". Requries 'django.contrib.humanize' in INSTALLED_APPS

   static:
      To access the public static file directory, without having to hard
      code urls.
{% endcomment %}
{% load humanize %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Color Likenatorizer</title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta name="viewport" content="width=device-width"/>
      <link rel="stylesheet" type="text/css" href="{% static 'stylesheet.css' %}">
   </head>
<body>
   <div class="search_section">
      <form id="search_colors_form_id" method="get" action="{% url 'color_list' %}">
         <input type="text" id="search_text" name="search_text"/>
         {# csrf_token is not needed when the method is "get" #}
         <input id="id_pic_submit_button" type="submit" value="Search for color"/>
         <p>(Requires {{ MIN_SEARCH_CHARS|apnumber }} or more characters)</p>
      </form>

      {% if  search_text|length >= MIN_SEARCH_CHARS %}
         <p><b>Searching for &quot;<code>{{ search_text }}</code>&quot;:</b>
         {% if  color_search_results.count > 0 %}
            </p>
            <ul>
               {% for  color in color_search_results %} {# No colon after "color_search_results" #}
                  <li>{{ color.name }}</li>
               {% endfor %}
            </ul>
         {% else %}
            <i>No results</i></p>
         {% endif %}
      {% endif %}
   </div>
   <div class="content_section">
      <h1>Color Likenatorizer</h1>

      {% if  colors.count == 0 %}
         <p><i>There are no colors in the database.</i></p>
      {% else %}
         <table>
            <tr>
               <th colspan="2">Color</th>
               <th>Favorite?</th>
            </tr>
               <!--
                  The yes/no like button is contained in the third column.
                  The stylesheet eliminates the button's border and expands
                  its width and height to 100%, so it fills its entire
                  container: the table cell. It therefore appears to *be*
                  the table cell.

                  The table cell is in this main template, the button in
                  it, is in the "include"d sub-template. The button
                  sub-template is used by the below for-loop, and also by
                  the toggle_color_like view, which is called by Ajax.

                  Ajax calls Django, which renders the sub-template and
                  feeds it back to Ajax, which then replaces the current
                  button/sub-template with the new one.

                  (The data-color_id is how the id is passed to JQuery.
                   See http://api.jquery.com/data/ )
               -->
            {% for  color in colors %} {# No colon after "colors" #}
               <tr>
                  <td style="background-color: {{ color.name }};" class="td__color_color">{{ color.name }}</td>
                  <td class="td__color_name">{{ color.name }}</td>
                  <td id="toggle_color_like_cell_{{ color.id }}" class="td__toggle_color_like_button" data-color_id="{{ color.id }}">
                     {% include "color_liker/color_like_link__html_snippet.txt" %}
                  </td>
               </tr>
            {% endfor %}
         </table>
      {% endif %}
   </div>
   <script type='text/javascript' src="{% static 'jquery-1.11.1.min.js' %}"></script>
   <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
   <script language="javascript">
      /*
         Before our JavaScript can be imported, the following JavaScript
         variable (the url to toggle the like) needs to be set from the
         Django variable. While this could be hard-coded into the
         JavaScript, this allows it to be centrally located (in
         color_liker.urls).

         Since an id must be provided to the Django url, give it a
         bogus one, then immediately lop it off (along with the
         ending '/'). It is re-appended by the JavaScript.
       */
      var LIKE_URL_PRE_ID = "{% url 'toggle_color_like' color_id='999999999' %}"
      LIKE_URL_PRE_ID = LIKE_URL_PRE_ID.substring(0, LIKE_URL_PRE_ID.length - "999999999/".length);
   </script>
   <script type='text/javascript' src="{% static 'color_ajax_like.js' %}"></script>
</body></html>

The stylesheet

Save the following as
    /home/myname/django_files/django_ajax_demo/static/stylesheet.css

.search_section  {
   float: left;
}
.content_section  {
   float: left;
}
table  {
   border:1px solid #000;
   border-spacing: 0px;
   background-color: #EEE;
   border-collapse:collapse;
}
thead  {
   font-weight: bold;
   text-decoration: underline;
}
th  {
   border:1px solid #000;
   padding: 4px;
}
/*
   All table cells except for the yes/no buttons, should have 4px of
   padding.

   The yes/no-buttons should completely fill the cell, so the cell
   itself (appears to) behave like a button.
 */
td  {
   text-align: center;
   vertical-align: middle;
   padding: 4px;
   border: 1px solid #000;
}
.td__toggle_color_like_button  {
   padding: 0px;
}
button  {
   border: 0px;
   /*
      So the buttons completely fill their containers--the table cell.
    */
   width: 100%;
   height: 100%;
}
.td__color_color  {
   text-align: right;
}
.td__color_name  {
   text-align: left;
}
.td__color_is_liked  {
   text-align: center;
}
.button_liked  {
   background-color: #CD0;
}
.button_unliked  {
   background-color: white;
}

The Ajax/JQuery JavaScript

The below code implements Ajax by attaching "click" events to each like button, and intercepting those clicks to decide if they should be forwarded onto Django. If the click is too soon (since the last actually-processed click) it is ignored. It then receives the response from Django, and uses it to update (a specific portion of) the web page.

The benefit of using JQuery to do these things, is that the same code works for all browsers.

Without Ajax:

With Ajax:

Save the following as
    /home/myname/django_files/django_ajax_demo/static/color_ajax_like.js

This code now uses Underscore.js. The original code doesn’t.

/*
  This file must be imported immediately-before the close-</body> tag,
  and after JQuery and Underscore.js are imported.
*/
/**
  The number of milliseconds to ignore clicks on the *same* like
  button, after a button *that was not ignored* was clicked. Used by
  `$(document).ready()`.

  Equal to <code>500</code>.
 */
var MILLS_TO_IGNORE_LIKES = 500;
/**
   Executes a like click. Triggered by clicks on the various yes/no links.
 */
var processLike = function()  {

   //In this scope, "this" is the button just clicked on.
   //The "this" in processServerResponse is *not* the button just clicked
   //on.
   var $button_just_clicked_on = $(this);

   //The value of the "data-color_id" attribute.
   var color_id = $button_just_clicked_on.data('color_id');

   var processServerResponse = function(sersverResponse_data, textStatus_ignored,
                            jqXHR_ignored)  {
      //alert("sf sersverResponse_data='" + sersverResponse_data + "', textStatus_ignored='" + textStatus_ignored + "', jqXHR_ignored='" + jqXHR_ignored + "', color_id='" + color_id + "'");
      $('#toggle_color_like_cell_' + color_id).html(sersverResponse_data);
   }

   var config = {
      url: LIKE_URL_PRE_ID + color_id + '/',
      dataType: 'html',
      success: processServerResponse
      //Should also have a "fail" call as well.
   };
   $.ajax(config);
};
/**
   The Ajax "main" function. Attaches the listeners to the elements on
   page load, each of which only take effect every
   <link to MILLS_TO_IGNORE_LIKES> seconds.

   This protection is only against a single user pressing buttons as fast
   as they can. This is in no way a protection against a real DDOS attack,
   of which almost 100% bypass the client (browser) (they instead
   directly attack the server). Hence client-side protection is pointless.

   - http://stackoverflow.com/questions/28309850/how-much-prevention-of-rapid-fire-form-submissions-should-be-on-the-client-side

   The protection is implemented via Underscore.js' debounce function:
  - http://underscorejs.org/#debounce

   Using this only requires importing underscore-min.js. underscore-min.map
   is not needed.
 */
$(document).ready(function()  {
  /*
    There are many buttons having the class

      td__toggle_color_like_button

    This attaches a listener to *every one*. Calling this again
    would attach a *second* listener to every button, meaning each
    click would be processed twice.
   */
  $('.td__toggle_color_like_button').click(_.debounce(processLike,
      MILLS_TO_IGNORE_LIKES, true));
  /*
    Warning: Placing the true parameter outside of the debounce call:

    $('#color_search_text').keyup(_.debounce(processSearch,
        MILLS_TO_IGNORE_SEARCH), true);

    results in "TypeError: e.handler.apply is not a function".
   */
});

That’s it! Give it a try!

Start your server, and go to
    http://my.website/color_liker

Once again, you should see something like this. Notice that clicking on the like buttons no longer reloads the page, and clicking as fast as you can on any one (like) button will not hit the server more than twice a second.

[All parts: ONE, TWO, THREE]

(If you need help, leave a comment!)

Update the search feature to Ajax

Now let’s implement Ajax into the search feature.

…to be continued…

(cue cliffhanger segue music)

The step-by-step tutorial I wish I had when learning Ajax in Django (with JQuery) — Part one of three

This tutorial demonstrates implementing Ajax in Django, using JQuery, for two specific tasks:

  1. A search box that executes on each key press.
  2. A like button that says "yes" if it’s liked, and "no" if it’s not.

[All parts: ONE, TWO, THREE]

Updated 2/5/2015:

Take a look at a mockup of the website we’re going to be creating, which is written in Knockout. (Here it is again as a JSFiddle.)

In addition, we’ll add in some basic protection against rapid-fire requests, by preventing the search form from submitting until at least two characters are provided, and by ignoring clicks of the same like-button if it is pressed again within a half-second.

First we’ll create the website without Ajax, and then we’ll integrate the Ajax calls.

Learning Django and JQuery by themselves

Before going through this tutorial, you’ll need a basic understanding of both Django (and Python!) and JQuery (and JavaScript!).

This is how I learned Django. My Django support is primarily on irc #django and by asking questions on Stack Overflow.

I learned Python by reading the official tutorial and Dive Into Python 3, running through the Coding Bat and codecademy tutorials, and especially by immersing myself in Django.

I learned JQuery by reading much of the documentation on the JQuery Learning Center, and then by running through the codecademy tutorial. I’m familiar with JavaScript, but have not professionally worked with it, so I read through the HTML Dog JavaScript tutorials (only read them) and also ran through the codecademy tutorial.

Depending on your background, all of these things are highly recommended.

Now…

My setup

  • Operating system: Ubuntu 14.04.1 x32
  • Web server: Nginx 1.4.6
  • Database: Postgres 9.3.5
  • WSGI app server: Gunicorn 19.1.0
  • Django 1.7
  • Python 3.4.0
  • JQuery 1.11.1

I have Django installed into a virtualenv that uses Python 3.4 only. Here are detailed instructions for installing Nginx, Postgres, Gunicorn, and Django onto a Digital Ocean web server.

My directory structure is based on these two folders:

  • The virtualenv directory, which is installed as per the above “detailed instructions” link:
        /home/myname/django_files/django_ajax_demo_venv
  • and Django project directory, which you should create now:
        /home/myname/django_files/django_ajax_demo

Install the Django project

  1. Start your virtualenv:
        source /home/myname/django_files/django_ajax_demo_venv/bin/activate     (exit it with deactivate)
  2. Create the project directory:
        mkdir /home/myname/django_files/django_ajax_demo/
  3. Create your project (this is a long command that belongs on a single line):
        django-admin.py startproject django_ajax_demo /home/myname/django_files/django_ajax_demo/

  4. Create the sub-application:
    1. cd /home/myname/django_files/django_ajax_demo/
    2. python manage.py startapp color_liker

     
    This and the previous command create the following (items unused by this tutorial are omitted):

    $ tree /home/myname/django_files/django_ajax_demo/
    +-- color_liker
    ¦   +-- admin.py
    ¦   +-- models.py
    ¦   +-- views.py
    +-- django_ajax_demo
    ¦   +-- settings.py
    ¦   +-- urls.py
    +-- manage.py
  5. In
        /home/myname/django_files/django_ajax_demo/django_ajax_demo/settings.py

    1. Add 'django.contrib.humanize' and 'color_liker' to INSTALLED_APPS ('humanize' is used in the template)
    2. Configure your database by overwriting the current value with
      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.postgresql_psycopg2',
              'NAME': 'database_name_here',
              'USER': 'database_username_here',
              'PASSWORD': 'database_user_password_goes_here',
              'HOST': "localhost",  # Empty for localhost through domain sockets or
                                    # '127.0.0.1' for localhost through TCP.
              'PORT': '',           # Set to empty string for default.
          }
      }

Create your model

Replace the contents of
    /home/myname/django_files/django_ajax_demo/color_liker/models.py
with

from django.db import models

class Color(models.Model):
    """
    The color's name (as used by the CSS 'color' attribute, meaning
    lowercase values are required), and a boolean of whether it's "liked"
    or not. There are NO USERS in this demo webapp, which is why there's no
    link/ManyToManyField to the User table.

    This implies that the website is only useable for ONE USER. If multiple
    users used it at the same time, they'd be all changing the same values
    (and would see each others' changes when they reload the page).
    """
    name = models.CharField(max_length=20)
    is_favorited = models.BooleanField(default=False)

    def __str__(self):
        return  self.name

    class Meta:
        ordering = ['name']

Register it with the admin app by replacing the contents of
    /home/myname/django_files/django_ajax_demo/color_liker/admin.py
with

from django.contrib import admin
from .models import Color

admin.site.register(Color)

and then sync it to the database:

  1. python manage.py makemigrations
  2. python manage.py migrate

Insert data into the database

If data already exists, delete it via the admin app (http://my.website/admin).

$ source /home/myname/django_files/django_ajax_demo_venv/bin/activate
$ cd /home/myname/django_files/django_ajax_demo/
$ python manage.py shell
>>> from color_liker.models import Color
>>> Color.objects.all()
[]
>>> colors = ["aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "orange", "purple", "red", "silver", "teal", "white", "yellow"]
>>> Color.objects.bulk_create(Color(name=name) for name in colors)
<generator object <genexpr> at 0xb5f8bacc>
>>> Color.objects.all()
[<Color: aqua>, <Color: black>, <Color: blue>, <Color: fuchsia>, <Color: gray>, <Color: green>, <Color: lime>, <Color: maroon>, <Color: navy>, <Color: olive>, <Color: orange>, <Color: purple>, <Color: red>, <Color: silver>, <Color: teal>, <Color: white>, <Color: yellow>]

The views

There are two views. The main class-based view that handles both the normal page load and search form submission, and a function-based view that handles the toggling of a color’s like-state.

Replace the contents of
    /home/myname/django_files/django_ajax_demo/color_liker/views.py
with

from django.shortcuts     import redirect
from django.views.generic import ListView
from color_liker.models   import Color

MIN_SEARCH_CHARS = 2
"""
The minimum number of characters required in a search. If there are less,
the form submission is ignored. This value is used by the below view and
the template.
"""
class ColorList(ListView):
    """
    Displays all colors in a table with only two columns: the name of the
    color, and a "like/unlike" button.
    """
    model = Color
    context_object_name = "colors"

    def dispatch(self, request, *args, **kwargs):
        self.request = request     #So get_context_data can access it.
        return super(ColorList, self).dispatch(request, *args, **kwargs)

    def get_queryset(self):
        """
        Returns the all colors, for display in the main table. The search
        result query set, if any, is passed as context.
        """
        return  super(ColorList, self).get_queryset()

    def get_context_data(self, **kwargs):
        #The current context.
        context = super(ColorList, self).get_context_data(**kwargs)

        global  MIN_SEARCH_CHARS

        search_text = ""   #Assume no search
        if(self.request.method == "GET"):
            """
            The search form has been submitted. Get the search text from
            it. If it's less than MIN_SEARCH_CHARS characters, ignore the
            request.

            Must be GET, not post.
            - http://stackoverflow.com/questions/25878993/django-view-works-with-default-call-but-form-submission-to-same-view-only-calls

            Also, must use

                if(self.request.method == "GET")

            not

                if(self.request.GET)

            https://docs.djangoproject.com/en/1.7/ref/request-response/#django.http.HttpRequest.method
            https://docs.djangoproject.com/en/1.7/ref/request-response/#django.http.HttpRequest.POST
            """
            search_text = self.request.GET.get("search_text", "").strip().lower()
            if(len(search_text) < MIN_SEARCH_CHARS):
                search_text = ""   #Ignore search

        if(search_text != ""):
            color_search_results = Color.objects.filter(name__contains=search_text)
        else:
            #An empty list instead of None. In the template, use
            #  {% if color_search_results.count > 0 %}
            color_search_results = []

        #Add items to the context:

        #The search text for display and result set
        context["search_text"] = search_text
        context["color_search_results"] = color_search_results

        #For display under the search form
        context["MIN_SEARCH_CHARS"] = MIN_SEARCH_CHARS

        return  context

def toggle_color_like(request, color_id):
    """Toggle "like" for a single color, then refresh the color-list page."""
    color = None
    try:
        #There's only one object with this id, but this returns a list
        #of length one. Get the first (index 0)
        color = Color.objects.filter(id=color_id)[0]
    except Color.DoesNotExist as e:
        raise  ValueError("Unknown color.id=" + str(color_id) + ". Original error: " + str(e))

    #print("pre-toggle:  color_id=" + str(color_id) + ", color.is_favorited=" + str(color.is_favorited) + "")

    color.is_favorited = not color.is_favorited
    color.save()  #Commit the change to the database

    #print("post-toggle: color_id=" + str(color_id) + ", color.is_favorited=" + str(color.is_favorited) + "")

    return  redirect("color_list")  #See urls.py

The template

Create the directory
    Q:\django_files\django_ajax_demo\color_liker\templates\color_liker\
(yes, with the redundant "color_liker") and in it create a file named color_list.html, with these contents:

{% comment %}
   humanize: For the "apnumber" filter, to display "two" instead of
   "2". Requries 'django.contrib.humanize' in INSTALLED_APPS
{% endcomment %}
{% load humanize %}
<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Color Likenatorizer</title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta name="viewport" content="width=device-width"/>
      <style type="text/css">
         .search_section  {
            float: left;
         }
         .content_section  {
            float: left;
         }
         table  {
            border:1px solid #000;
            border-spacing: 0px;
            background-color: #EEE;
            border-collapse:collapse;
         }
         thead  {
            font-weight: bold;
            text-decoration: underline;
         }
         th  {
            border:1px solid #000;
            padding: 4px;
         }
         td  {
            text-align: center;
            vertical-align: middle;
            padding: 4px;
            border:1px solid #000;
         }
         .td__color_color  {
            text-align: right;
         }
         .td__color_name  {
            text-align: left;
         }
         .liked  {
            text-align: center;
            background-color: #CD0;
         }
         .unliked  {
            text-align: center;
            background-color: white;
         }
      </style>
   </head>
<body>
   <div class="search_section">
      <form id="search_colors_form_id" method="get" action="{% url 'color_list' %}">
         <input type="text" id="search_text" name="search_text"/>
         {# csrf_token is not needed when the method is "get" #}
         <input id="id_pic_submit_button" type="submit" value="Search for color"/>
         <p>(Requires {{ MIN_SEARCH_CHARS|apnumber }} or more characters)</p>
      </form>

      {% if  search_text|length >= MIN_SEARCH_CHARS %}
         <p><b>Searching for &quot;<code>{{ search_text }}</code>&quot;:</b>
         {% if  color_search_results.count > 0 %}
            </p>
            <ul>
               {% for  color in color_search_results %} {# No colon after "color_search_results" #}
                  <li>{{ color.name }}</li>
               {% endfor %}
            </ul>
         {% else %}
            <i>No results</i></p>
         {% endif %}
      {% endif %}
   </div>
   <div class="content_section">
      <h1>Color Likenatorizer</h1>

      {% if  colors.count == 0 %}
         <p><i>There are no colors in the database.</i></p>
      {% else %}
         <table>
            <tr>
               <th colspan="2">Color</th>
               <th>Favorite?</th>
            </tr>
            {% for  color in colors %} {# No colon after "colors" #}
               <tr>
                  <td style="background-color: {{ color.name }};" class="td__color_color"
                     >{{ color.name }}</td>
                  <td class="td__color_name">{{ color.name }}</td>
                  <td class="{% if not color.is_favorited %}un{% endif %}liked"
                     ><a href="{% url 'toggle_color_like' color.id %}"
                     >{% if color.is_favorited %}Yes{% else %}No{% endif %}</a></td>
               </tr>
            {% endfor %}
         </table>
      {% endif %}
   </div>
   <script language="javascript">
      document.getElementById("search_text").focus();
   </script>
</body></html>

Finally, configure the urls

Save the following text as
    /home/myname/django_files/django_ajax_demo/color_liker/urls.py

from django.conf.urls  import patterns, include, url
from color_liker.views import ColorList

urlpatterns = patterns('',
    #Used as both the main page url, and for the search-form submission.
    #If the GET object exists, then the search-form is being submitted.
    #Otherwise, it's a normal page request.
    url(r"^$", ColorList.as_view(), name="color_list"),

    url(r"^like_color_(?P<color_id>\d+)/$", "color_liker.views.toggle_color_like", name="toggle_color_like"),
)

and replace the contents of
    /home/myname/django_files/django_ajax_demo/django_ajax_demo/urls.py
with

from django.conf.urls import patterns, include, url
from django.contrib   import admin

urlpatterns = patterns('',
    (r'^color_liker/', include('color_liker.urls')),
    url(r'^admin/', include(admin.site.urls)),
)

Give it a try!

  1. Start the webserver:
    • For Nginx/Gunicorn (may be executed in or out of the virtualenv):
      1. sudo service nginx start
      2. sudo /home/myname/django_files/django_ajax_demo_venv/bin/gunicorn -c /home/myname/django_files/django_ajax_demo_venv/gunicorn_config.py django_ajax_demo.wsgi:application     (the contents of the config file)
    • For the development-only Django server:
      1. Make sure you’re in the virtualenv:
            source /home/myname/django_files/django_ajax_demo_venv/bin/activate
      2. cd /home/myname/django_files/django_ajax_demo/
      3. python manage.py runserver
    • Open the browser and load the page:
          http://my.website/color_liker

You should see something like this. Everything you do on this page causes a full-page reload.

(Let me know in the comments if you have any problems, and I’ll be happy to help.)

Update the website for Ajax

Now let’s change it so that you don’t need to click the submit button, and the yes/no buttons reload only themselves–not the entire page.

[All parts: ONE, TWO, THREE]

At this point, it would be a good idea to backup your files.

…to be continued…

(cue cliffhanger segue music)

Three links

Home Depot ignored security warnings for years, employees say

“In 2012, Home Depot hired Ricky Joe Mitchell as its senior IT security architect. Mitchell got the job after being fired from EnerVest Operating in Charelston, South Carolina—and he sabotaged that company’s network in an act of revenge, taking the company offline for 30 days. Mitchell retained his position at Home Depot even after his indictment a year later and remained in charge of Home Deopt’s security until he pled guilty to federal charges in January of 2014.”


iOS 8, thoroughly reviewed

Nice review, and impressive operating system. One day, I’ll have an iOS device newer than my 3.5-year-old third generation iPod Touch.


Browser comparison: How the five leaders stack up in speed, ease of use and more

Convinced me to switch back to Firefox, after two years with Chrome. I thought Firefox was the memory hog, but Chrome is worse. Firefox’s browser seems great, but the bookmark manager is terrible. Freezes badly.

My source code from Mike Hibbert’s free Django tutorial on youtube

After going through the official Django tutorial, I did Mike Hibbert’s free youtube tutorial. I highly recommend doing both of these tutorials, and in this order.

Note that I used Python 3.4 and Django 1.7c2 (and learned Python itself, at the same time as learning Django). The Hibbert tutorial uses–I think–Django 1.4 and (I know) Python 2.something. So there were some minor headaches, but nothing to overshadow its usefulness. Although I ended up completely skipping chapter 21, which is on Whoosh and Haystack content searches.

I backed up my source code at pretty much every other chapter. Here it is in a 820kb zip.

I was seriously considering paying for Hibbert’s intermediate and advanced tutorials (which, as of today, are $36 each), but decided instead to create my own demo website.